Merge branch 'github_master'
Conflicts: .gitmodules
This commit is contained in:
commit
59d5f9f106
23 changed files with 330 additions and 472 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -5,11 +5,21 @@ CMakeCache.txt
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
Makefile
|
Makefile
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
*.cmake
|
||||||
|
|
||||||
libraries/utilities/git_revision.cpp
|
libraries/utilities/git_revision.cpp
|
||||||
|
|
||||||
|
programs/cli_wallet/cli_wallet
|
||||||
|
programs/js_operation_serializer/js_operation_serializer
|
||||||
|
programs/witness_node/witness_node
|
||||||
|
|
||||||
tests/app_test
|
tests/app_test
|
||||||
tests/chain_bench
|
tests/chain_bench
|
||||||
tests/chain_test
|
tests/chain_test
|
||||||
|
tests/intense_test
|
||||||
|
tests/performance_test
|
||||||
|
|
||||||
/doxygen
|
/doxygen
|
||||||
|
|
||||||
|
witness_node_data_dir
|
||||||
|
wallet.json
|
||||||
|
|
|
||||||
5
.gitmodules
vendored
5
.gitmodules
vendored
|
|
@ -1,8 +1,11 @@
|
||||||
|
[submodule "docs"]
|
||||||
|
path = docs
|
||||||
|
url = git@github.com:cryptonomex/graphene.wiki.git
|
||||||
[submodule "libraries/fc"]
|
[submodule "libraries/fc"]
|
||||||
path = libraries/fc
|
path = libraries/fc
|
||||||
url = git@git.syncad.com:/fc.git
|
url = git@git.syncad.com:/fc.git
|
||||||
ignore = dirty
|
ignore = dirty
|
||||||
[submodule "libraries/leveldb"]
|
[submodule "libraries/leveldb"]
|
||||||
path = libraries/leveldb
|
path = libraries/leveldb
|
||||||
url = https://github.com/bitcoin/leveldb.git
|
url = git@github.com:bitcoin/leveldb.git
|
||||||
ignore = dirty
|
ignore = dirty
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
How to use fc async to do recurring tasks
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
_my_task = fc::async( callable, "My Task" );
|
|
||||||
_my_task = fc::schedule( callable, "My Task 2", exec_time );
|
|
||||||
|
|
||||||
Stuff to know about the code
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
`static_variant<t1, t2>` is a *union type* which says "this variable may be either t1 or t2." It is serializable if t1 and t2 are both serializable.
|
|
||||||
|
|
||||||
The file `operations.hpp` documents the available operations, and `database_fixture.hpp` is a good reference for building and submitting transactions for processing.
|
|
||||||
|
|
||||||
Tests also show the way to do many things, but are often cluttered with code that generates corner cases to try to break things in every possible way.
|
|
||||||
|
|
||||||
Visitors are at the end of `operations.hpp` after the large typedef for `operation` as a `static_variant`. TODO: They should be refactored into a separate header.
|
|
||||||
|
|
||||||
Downcasting stuff
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
- You have an `object_id_type` and want to downcast it to a `key_id_type` : `key_id_type( object_id )`
|
|
||||||
- You have an `operation_result` and want to downcast it to an `object_id_type` : `op_result.get<object_id_type>()`
|
|
||||||
- Since `operation_result` is a `static_variant`, the above is also how you downcast `static_variant`
|
|
||||||
|
|
||||||
Debugging FC exceptions with GDB
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
- `catch throw`
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
The Boost which ships with Ubuntu 14.04 LTS is too old. You need to download the Boost tarball for Boost 1.57.0
|
|
||||||
(Note, 1.58.0 requires C++14 and will not build on Ubuntu LTS; this requirement was an accident, see ). Build Boost as follows:
|
|
||||||
|
|
||||||
# tarball available at http://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.bz2/download
|
|
||||||
# sha256sum is 910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967
|
|
||||||
|
|
||||||
BOOST_ROOT=$(echo ~/opt/boost_1_57_0)
|
|
||||||
|
|
||||||
# build Boost from source
|
|
||||||
cd ~/src/boost_1_57_0
|
|
||||||
./bootstrap.sh --prefix=$BOOST_ROOT
|
|
||||||
./b2 link=static variant=debug threading=multi stage
|
|
||||||
./b2 link=static variant=debug threading=multi install
|
|
||||||
|
|
||||||
Then we need to tell `cmake` to use the Boost we just built, instead of using the system-wide Boost:
|
|
||||||
|
|
||||||
cd ~/src/graphene
|
|
||||||
[ -e ./doc/build-ubuntu.md ] && sh -c 'cmake -DBOOST_ROOT="$BOOST_ROOT" -DCMAKE_BUILD_TYPE=Debug .'
|
|
||||||
|
|
||||||
If all goes well, you should see the correct Boost version in the output messages to the above command.
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
* This is draft license text currently under development. This license does not apply to anything right now.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015, Cryptonomex, Inc
|
|
||||||
* 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. No distribution of binaries compiled from modified versions of the code
|
|
||||||
*
|
|
||||||
* 2. Any modifications become property of Cryptonomex, Inc and must be licensed under these same terms
|
|
||||||
*
|
|
||||||
* 3. Any hosting provider is authorized to delete any version of this code at the request Cryptonomex, Inc.
|
|
||||||
*/
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
This is draft license text currently under development. This license does not apply to anything right now.
|
|
||||||
|
|
||||||
Copyright (c) 2015, Cryptonomex, Inc
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
The graphene source code is proprietary and may not be used for any purpose without written permission from Cryptonomex, Inc. The source code is
|
|
||||||
provided publicly for the sole purpose of allowing users to audit the code and compile their own executables. Blockchain technology depends upon
|
|
||||||
a strong consensus on the rules of an open protocol; this source code is provided to completely document the protocol rules (bugs and all).
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. No distribution of binaries compiled from modified versions of the code
|
|
||||||
|
|
||||||
2. Any modifications made to the code become property of Cryptonomex, Inc and must be licensed under these same terms
|
|
||||||
|
|
||||||
3. The software may be forked/cloned on github for the purpose of submitting bug fixes and pull requests. All forked repositories are property of Cryptonomex and by
|
|
||||||
forking you authorize Github.com to delete your fork at the request Cryptonomex, Inc.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
|
|
||||||
Witness scheduler RNG
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The witness scheduler RNG is a random number generator which uses the
|
|
||||||
blockchain random number generator state as its seed.
|
|
||||||
|
|
||||||
The witness scheduler RNG creates an infinite stream of random bytes
|
|
||||||
by computing `sha256( sha256( seed ) + little_endian_64bit(i) )`, increasing
|
|
||||||
`i` from 0 to 1 to 2, etc. The RNG only runs during a scheduling block,
|
|
||||||
and `i` starts from `0` in each scheduling block (relying on different
|
|
||||||
seeds to produce different results).
|
|
||||||
|
|
||||||
This infinite stream of random bytes is equivalent to an infinite
|
|
||||||
stream of random bits in little bit-endian order. Given a bound `B`,
|
|
||||||
the bitstream can be used to produce a random number uniformly
|
|
||||||
distributed in the range `[0, B)` using a sample-and-reject algorithm:
|
|
||||||
|
|
||||||
- Let `n` be the smallest integer such that `2^n >= B`.
|
|
||||||
- Let `x` be the next `n` bits from the bitstream, interpreted as an integer in little bit-endian order.
|
|
||||||
- If `x <= B`, return `x`. Otherwise, throw `x` away and repeat.
|
|
||||||
|
|
||||||
The worst-case running time is unbounded, but each iteration has a
|
|
||||||
termination probability greater than one half. Thus the average-case
|
|
||||||
running time is `2` iterations, and a running time of more than `N`
|
|
||||||
iterations will occur (on average) at most once every `2^N`
|
|
||||||
RNG queries (assuming a worst-case choice of e.g. `B = 2^63+1` for all
|
|
||||||
queries). Since each RNG query schedules a witness, the query rate
|
|
||||||
is (over the long term) equal to the block production rate (although
|
|
||||||
in practice many queries are all performed at once in scheduling
|
|
||||||
blocks). So while it is, in theory, possible for the algorithm to
|
|
||||||
require more than 1000 iterations, in practice this will occur on average
|
|
||||||
only once every `2^1000` blocks (again assuming all queries have
|
|
||||||
worst-case `B`).
|
|
||||||
|
|
||||||
The sample-and-reject algorithm is totally unbiased; every `x` value
|
|
||||||
has equal probability.
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
|
|
||||||
Turn/Token witness scheduling algorithm
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
The algorithm which determines the order of witnesses is referred
|
|
||||||
to as the *witness scheduling algorithm*.
|
|
||||||
|
|
||||||
This was designed by a community bounty in thread
|
|
||||||
https://bitsharestalk.org/index.php/topic,15547.0
|
|
||||||
however, Graphene has an additional requirement which
|
|
||||||
is not taken into account by the solutions in the thread:
|
|
||||||
|
|
||||||
The membership and length of the list of witnesses may change over
|
|
||||||
time.
|
|
||||||
|
|
||||||
So in this article I'll describe my solution.
|
|
||||||
|
|
||||||
Turns and tokens
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The solution is based on terms of *turns* and *tokens*.
|
|
||||||
|
|
||||||
- Newly inserted witnesses start out with a turn and a token.
|
|
||||||
- In order for a witness to be scheduled, it must have a turn and a token.
|
|
||||||
- The scheduler maintains a FIFO of witnesses without tokens.
|
|
||||||
- If no witness has a turn, then the scheduler gives a turn to all witnesses. This is called "emitting a turn."
|
|
||||||
- While less than half of the witnesses have tokens, give a token to the first witness in the FIFO and remove it from the FIFO.
|
|
||||||
- Schedule a witness by picking randomly from all witnesses with both a turn and token.
|
|
||||||
- When a witness is scheduled, it loses its turn and token.
|
|
||||||
|
|
||||||
The generic scheduler
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The generic scheduler implements turns and tokens. It only depends
|
|
||||||
on the C++11 stdlib and boost (not even using fc). Types provided
|
|
||||||
by Graphene are template parameters.
|
|
||||||
|
|
||||||
The generic far future scheduler
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
The far future scheduler is implemented with the following rules:
|
|
||||||
|
|
||||||
- Run until you emit a turn.
|
|
||||||
- Record all witnesses produced.
|
|
||||||
- Run until you emit a second turn.
|
|
||||||
- The witnesses produced between the emission of the first turn (exclusive)
|
|
||||||
and emission of the second turn (inclusive) are called the *far future schedule*.
|
|
||||||
|
|
||||||
Then the schedule for the rest of time is determined by repeating
|
|
||||||
the future schedule indefinitely. The far future scheduler is required
|
|
||||||
to give the scheduling algorithm bounded runtime and memory usage even
|
|
||||||
in chains involving very long gaps.
|
|
||||||
|
|
||||||
Slots
|
|
||||||
-----
|
|
||||||
|
|
||||||
Due to dynamic block interval, we must carefully keep in mind
|
|
||||||
the difference between schedule slots and timestamps. A
|
|
||||||
*schedule slot number* is a positive integer. A slot number of `n`
|
|
||||||
represents the `n`th next block-interval-aligned timestamp after
|
|
||||||
the head block.
|
|
||||||
|
|
||||||
Note that the mapping between slot numbers and timestamps will change
|
|
||||||
if the block interval changes.
|
|
||||||
|
|
||||||
Scheduling blocks
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
When each block is produced, the blockchain must determine whether
|
|
||||||
the scheduler needs to be run. If fewer than `num_witnesses` are
|
|
||||||
scheduled, the scheduler will run until `2*num_witnesses` are scheduled.
|
|
||||||
A block in which the scheduler runs is called a *scheduling block*.
|
|
||||||
|
|
||||||
Changes in the set of active witnesses do not modify the existing
|
|
||||||
schedule. Rather, they will be incorporated into new schedule entries
|
|
||||||
when the scheduler runs in the next scheduling block. Thus, a witness
|
|
||||||
that has lost an election may still produce 1-2 blocks. Such a witness
|
|
||||||
is called a *lame duck*.
|
|
||||||
|
|
||||||
Near vs. far schedule
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
From a particular chain state, it must be possible to specify a
|
|
||||||
mapping from slots to witnesses, called the *total witness schedule*.
|
|
||||||
The total witness schedule is partitioned into a prefix, called the
|
|
||||||
*near schedule*; the remainder is the *far schedule*.
|
|
||||||
|
|
||||||
When a block occurs, `n` entries are *drained* (removed) from the head
|
|
||||||
of the total schedule, where `n` is the slot number of the new block
|
|
||||||
according to its parent block.
|
|
||||||
|
|
||||||
If the block is a scheduling block, the total schedule is further
|
|
||||||
transformed. The new near schedule contains `2*num_witnesses` entries,
|
|
||||||
with the previous near schedule as a prefix. The rest of the near
|
|
||||||
schedule is determined by the current blockchain RNG.
|
|
||||||
|
|
||||||
The new far schedule is determined by running the far future scheduler,
|
|
||||||
as described above. The far future scheduler also obtains entropy
|
|
||||||
from the current blockchain RNG.
|
|
||||||
|
|
||||||
As an optimization, the implementation does not run the far future
|
|
||||||
scheduler until a far-future slot is actually queried. With this
|
|
||||||
optimization, the only circumstance under which validating nodes must
|
|
||||||
run the far future scheduler is when a block gap longer than `num_witnesses`
|
|
||||||
occurs (an extremely rare condition).
|
|
||||||
|
|
||||||
Minimizing impact of selective dropout
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
The ability of any single malicious witness to affect the results of the
|
|
||||||
shuffle algorithm is limited because the RNG is based on bit commitment
|
|
||||||
of the witnesses. However, a malicious witness *is* able to
|
|
||||||
refuse to produce a block. A run of `m` consecutively scheduled
|
|
||||||
malicious witnesses can independently make `m` independent choices
|
|
||||||
of whether to refuse to produce a block. Basically they are able to
|
|
||||||
control `m` bits of entropy in the shuffle algorithm's output.
|
|
||||||
|
|
||||||
It is difficult-to-impossible to entirely eliminate "the last person
|
|
||||||
being evil" problem in trustless distributed RNG's. But we can at least
|
|
||||||
mitigate this vector by rate-limiting changes to the total witness
|
|
||||||
schedule to a very slow rate.
|
|
||||||
|
|
||||||
If every block schedules a witness, our adversary with `m` malicious
|
|
||||||
witnesses gets `m` chances per round to selectively drop out in order
|
|
||||||
to manipulate the shuffle order, allowing `m` attacks per round.
|
|
||||||
If witnesses are only scheduled once per round,
|
|
||||||
a selective dropout requires the malicious witness to produce the
|
|
||||||
scheduling block, limiting the probability to `m/n` attacks per round.
|
|
||||||
1
docs
Submodule
1
docs
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1b53a8eca77783d073ce7cc95991447c3f34b927
|
||||||
|
|
@ -36,4 +36,24 @@ void account_balance_object::adjust_balance(const asset& delta)
|
||||||
balance += delta.amount;
|
balance += delta.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_parameters& params) const
|
||||||
|
{
|
||||||
|
uint64_t bulk_discount_percent = 0;
|
||||||
|
if( lifetime_fees_paid >= params.bulk_discount_threshold_max )
|
||||||
|
bulk_discount_percent = params.max_bulk_discount_percent_of_fee;
|
||||||
|
else if(params.bulk_discount_threshold_max.value !=
|
||||||
|
params.bulk_discount_threshold_min.value)
|
||||||
|
{
|
||||||
|
bulk_discount_percent =
|
||||||
|
(params.max_bulk_discount_percent_of_fee *
|
||||||
|
(lifetime_fees_paid.value -
|
||||||
|
params.bulk_discount_threshold_min.value)) /
|
||||||
|
(params.bulk_discount_threshold_max.value -
|
||||||
|
params.bulk_discount_threshold_min.value);
|
||||||
|
}
|
||||||
|
assert( bulk_discount_percent <= GRAPHENE_100_PERCENT );
|
||||||
|
|
||||||
|
return bulk_discount_percent;
|
||||||
|
}
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#include <graphene/chain/asset_operations.hpp>
|
|
||||||
#include <graphene/chain/database.hpp>
|
|
||||||
#include <graphene/chain/account_object.hpp>
|
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
|
||||||
} }
|
|
||||||
|
|
@ -89,7 +89,7 @@ void database::adjust_core_in_orders( const account_object& acnt, asset delta )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::deposit_cashback( const account_object& acct, share_type amount )
|
void database::deposit_cashback(const account_object& acct, share_type amount, bool require_vesting)
|
||||||
{
|
{
|
||||||
// If we don't have a VBO, or if it has the wrong maturity
|
// If we don't have a VBO, or if it has the wrong maturity
|
||||||
// due to a policy change, cut it loose.
|
// due to a policy change, cut it loose.
|
||||||
|
|
@ -112,7 +112,10 @@ void database::deposit_cashback( const account_object& acct, share_type amount )
|
||||||
|
|
||||||
modify( cashback_vb, [&]( vesting_balance_object& obj )
|
modify( cashback_vb, [&]( vesting_balance_object& obj )
|
||||||
{
|
{
|
||||||
obj.deposit( now, amount );
|
if( require_vesting )
|
||||||
|
obj.deposit(now, amount);
|
||||||
|
else
|
||||||
|
obj.deposit_vested(now, amount);
|
||||||
} );
|
} );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +127,7 @@ void database::deposit_cashback( const account_object& acct, share_type amount )
|
||||||
|
|
||||||
cdd_vesting_policy policy;
|
cdd_vesting_policy policy;
|
||||||
policy.vesting_seconds = global_vesting_seconds;
|
policy.vesting_seconds = global_vesting_seconds;
|
||||||
policy.coin_seconds_earned = 0;
|
policy.coin_seconds_earned = require_vesting? 0 : amount.value * policy.vesting_seconds;
|
||||||
policy.coin_seconds_earned_last_update = now;
|
policy.coin_seconds_earned_last_update = now;
|
||||||
|
|
||||||
obj.policy = policy;
|
obj.policy = policy;
|
||||||
|
|
|
||||||
|
|
@ -357,10 +357,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
process_fees_helper(database& d, const global_property_object& gpo)
|
process_fees_helper(database& d, const global_property_object& gpo)
|
||||||
: d(d), props(gpo) {}
|
: d(d), props(gpo) {}
|
||||||
|
|
||||||
void operator()(const account_object& a) {
|
share_type cut_fee(share_type a, uint16_t p)const
|
||||||
const account_statistics_object& stats = a.statistics(d);
|
|
||||||
|
|
||||||
auto cut_fee = [](share_type a, uint16_t p) -> share_type
|
|
||||||
{
|
{
|
||||||
if( a == 0 || p == 0 )
|
if( a == 0 || p == 0 )
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -371,72 +368,70 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
r *= p;
|
r *= p;
|
||||||
r /= GRAPHENE_100_PERCENT;
|
r /= GRAPHENE_100_PERCENT;
|
||||||
return r.to_uint64();
|
return r.to_uint64();
|
||||||
};
|
|
||||||
|
|
||||||
if( stats.pending_fees > 0 )
|
|
||||||
{
|
|
||||||
share_type core_fee_subtotal(stats.pending_fees);
|
|
||||||
share_type bulk_cashback = share_type(0);
|
|
||||||
if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
|
|
||||||
a.is_member(d.head_block_time()) )
|
|
||||||
{
|
|
||||||
uint64_t bulk_discount_percent = 0;
|
|
||||||
if( stats.lifetime_fees_paid >= props.parameters.bulk_discount_threshold_max )
|
|
||||||
bulk_discount_percent = props.parameters.max_bulk_discount_percent_of_fee;
|
|
||||||
else if(props.parameters.bulk_discount_threshold_max.value !=
|
|
||||||
props.parameters.bulk_discount_threshold_min.value)
|
|
||||||
{
|
|
||||||
bulk_discount_percent =
|
|
||||||
(props.parameters.max_bulk_discount_percent_of_fee *
|
|
||||||
(stats.lifetime_fees_paid.value -
|
|
||||||
props.parameters.bulk_discount_threshold_min.value)) /
|
|
||||||
(props.parameters.bulk_discount_threshold_max.value -
|
|
||||||
props.parameters.bulk_discount_threshold_min.value);
|
|
||||||
}
|
|
||||||
assert( bulk_discount_percent <= GRAPHENE_100_PERCENT );
|
|
||||||
assert( bulk_discount_percent >= 0 );
|
|
||||||
|
|
||||||
bulk_cashback = cut_fee(core_fee_subtotal, bulk_discount_percent);
|
|
||||||
assert( bulk_cashback <= core_fee_subtotal );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
share_type core_fee_total = core_fee_subtotal - bulk_cashback;
|
void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting)
|
||||||
share_type network_cut = cut_fee(core_fee_total, a.network_fee_percentage);
|
{
|
||||||
|
share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);
|
||||||
assert( network_cut <= core_fee_total );
|
assert( network_cut <= core_fee_total );
|
||||||
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee);
|
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee);
|
||||||
share_type accumulated = network_cut - burned;
|
share_type accumulated = network_cut - burned;
|
||||||
assert( accumulated + burned == network_cut );
|
assert( accumulated + burned == network_cut );
|
||||||
share_type lifetime_cut = cut_fee(core_fee_total, a.lifetime_referrer_fee_percentage);
|
share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);
|
||||||
share_type referral = core_fee_total - network_cut - lifetime_cut;
|
share_type referral = core_fee_total - network_cut - lifetime_cut;
|
||||||
|
|
||||||
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
|
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
|
||||||
d.accumulated_fees += network_cut;
|
d.accumulated_fees += network_cut;
|
||||||
});
|
});
|
||||||
|
|
||||||
d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) {
|
|
||||||
s.lifetime_fees_paid += core_fee_total;
|
|
||||||
s.pending_fees = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
d.deposit_cashback( a, bulk_cashback );
|
|
||||||
|
|
||||||
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
|
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
|
||||||
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
|
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
|
||||||
// it directly.
|
// it directly.
|
||||||
share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage);
|
share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);
|
||||||
share_type registrar_cut = referral - referrer_cut;
|
share_type registrar_cut = referral - referrer_cut;
|
||||||
|
|
||||||
d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut);
|
d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);
|
||||||
d.deposit_cashback(d.get(a.referrer), referrer_cut);
|
d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);
|
||||||
d.deposit_cashback(d.get(a.registrar), registrar_cut);
|
d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);
|
||||||
|
|
||||||
idump((referrer_cut)(registrar_cut)(bulk_cashback)(accumulated)(burned)(lifetime_cut)(core_fee_subtotal));
|
assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total );
|
||||||
assert( referrer_cut + registrar_cut + bulk_cashback + accumulated + burned + lifetime_cut == core_fee_subtotal );
|
}
|
||||||
|
|
||||||
|
void operator()(const account_object& a) {
|
||||||
|
const account_statistics_object& stats = a.statistics(d);
|
||||||
|
|
||||||
|
if( stats.pending_fees > 0 )
|
||||||
|
{
|
||||||
|
share_type vesting_fee_subtotal(stats.pending_fees);
|
||||||
|
share_type vested_fee_subtotal(stats.pending_vested_fees);
|
||||||
|
share_type vesting_cashback, vested_cashback;
|
||||||
|
|
||||||
|
if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
|
||||||
|
a.is_member(d.head_block_time()) )
|
||||||
|
{
|
||||||
|
auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters);
|
||||||
|
vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
|
||||||
|
vesting_fee_subtotal -= vesting_cashback;
|
||||||
|
|
||||||
|
vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
|
||||||
|
vested_fee_subtotal -= vested_cashback;
|
||||||
|
}
|
||||||
|
|
||||||
|
pay_out_fees(a, vesting_fee_subtotal, true);
|
||||||
|
d.deposit_cashback(a, vesting_cashback, true);
|
||||||
|
pay_out_fees(a, vested_fee_subtotal, false);
|
||||||
|
d.deposit_cashback(a, vested_cashback, false);
|
||||||
|
|
||||||
|
d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
|
||||||
|
s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
|
||||||
|
s.pending_fees = 0;
|
||||||
|
s.pending_vested_fees = 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} fees_helper(*this, gpo);
|
} fee_helper(*this, gpo);
|
||||||
|
|
||||||
perform_account_maintenance(std::tie(tally_helper, fees_helper));
|
perform_account_maintenance(std::tie(tally_helper, fee_helper));
|
||||||
|
|
||||||
struct clear_canary {
|
struct clear_canary {
|
||||||
clear_canary(vector<uint64_t>& target): target(target){}
|
clear_canary(vector<uint64_t>& target): target(target){}
|
||||||
|
|
@ -451,9 +446,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
update_active_witnesses();
|
update_active_witnesses();
|
||||||
update_active_delegates();
|
update_active_delegates();
|
||||||
|
|
||||||
const global_property_object& global_properties = get_global_properties();
|
if( gpo.pending_parameters )
|
||||||
if( global_properties.pending_parameters )
|
modify(gpo, [](global_property_object& p) {
|
||||||
modify(get_global_properties(), [](global_property_object& p) {
|
|
||||||
p.parameters = std::move(*p.pending_parameters);
|
p.parameters = std::move(*p.pending_parameters);
|
||||||
p.pending_parameters.reset();
|
p.pending_parameters.reset();
|
||||||
});
|
});
|
||||||
|
|
@ -472,7 +466,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
}
|
}
|
||||||
|
|
||||||
auto next_maintenance_time = get<dynamic_global_property_object>(dynamic_global_property_id_type()).next_maintenance_time;
|
auto next_maintenance_time = get<dynamic_global_property_object>(dynamic_global_property_id_type()).next_maintenance_time;
|
||||||
auto maintenance_interval = get_global_properties().parameters.maintenance_interval;
|
auto maintenance_interval = gpo.parameters.maintenance_interval;
|
||||||
|
|
||||||
if( next_maintenance_time <= next_block.timestamp )
|
if( next_maintenance_time <= next_block.timestamp )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,10 @@ namespace graphene { namespace chain {
|
||||||
d.fee_pool -= core_fee_paid;
|
d.fee_pool -= core_fee_paid;
|
||||||
});
|
});
|
||||||
db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) {
|
db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) {
|
||||||
|
if( core_fee_paid > db().get_global_properties().parameters.cashback_vesting_threshold )
|
||||||
s.pending_fees += core_fee_paid;
|
s.pending_fees += core_fee_paid;
|
||||||
|
else
|
||||||
|
s.pending_vested_fees += core_fee_paid;
|
||||||
});
|
});
|
||||||
} FC_CAPTURE_AND_RETHROW() }
|
} FC_CAPTURE_AND_RETHROW() }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,20 @@ namespace graphene { namespace chain {
|
||||||
/**
|
/**
|
||||||
* Tracks the fees paid by this account which have not been disseminated to the various parties that receive
|
* Tracks the fees paid by this account which have not been disseminated to the various parties that receive
|
||||||
* them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
|
* them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
|
||||||
* doing massive amounts of uint128 math on each and every operation.
|
* doing massive amounts of uint128 arithmetic on each and every operation.
|
||||||
*
|
*
|
||||||
* These fees will be paid out and this counter will reset during the maintenance interval.
|
*These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance
|
||||||
|
*interval.
|
||||||
*/
|
*/
|
||||||
share_type pending_fees;
|
share_type pending_fees;
|
||||||
|
/**
|
||||||
|
* Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately
|
||||||
|
* available for withdrawal) rather than requiring the normal vesting period.
|
||||||
|
*/
|
||||||
|
share_type pending_vested_fees;
|
||||||
|
|
||||||
|
/// @brief Calculate the percentage discount this user receives on his fees
|
||||||
|
uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -290,5 +299,6 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain
|
||||||
(most_recent_op)
|
(most_recent_op)
|
||||||
(total_core_in_orders)
|
(total_core_in_orders)
|
||||||
(lifetime_fees_paid)
|
(lifetime_fees_paid)
|
||||||
|
(pending_fees)(pending_vested_fees)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ namespace graphene { namespace chain {
|
||||||
void adjust_core_in_orders( const account_object& acnt, asset delta );
|
void adjust_core_in_orders( const account_object& acnt, asset delta );
|
||||||
|
|
||||||
// helper to handle cashback rewards
|
// helper to handle cashback rewards
|
||||||
void deposit_cashback( const account_object& acct, share_type amount );
|
void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true);
|
||||||
|
|
||||||
//////////////////// db_debug.cpp ////////////////////
|
//////////////////// db_debug.cpp ////////////////////
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,7 @@ namespace graphene { namespace chain {
|
||||||
uint32_t account_len5_fee;
|
uint32_t account_len5_fee;
|
||||||
uint32_t account_len4_fee;
|
uint32_t account_len4_fee;
|
||||||
uint32_t account_len3_fee;
|
uint32_t account_len3_fee;
|
||||||
|
uint32_t account_len2_fee;
|
||||||
uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false
|
uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false
|
||||||
uint32_t account_whitelist_fee; ///< the fee to whitelist an account
|
uint32_t account_whitelist_fee; ///< the fee to whitelist an account
|
||||||
uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates
|
uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates
|
||||||
|
|
@ -538,6 +539,7 @@ FC_REFLECT( graphene::chain::fee_schedule_type,
|
||||||
(account_len5_fee)
|
(account_len5_fee)
|
||||||
(account_len4_fee)
|
(account_len4_fee)
|
||||||
(account_len3_fee)
|
(account_len3_fee)
|
||||||
|
(account_len2_fee)
|
||||||
(account_premium_fee)
|
(account_premium_fee)
|
||||||
(account_whitelist_fee)
|
(account_whitelist_fee)
|
||||||
(delegate_create_fee)
|
(delegate_create_fee)
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ namespace graphene { namespace chain {
|
||||||
vesting_policy_context(
|
vesting_policy_context(
|
||||||
asset _balance,
|
asset _balance,
|
||||||
fc::time_point_sec _now,
|
fc::time_point_sec _now,
|
||||||
asset _amount )
|
asset _amount)
|
||||||
: balance( _balance ), now( _now ), amount( _amount ) {}
|
: balance(_balance), now(_now), amount(_amount) {}
|
||||||
|
|
||||||
asset balance;
|
asset balance;
|
||||||
fc::time_point_sec now;
|
fc::time_point_sec now;
|
||||||
|
|
@ -53,11 +53,14 @@ namespace graphene { namespace chain {
|
||||||
share_type begin_balance; // same asset as balance
|
share_type begin_balance; // same asset as balance
|
||||||
share_type total_withdrawn; // same asset as balance
|
share_type total_withdrawn; // same asset as balance
|
||||||
|
|
||||||
asset get_allowed_withdraw( const vesting_policy_context& ctx )const;
|
asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
|
||||||
bool is_deposit_allowed( const vesting_policy_context& ctx )const;
|
bool is_deposit_allowed(const vesting_policy_context& ctx)const;
|
||||||
bool is_withdraw_allowed( const vesting_policy_context& ctx )const;
|
bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; }
|
||||||
void on_deposit( const vesting_policy_context& ctx );
|
bool is_withdraw_allowed(const vesting_policy_context& ctx)const;
|
||||||
void on_withdraw( const vesting_policy_context& ctx );
|
void on_deposit(const vesting_policy_context& ctx);
|
||||||
|
void on_deposit_vested(const vesting_policy_context&)
|
||||||
|
{ FC_THROW( "May not deposit vested into a linear vesting balance." ); }
|
||||||
|
void on_withdraw(const vesting_policy_context& ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cdd_vesting_policy
|
struct cdd_vesting_policy
|
||||||
|
|
@ -71,20 +74,22 @@ namespace graphene { namespace chain {
|
||||||
* non-destructively figure out how many coin seconds
|
* non-destructively figure out how many coin seconds
|
||||||
* are available.
|
* are available.
|
||||||
*/
|
*/
|
||||||
fc::uint128_t compute_coin_seconds_earned( const vesting_policy_context& ctx )const;
|
fc::uint128_t compute_coin_seconds_earned(const vesting_policy_context& ctx)const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update coin_seconds_earned and
|
* Update coin_seconds_earned and
|
||||||
* coin_seconds_earned_last_update fields; called by both
|
* coin_seconds_earned_last_update fields; called by both
|
||||||
* on_deposit() and on_withdraw().
|
* on_deposit() and on_withdraw().
|
||||||
*/
|
*/
|
||||||
void update_coin_seconds_earned( const vesting_policy_context& ctx );
|
void update_coin_seconds_earned(const vesting_policy_context& ctx);
|
||||||
|
|
||||||
asset get_allowed_withdraw( const vesting_policy_context& ctx )const;
|
asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
|
||||||
bool is_deposit_allowed( const vesting_policy_context& ctx )const;
|
bool is_deposit_allowed(const vesting_policy_context& ctx)const;
|
||||||
bool is_withdraw_allowed( const vesting_policy_context& ctx )const;
|
bool is_deposit_vested_allowed(const vesting_policy_context& ctx)const;
|
||||||
void on_deposit( const vesting_policy_context& ctx );
|
bool is_withdraw_allowed(const vesting_policy_context& ctx)const;
|
||||||
void on_withdraw( const vesting_policy_context& ctx );
|
void on_deposit(const vesting_policy_context& ctx);
|
||||||
|
void on_deposit_vested(const vesting_policy_context& ctx);
|
||||||
|
void on_withdraw(const vesting_policy_context& ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef fc::static_variant<
|
typedef fc::static_variant<
|
||||||
|
|
@ -111,8 +116,12 @@ namespace graphene { namespace chain {
|
||||||
/**
|
/**
|
||||||
* Used to increase existing vesting balances.
|
* Used to increase existing vesting balances.
|
||||||
*/
|
*/
|
||||||
void deposit( const fc::time_point_sec& now, const asset& amount );
|
void deposit(const fc::time_point_sec& now, const asset& amount);
|
||||||
bool is_deposit_allowed( const fc::time_point_sec& now, const asset& amount )const;
|
bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const;
|
||||||
|
|
||||||
|
/// @brief Deposit amount into vesting balance, making the new funds vest immediately
|
||||||
|
void deposit_vested(const fc::time_point_sec& now, const asset& amount);
|
||||||
|
bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to remove a vesting balance from the VBO. As well
|
* Used to remove a vesting balance from the VBO. As well
|
||||||
|
|
@ -122,27 +131,27 @@ namespace graphene { namespace chain {
|
||||||
* The money doesn't "go" anywhere; the caller is responsible
|
* The money doesn't "go" anywhere; the caller is responsible
|
||||||
* for crediting it to the proper account.
|
* for crediting it to the proper account.
|
||||||
*/
|
*/
|
||||||
void withdraw( const fc::time_point_sec& now, const asset& amount );
|
void withdraw(const fc::time_point_sec& now, const asset& amount);
|
||||||
bool is_withdraw_allowed( const fc::time_point_sec& now, const asset& amount )const;
|
bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::linear_vesting_policy,
|
FC_REFLECT(graphene::chain::linear_vesting_policy,
|
||||||
(vesting_seconds)
|
(vesting_seconds)
|
||||||
(begin_date)
|
(begin_date)
|
||||||
(begin_balance)
|
(begin_balance)
|
||||||
(total_withdrawn)
|
(total_withdrawn)
|
||||||
)
|
)
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::cdd_vesting_policy,
|
FC_REFLECT(graphene::chain::cdd_vesting_policy,
|
||||||
(vesting_seconds)
|
(vesting_seconds)
|
||||||
(coin_seconds_earned)
|
(coin_seconds_earned)
|
||||||
(coin_seconds_earned_last_update)
|
(coin_seconds_earned_last_update)
|
||||||
)
|
)
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::vesting_balance_object, (graphene::db::object),
|
FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),
|
||||||
(owner)
|
(owner)
|
||||||
(balance)
|
(balance)
|
||||||
(policy)
|
(policy)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,8 @@ share_type account_create_operation::calculate_fee( const fee_schedule_type& sch
|
||||||
core_fee_required = schedule.account_len4_fee;
|
core_fee_required = schedule.account_len4_fee;
|
||||||
else if( s == 3 )
|
else if( s == 3 )
|
||||||
core_fee_required = schedule.account_len3_fee;
|
core_fee_required = schedule.account_len3_fee;
|
||||||
|
else if( s == 2 )
|
||||||
|
core_fee_required = schedule.account_len2_fee;
|
||||||
|
|
||||||
return core_fee_required;
|
return core_fee_required;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,66 +20,64 @@
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
inline bool sum_below_max_shares( const asset& a, const asset& b )
|
inline bool sum_below_max_shares(const asset& a, const asset& b)
|
||||||
{
|
{
|
||||||
assert( GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY );
|
assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY);
|
||||||
return ( a.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
|
return (a.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
|
||||||
&& ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
|
&& ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
|
||||||
&& ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY)
|
&& ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
asset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx ) const
|
asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const
|
||||||
{
|
{
|
||||||
if( ctx.now <= begin_date )
|
if(ctx.now <= begin_date)
|
||||||
return asset( 0, ctx.balance.asset_id );
|
return asset(0, ctx.balance.asset_id);
|
||||||
if( vesting_seconds == 0 )
|
if(vesting_seconds == 0)
|
||||||
return ctx.balance;
|
return ctx.balance;
|
||||||
|
|
||||||
int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds();
|
int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds();
|
||||||
|
|
||||||
// if elapsed_seconds <= 0, then ctx.now <= begin_date,
|
// if elapsed_seconds <= 0, then ctx.now <= begin_date,
|
||||||
// and we should have returned above.
|
// and we should have returned above.
|
||||||
assert( elapsed_seconds > 0 );
|
assert(elapsed_seconds > 0);
|
||||||
|
|
||||||
fc::uint128_t total_allowed = begin_balance.value;
|
fc::uint128_t total_allowed = begin_balance.value;
|
||||||
total_allowed *= uint64_t( elapsed_seconds );
|
total_allowed *= uint64_t(elapsed_seconds);
|
||||||
total_allowed /= vesting_seconds;
|
total_allowed /= vesting_seconds;
|
||||||
|
|
||||||
if( total_allowed <= total_withdrawn.value )
|
if(total_allowed <= total_withdrawn.value)
|
||||||
return asset( 0, ctx.balance.asset_id );
|
return asset(0, ctx.balance.asset_id);
|
||||||
total_allowed -= total_withdrawn.value;
|
total_allowed -= total_withdrawn.value;
|
||||||
FC_ASSERT( total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY );
|
FC_ASSERT(total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY);
|
||||||
return asset( total_allowed.to_uint64(), ctx.balance.asset_id );
|
return asset(total_allowed.to_uint64(), ctx.balance.asset_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void linear_vesting_policy::on_deposit( const vesting_policy_context& ctx )
|
void linear_vesting_policy::on_deposit(const vesting_policy_context& ctx)
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool linear_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const
|
bool linear_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const
|
||||||
{
|
{
|
||||||
return (ctx.amount.asset_id == ctx.balance.asset_id)
|
return (ctx.amount.asset_id == ctx.balance.asset_id)
|
||||||
&& sum_below_max_shares( ctx.amount, ctx.balance );
|
&& sum_below_max_shares(ctx.amount, ctx.balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void linear_vesting_policy::on_withdraw( const vesting_policy_context& ctx )
|
void linear_vesting_policy::on_withdraw(const vesting_policy_context& ctx)
|
||||||
{
|
{
|
||||||
total_withdrawn += ctx.amount.amount;
|
total_withdrawn += ctx.amount.amount;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool linear_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const
|
bool linear_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const
|
||||||
{
|
{
|
||||||
return ( ctx.amount <= get_allowed_withdraw( ctx ) );
|
return (ctx.amount <= get_allowed_withdraw(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_policy_context& ctx )const
|
fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned(const vesting_policy_context& ctx)const
|
||||||
{
|
{
|
||||||
assert( ctx.now >= coin_seconds_earned_last_update );
|
assert(ctx.now >= coin_seconds_earned_last_update);
|
||||||
int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds();
|
int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds();
|
||||||
assert( delta_seconds >= 0 );
|
assert(delta_seconds >= 0);
|
||||||
|
|
||||||
fc::uint128_t delta_coin_seconds = ctx.balance.amount.value;
|
fc::uint128_t delta_coin_seconds = ctx.balance.amount.value;
|
||||||
delta_coin_seconds *= delta_seconds;
|
delta_coin_seconds *= delta_seconds;
|
||||||
|
|
@ -87,58 +85,63 @@ fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_pol
|
||||||
fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value;
|
fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value;
|
||||||
coin_seconds_earned_cap *= vesting_seconds;
|
coin_seconds_earned_cap *= vesting_seconds;
|
||||||
|
|
||||||
return std::min(
|
return std::min(coin_seconds_earned + delta_coin_seconds, coin_seconds_earned_cap);
|
||||||
coin_seconds_earned + delta_coin_seconds,
|
|
||||||
coin_seconds_earned_cap
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cdd_vesting_policy::update_coin_seconds_earned( const vesting_policy_context& ctx )
|
void cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context& ctx)
|
||||||
{
|
{
|
||||||
coin_seconds_earned = compute_coin_seconds_earned( ctx );
|
coin_seconds_earned = compute_coin_seconds_earned(ctx);
|
||||||
coin_seconds_earned_last_update = ctx.now;
|
coin_seconds_earned_last_update = ctx.now;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asset cdd_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const
|
asset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const
|
||||||
{
|
{
|
||||||
fc::uint128_t cs_earned = compute_coin_seconds_earned( ctx );
|
fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx);
|
||||||
fc::uint128_t withdraw_available = cs_earned / vesting_seconds;
|
fc::uint128_t withdraw_available = cs_earned / vesting_seconds;
|
||||||
assert( withdraw_available <= ctx.balance.amount.value );
|
assert(withdraw_available <= ctx.balance.amount.value);
|
||||||
return asset( withdraw_available.to_uint64(), ctx.balance.asset_id );
|
return asset(withdraw_available.to_uint64(), ctx.balance.asset_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cdd_vesting_policy::on_deposit( const vesting_policy_context& ctx )
|
void cdd_vesting_policy::on_deposit(const vesting_policy_context& ctx)
|
||||||
{
|
{
|
||||||
update_coin_seconds_earned( ctx );
|
update_coin_seconds_earned(ctx);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cdd_vesting_policy::on_withdraw( const vesting_policy_context& ctx )
|
void cdd_vesting_policy::on_deposit_vested(const vesting_policy_context& ctx)
|
||||||
{
|
{
|
||||||
update_coin_seconds_earned( ctx );
|
on_deposit(ctx);
|
||||||
|
coin_seconds_earned += ctx.amount.amount.value * vesting_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cdd_vesting_policy::on_withdraw(const vesting_policy_context& ctx)
|
||||||
|
{
|
||||||
|
update_coin_seconds_earned(ctx);
|
||||||
fc::uint128_t coin_seconds_needed = ctx.amount.amount.value;
|
fc::uint128_t coin_seconds_needed = ctx.amount.amount.value;
|
||||||
coin_seconds_needed *= vesting_seconds;
|
coin_seconds_needed *= vesting_seconds;
|
||||||
// is_withdraw_allowed should forbid any withdrawal that
|
// is_withdraw_allowed should forbid any withdrawal that
|
||||||
// would trigger this assert
|
// would trigger this assert
|
||||||
assert( coin_seconds_needed <= coin_seconds_earned );
|
assert(coin_seconds_needed <= coin_seconds_earned);
|
||||||
|
|
||||||
coin_seconds_earned -= coin_seconds_needed;
|
coin_seconds_earned -= coin_seconds_needed;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cdd_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const
|
bool cdd_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const
|
||||||
{
|
{
|
||||||
return (ctx.amount.asset_id == ctx.balance.asset_id)
|
return (ctx.amount.asset_id == ctx.balance.asset_id)
|
||||||
&& sum_below_max_shares( ctx.amount, ctx.balance );
|
&& sum_below_max_shares(ctx.amount, ctx.balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cdd_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const
|
bool cdd_vesting_policy::is_deposit_vested_allowed(const vesting_policy_context& ctx) const
|
||||||
{
|
{
|
||||||
return ( ctx.amount <= get_allowed_withdraw( ctx ) );
|
return is_deposit_allowed(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define VESTING_VISITOR( NAME, MAYBE_CONST ) \
|
bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const
|
||||||
|
{
|
||||||
|
return (ctx.amount <= get_allowed_withdraw(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VESTING_VISITOR(NAME, MAYBE_CONST) \
|
||||||
struct NAME ## _visitor \
|
struct NAME ## _visitor \
|
||||||
{ \
|
{ \
|
||||||
typedef decltype( \
|
typedef decltype( \
|
||||||
|
|
@ -151,49 +154,63 @@ struct NAME ## _visitor \
|
||||||
const time_point_sec& now, \
|
const time_point_sec& now, \
|
||||||
const asset& amount \
|
const asset& amount \
|
||||||
) \
|
) \
|
||||||
: ctx( balance, now, amount ) {} \
|
: ctx(balance, now, amount) {} \
|
||||||
\
|
\
|
||||||
template< typename Policy > \
|
template< typename Policy > \
|
||||||
result_type \
|
result_type \
|
||||||
operator()( MAYBE_CONST Policy& policy ) MAYBE_CONST \
|
operator()(MAYBE_CONST Policy& policy) MAYBE_CONST \
|
||||||
{ \
|
{ \
|
||||||
return policy.NAME( ctx ); \
|
return policy.NAME(ctx); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
vesting_policy_context ctx; \
|
vesting_policy_context ctx; \
|
||||||
}
|
}
|
||||||
|
|
||||||
VESTING_VISITOR( on_deposit, );
|
VESTING_VISITOR(on_deposit,);
|
||||||
VESTING_VISITOR( on_withdraw, );
|
VESTING_VISITOR(on_deposit_vested,);
|
||||||
VESTING_VISITOR( is_deposit_allowed, const );
|
VESTING_VISITOR(on_withdraw,);
|
||||||
VESTING_VISITOR( is_withdraw_allowed, const );
|
VESTING_VISITOR(is_deposit_allowed, const);
|
||||||
|
VESTING_VISITOR(is_deposit_vested_allowed, const);
|
||||||
|
VESTING_VISITOR(is_withdraw_allowed, const);
|
||||||
|
|
||||||
bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const
|
bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const
|
||||||
{
|
{
|
||||||
return policy.visit( is_deposit_allowed_visitor( balance, now, amount ) );
|
return policy.visit(is_deposit_allowed_visitor(balance, now, amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const
|
bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const
|
||||||
{
|
{
|
||||||
bool result = policy.visit( is_withdraw_allowed_visitor( balance, now, amount ) );
|
bool result = policy.visit(is_withdraw_allowed_visitor(balance, now, amount));
|
||||||
// if some policy allows you to withdraw more than your balance,
|
// if some policy allows you to withdraw more than your balance,
|
||||||
// there's a programming bug in the policy algorithm
|
// there's a programming bug in the policy algorithm
|
||||||
assert( (amount <= balance) || (!result) );
|
assert((amount <= balance) || (!result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount)
|
void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount)
|
||||||
{
|
{
|
||||||
on_deposit_visitor vtor( balance, now, amount );
|
on_deposit_visitor vtor(balance, now, amount);
|
||||||
policy.visit( vtor );
|
policy.visit(vtor);
|
||||||
balance += amount;
|
balance += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vesting_balance_object::deposit_vested(const time_point_sec& now, const asset& amount)
|
||||||
|
{
|
||||||
|
on_deposit_vested_visitor vtor(balance, now, amount);
|
||||||
|
policy.visit(vtor);
|
||||||
|
balance += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vesting_balance_object::is_deposit_vested_allowed(const time_point_sec& now, const asset& amount) const
|
||||||
|
{
|
||||||
|
return policy.visit(is_deposit_vested_allowed_visitor(balance, now, amount));
|
||||||
|
}
|
||||||
|
|
||||||
void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount)
|
void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount)
|
||||||
{
|
{
|
||||||
assert( amount <= balance );
|
assert(amount <= balance);
|
||||||
on_withdraw_visitor vtor( balance, now, amount );
|
on_withdraw_visitor vtor(balance, now, amount);
|
||||||
policy.visit( vtor );
|
policy.visit(vtor);
|
||||||
balance -= amount;
|
balance -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ void database_fixture::verify_asset_supplies( )const
|
||||||
for( const account_statistics_object& a : statistics_index )
|
for( const account_statistics_object& a : statistics_index )
|
||||||
{
|
{
|
||||||
reported_core_in_orders += a.total_core_in_orders;
|
reported_core_in_orders += a.total_core_in_orders;
|
||||||
total_balances[asset_id_type()] += a.pending_fees;
|
total_balances[asset_id_type()] += a.pending_fees + a.pending_vested_fees;
|
||||||
}
|
}
|
||||||
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
|
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
@ -236,4 +237,77 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To have a secure random number we need to ensure that the same
|
||||||
|
* delegate does not get to produce two blocks in a row. There is
|
||||||
|
* always a chance that the last delegate of one round will be the
|
||||||
|
* first delegate of the next round.
|
||||||
|
*
|
||||||
|
* This means that when we shuffle delegates we need to make sure
|
||||||
|
* that there is at least N/2 delegates between consecutive turns
|
||||||
|
* of the same delegate. This means that durring the random
|
||||||
|
* shuffle we need to restrict the placement of delegates to maintain
|
||||||
|
* this invariant.
|
||||||
|
*
|
||||||
|
* This test checks the requirement using Monte Carlo approach
|
||||||
|
* (produce lots of blocks and check the invariant holds).
|
||||||
|
*/
|
||||||
|
BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture )
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
size_t num_witnesses = db.get_global_properties().active_witnesses.size();
|
||||||
|
size_t dmin = num_witnesses >> 1;
|
||||||
|
|
||||||
|
vector< witness_id_type > cur_round;
|
||||||
|
vector< witness_id_type > full_schedule;
|
||||||
|
// if we make the maximum witness count testable,
|
||||||
|
// we'll need to enlarge this.
|
||||||
|
std::bitset< 0x40 > witness_seen;
|
||||||
|
size_t total_blocks = 1000000;
|
||||||
|
|
||||||
|
cur_round.reserve( num_witnesses );
|
||||||
|
full_schedule.reserve( total_blocks );
|
||||||
|
|
||||||
|
// we assert so the test doesn't continue, which would
|
||||||
|
// corrupt memory
|
||||||
|
assert( num_witnesses <= witness_seen.size() );
|
||||||
|
|
||||||
|
while( full_schedule.size() < total_blocks )
|
||||||
|
{
|
||||||
|
witness_id_type wid = db.get_scheduled_witness( 1 ).first;
|
||||||
|
full_schedule.push_back( wid );
|
||||||
|
cur_round.push_back( wid );
|
||||||
|
if( cur_round.size() == num_witnesses )
|
||||||
|
{
|
||||||
|
wdump( (cur_round) );
|
||||||
|
// check that the current round contains exactly 1 copy
|
||||||
|
// of each witness
|
||||||
|
witness_seen.reset();
|
||||||
|
for( const witness_id_type& w : cur_round )
|
||||||
|
{
|
||||||
|
uint64_t inst = w.instance.value;
|
||||||
|
BOOST_CHECK( !witness_seen.test( inst ) );
|
||||||
|
assert( !witness_seen.test( inst ) );
|
||||||
|
witness_seen.set( inst );
|
||||||
|
}
|
||||||
|
cur_round.clear();
|
||||||
|
}
|
||||||
|
generate_block();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( size_t i=0,m=full_schedule.size(); i<m; i++ )
|
||||||
|
{
|
||||||
|
for( size_t j=i+1,n=std::min( m, i+dmin ); j<n; j++ )
|
||||||
|
{
|
||||||
|
BOOST_CHECK( full_schedule[i] != full_schedule[j] );
|
||||||
|
assert( full_schedule[i] != full_schedule[j] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (fc::exception& e) {
|
||||||
|
edump((e.to_detail_string()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
||||||
|
|
@ -1969,25 +1969,6 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test )
|
||||||
BOOST_CHECK_EQUAL(witness->accumulated_income.value, 0);
|
BOOST_CHECK_EQUAL(witness->accumulated_income.value, 0);
|
||||||
} FC_LOG_AND_RETHROW() }
|
} FC_LOG_AND_RETHROW() }
|
||||||
|
|
||||||
/**
|
|
||||||
* To have a secure random number we need to ensure that the same
|
|
||||||
* delegate does not get to produce two blocks in a row. There is
|
|
||||||
* always a chance that the last delegate of one round will be the
|
|
||||||
* first delegate of the next round.
|
|
||||||
*
|
|
||||||
* This means that when we shuffle delegates we need to make sure
|
|
||||||
* that there is at least N/2 delegates between consecutive turns
|
|
||||||
* of the same delegate. This means that durring the random
|
|
||||||
* shuffle we need to restrict the placement of delegates to maintain
|
|
||||||
* this invariant.
|
|
||||||
*/
|
|
||||||
BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES( unimp_delegate_groups_test, 1 )
|
|
||||||
BOOST_AUTO_TEST_CASE( unimp_delegate_groups_test )
|
|
||||||
{
|
|
||||||
BOOST_FAIL( "not implemented" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test should simulate a prediction market which means the following:
|
* This test should simulate a prediction market which means the following:
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue