Merge remote-tracking branch 'origin/baxter'

This commit is contained in:
Peter Conrad 2018-09-04 14:52:48 +02:00
commit 0a2af8afb2
153 changed files with 15441 additions and 2521 deletions

8
.gitmodules vendored
View file

@ -1,8 +1,8 @@
[submodule "docs"]
path = docs
url = https://github.com/cryptonomex/graphene.wiki.git
path = docs
url = https://github.com/bitshares/bitshares-core.wiki.git
ignore = dirty
[submodule "libraries/fc"]
path = libraries/fc
url = https://github.com/PBSA/peerplays-0.1-fc.git
path = libraries/fc
url = https://github.com/PBSA/peerplays-0.1-fc.git
ignore = dirty

View file

@ -32,13 +32,16 @@ if (USE_PCH)
include (cotire)
endif(USE_PCH)
list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/CMakeModules" )
IF( NOT WIN32 )
list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/CMakeModules" )
ENDIF( NOT WIN32 )
list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/GitVersionGen" )
include( GetGitRevisionDescription )
get_git_head_revision( GIT_REFSPEC GIT_SHA2 )
SET(BOOST_COMPONENTS)
LIST(APPEND BOOST_COMPONENTS thread
iostreams
date_time
system
filesystem
@ -129,6 +132,10 @@ else( WIN32 ) # Apple AND Linux
if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp" )
elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" )
if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization" )
endif()
endif()
if( "${CMAKE_GENERATOR}" STREQUAL "Ninja" )
@ -143,8 +150,6 @@ else( WIN32 ) # Apple AND Linux
endif( WIN32 )
find_package( BerkeleyDB )
set(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL "Build BitShares for code coverage analysis")
if(ENABLE_COVERAGE_TESTING)

0
CONTRIBUTORS.txt Normal file
View file

View file

@ -1,22 +1,64 @@
# This will build the witness_node in a docker image. Make sure you've already
# checked out the submodules before building.
FROM phusion/baseimage:0.9.19
MAINTAINER PeerPlays Blockchain Standards Association
FROM l3iggs/archlinux:latest
MAINTAINER Nathan Hourt <nathan@followmyvote.com>
ENV LANG=en_US.UTF-8
RUN \
apt-get update -y && \
apt-get install -y \
g++ \
autoconf \
cmake \
git \
libbz2-dev \
libreadline-dev \
libboost-all-dev \
libcurl4-openssl-dev \
libssl-dev \
libncurses-dev \
doxygen \
libcurl4-openssl-dev \
&& \
apt-get update -y && \
apt-get install -y fish && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN pacman -Syu --noconfirm gcc make autoconf automake cmake ninja boost libtool git
ADD . /peerplays-core
WORKDIR /peerplays-core
ADD . /bitshares-2
WORKDIR /bitshares-2
RUN cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .
RUN ninja witness_node || ninja -j 1 witness_node
# Compile
RUN \
git submodule update --init --recursive && \
cmake \
-DCMAKE_BUILD_TYPE=Release \
. && \
make witness_node && \
make install && \
#
# Obtain version
mkdir /etc/peerplays && \
git rev-parse --short HEAD > /etc/peerplays/version && \
cd / && \
rm -rf /peerplays-core
RUN mkdir /data_dir
ADD docker/default_config.ini /default_config.ini
ADD docker/launch /launch
RUN chmod a+x /launch
VOLUME /data_dir
# Home directory $HOME
WORKDIR /
RUN useradd -s /bin/bash -m -d /var/lib/peerplays peerplays
ENV HOME /var/lib/peerplays
RUN chown peerplays:peerplays -R /var/lib/peerplays
EXPOSE 8090 9090
# Volume
VOLUME ["/var/lib/peerplays", "/etc/peerplays"]
ENTRYPOINT ["/launch"]
# rpc service:
EXPOSE 8090
# p2p service:
EXPOSE 2001
# default exec/config files
ADD docker/default_config.ini /etc/peerplays/config.ini
ADD docker/peerplaysentry.sh /usr/local/bin/peerplaysentry.sh
RUN chmod a+x /usr/local/bin/peerplaysentry.sh
# default execute entry
CMD /usr/local/bin/peerplaysentry.sh

View file

@ -1,4 +1,5 @@
Copyright (c) 2015 Cryptonomex, Inc., and contributors.
Copyright (c) 2015-2016 Cryptonomex Inc. <contact@cryptonomex.com>
Copyright (c) 2015-2017 contributors <CONTRIBUTORS.txt>
The MIT License
@ -19,4 +20,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -210,6 +210,3 @@ Running specific tests
----------------------
- `tests/chain_tests -t block_tests/name_of_test`

161
betting2-nhl Normal file
View file

@ -0,0 +1,161 @@
### PRE...
import_key init0 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init1 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init2 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init3 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init4 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init5 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init6 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init7 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init8 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init9 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
import_key init10 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
#upgrade_account nathan 1
import_balance nathan [5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3] 1
transfer nathan init0 125 PPY "" true
transfer nathan init1 125 PPY "" true
#transfer nathan init2 125 PPY "" true
#transfer nathan init3 125 PPY "" true
#transfer nathan init4 125 PPY "" true
#transfer nathan init5 125 PPY "" true
#transfer nathan init6 125 PPY "" true
#transfer nathan init7 125 PPY "" true
#transfer nathan init8 125 PPY "" true
#transfer nathan init9 125 PPY "" true
#transfer nathan init10 125 PPY "" true
transfer nathan 1.2.0 1250 PPY "" true
transfer nathan 1.2.1 1250 PPY "" true
transfer nathan 1.2.5 1250 PPY "" true
vote_for_witness nathan init0 1 1
vote_for_witness nathan init1 1 1
whitelist_account init0 1.2.1 1 1
whitelist_account init1 1.2.1 1 1
#whitelist_account init2 1.2.1 1 1
#whitelist_account init2 1.2.1 1 1
#whitelist_account init3 1.2.1 1 1
#whitelist_account init4 1.2.1 1 1
#whitelist_account init5 1.2.1 1 1
#whitelist_account init6 1.2.1 1 1
#whitelist_account init7 1.2.1 1 1
#whitelist_account init8 1.2.1 1 1
#whitelist_account init9 1.2.1 1 1
#whitelist_account init10 1.2.1 1 1
# ALICE & BOB
register_account alice PPY4zSJHx7D84T1j6HQ7keXWdtabBBWJxvfJw72XmEyqmgdoo1njF PPY4zSJHx7D84T1j6HQ7keXWdtabBBWJxvfJw72XmEyqmgdoo1njF nathan nathan 0 true
register_account bob PPY4zSJHx7D84T1j6HQ7keXWdtabBBWJxvfJw72XmEyqmgdoo1njF PPY4zSJHx7D84T1j6HQ7keXWdtabBBWJxvfJw72XmEyqmgdoo1njF nathan nathan 0 true
transfer nathan alice 1250 PPY "" true
transfer nathan bob 1250 PPY "" true
import_key alice 5HuCDiMeESd86xrRvTbexLjkVg2BEoKrb7BAA5RLgXizkgV3shs
import_key bob 5HuCDiMeESd86xrRvTbexLjkVg2BEoKrb7BAA5RLgXizkgV3shs
### SPORT
propose_create_sport nathan "2017-05-16-T07:46:03" [["en","Ice Hockey"],["zh_Hans","冰球"],["ja","アイスホッケー"]] 1
propose_create_sport nathan "2017-05-16-T07:47:01" [["en","spce balls"], ["pl","gra w kulki"]] 1
// proposal
#get_object 1.10.0
#get_object 1.10.1
#approve_proposal nathan 1.10.0 { "active_approvals_to_add" : [ "init0", "init1","init2", "init3", "init4", "init5", "init6", "init7", "init8", "init9", "init10"] } 1
approve_proposal nathan 1.10.0 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.12 { "active_approvals_to_add" : [ "init0", "init1"] } 1
#get_object 1.16.0
#get_object 1.16.1
### EVENT GROUOP
propose_create_event_group nathan "2017-05-16-T08:45:01" [ ["en", "NHL"], ["zh_Hans", "國家冰球聯盟"], ["ja", "ナショナルホッケーリーグ"] ] "1.16.0" 1
approve_proposal nathan 1.10.2 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.17.0
### EVENT
propose_create_event nathan "2017-08-07-T11:25:01" [["en", "Washington Capitals/Chicago Blackhawks"], ["zh_Hans", "華盛頓首都隊/芝加哥黑鷹"], ["ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"]] [["en","2016-17"]] "2018-09-16-T07:47:01" "1.17.0" 1
propose_create_event nathan "2017-08-07-T11:25:01" [["en", "Washington Capitals/Chicago Blackhawks"], ["zh_Hans", "華盛頓首都隊/芝加哥黑鷹"], ["ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"]] [["en","2017-18"]] "2018-09-16-T07:47:01" "1.17.0" 1
//propose_update_event nathan "2017-08-07-T11:25:01" "1.18.0" "1.17.0" [["en", "Washington Capitals/Chicago Blackhawks"], ["zh_Hans", "華盛頓首都隊/芝加哥黑鷹"], ["ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"]] [["en","2016-17"]] "2018-09-16-T07:47:01" 1
approve_proposal nathan 1.10.3 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.18.0
# RULES
propose_create_betting_market_rules nathan "2017-08-31-T10:35:01" [["en","NHL Rules v1.0"]] [["en", "The winner will be the team with the most points at the end of the game. The team with fewer points will not be the winner."]] 1
approve_proposal nathan 1.10.8 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.19.0
### BETTING_MARKET GROUP
propose_create_betting_market_group nathan "2017-08-31-T10:39:01" [["en", "Moneyline"]] "1.18.0" "1.19.0" "1.3.0" 1
propose_create_betting_market_group nathan "2017-08-25-T11:10:01" [["en", "Moneyline NHL"]] "1.18.1" "1.19.1" "1.3.0" 1
approve_proposal nathan 1.10.9 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.20.0
### BETTING_MARKET
propose_create_betting_market nathan "2017-08-31-T10:42:01" "1.20.0" [["en","Washington Capitals win"]] [] 1
propose_create_betting_market nathan "2017-08-31-T10:42:01" "1.20.0" [["en","Chicago Blackhawks win"]] [] 1
approve_proposal nathan 1.10.10 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.11 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.21.0
//get_object 1.21.1
//list_betting_markets 1.20.0
# PLACE_BET
//place_bet bob "1.21.0" 1 { "amount" : 100 } 20000 20000 true
//place_bet alice "1.21.0" 0 { "amount" : 100 } 20000 20000 true
place_bet alice "1.21.0" 1 "123" "PPY" 200 true
//place_bet bob "1.21.0" 0 "123" "PPY" 200 true
place_bet carol "1.21.1" 0 "123" "PPY" 200 true
cancel_bet carol 1.22.2 1
get_unmatched_bets_for_bettor 1.21.1 1.2.22
# RESOLVE
propose_resolve_betting_market_group nathan "2017-08-16-T17:50:01" "1.20.0" [ [ "1.21.0" , 0] [ "1.21.1" , 1] ] 1
approve_proposal nathan 1.10.9 { "active_approvals_to_add" : [ "init0", "init1"] } 1

165
betting2-wimbledon Normal file
View file

@ -0,0 +1,165 @@
http://api.coindesk.com/charts/data?data=close&startdate=2017-01-01&enddate=2017-08-28&exchanges=bpi,bitstamp,coinbase,itbit,okcoin&dev=1&index=USD
select date, b.coin_type as bought_coin_type, bought_quantity, s.coin_type as sold_coin_type, sold_quantity from external_trades as t
where processed = false
left join coin_types as b ON t.bought_coin_type_id = b.coin_type_id
left join coin_types as s ON t.sold_coin_type_id = s.coin_type_id
### PRE...
see betting-nhl
### SPORT
propose_create_sport nathan "2017-08-16-T08:20:01" [["en","Tennis"], ["pl","Tenis"]] 1
// proposal
#get_object 1.10.10
approve_proposal nathan 1.10.10 { "active_approvals_to_add" : [ "init0", "init1"] } 1
#get_object 1.16.2
### EVENT GROUOP
propose_create_event_group nathan "2017-08-16-T08:25:01" [ ["en", "Wimbledon"] ] "1.16.2" 1
approve_proposal nathan 1.10.12 { "active_approvals_to_add" : [ "init0", "init1"] } 1
// proposal
#get_object 1.10.11
//get_object 1.17.1
propose_update_event_group nathan "2017-08-16-T09:20:01" "1.17.2" "1.16.2" [ ["en", "US Open"] ] 1
approve_proposal nathan 1.10.12 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.13 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.17.2
### EVENT
propose_create_event nathan "2017-08-16-T16:15:01" [["en", "R. Federer/T. Berdych"]] [["en","2017"]] "2017-09-16-T07:47:01" "1.17.1" 1
propose_create_event nathan "2017-08-16-T16:15:01" [["en", "M. Cilic/S. Querrye"]] [["en","2017"]] "2017-09-16-T07:47:01" "1.17.1" 1
approve_proposal nathan 1.10.14 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.15 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.18.1
//get_object 1.18.2
# RULES
propose_create_betting_market_rules nathan "2017-08-16-T16:20:01" [["en","Tennis Rules v1.0"]] [["en", "The winner is the player who wins the last ball in the match.."]] 1
approve_proposal nathan 1.10.16 { "active_approvals_to_add" : [ "init0", "init1"] } 1
0.14
//get_object 1.19.?
### BETTING_MARKET GROUP
propose_create_betting_market_group nathan "2017-08-16-T16:25:01" [["en", "Moneyline 1st sf men"]] "1.18.1" "1.19.1" "1.3.0" 1
propose_create_betting_market_group nathan "2017-08-16-T16:25:01" [["en", "Moneyline 2nd sf men"]] "1.18.2" "1.19.1" "1.3.0" 1
approve_proposal nathan 1.10.17 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.18 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.20.0
### FINAL
propose_create_event nathan "2017-08-16-T16:30:01" [["en", "R. Federer/M. Cilic"]] [["en","2017"]] "2017-09-16-T07:47:01" "1.17.1" 1
approve_proposal nathan 1.10.19 { "active_approvals_to_add" : [ "init0", "init1"] } 1
propose_create_betting_market_group nathan "2017-08-16-T16:35:01" [["en", "Moneyline final men"]] "1.18.3" "1.19.1" "1.3.0" 1
approve_proposal nathan 1.10.20 { "active_approvals_to_add" : [ "init0", "init1"] } 1
### BETTING_MARKET
propose_create_betting_market nathan "2017-08-16-T17:05:01" "1.20.1" [["en", "T. Berdych defeats R. Federer"}]] [] 1
propose_create_betting_market nathan "2017-08-16-T17:05:02" "1.20.1" [["en","R. Federer defeats T. Berdych"]] [] 1
approve_proposal nathan 1.10.21 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.22 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.21.2
//get_object 1.21.3
//list_betting_markets 1.20.1
propose_create_betting_market nathan "2017-08-16-T17:10:01" "1.20.2" [["en", "M. Cilic defeats S. Querrey"]] [] 1
propose_create_betting_market nathan "2017-08-16-T17:10:02" "1.20.2" [["en","S. Querrey defeats M. Cilic"]] [] 1
approve_proposal nathan 1.10.23 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.24 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.21.4
//get_object 1.21.5
//list_betting_markets 1.20.2
propose_create_betting_market nathan "2017-08-16-T17:15:01" "1.20.3" [["en", "R. Federer defeats M. Cilic"]] [] 1
propose_create_betting_market nathan "2017-08-16-T17:15:02" "1.20.3" [["en","M. Cilic defeats R. Federer"]] [] 1
approve_proposal nathan 1.10.25 { "active_approvals_to_add" : [ "init0", "init1"] } 1
approve_proposal nathan 1.10.26 { "active_approvals_to_add" : [ "init0", "init1"] } 1
//get_object 1.21.6
//get_object 1.21.7
//
# PLACE_BET
#place_bet bob "1.21.2" 1 { "amount" : 100 } 20000 20000 true
#place_bet alice "1.21.2" 0 { "amount" : 100 } 20000 20000 true
place_bet alice "1.21.1" 1 "123" "PPY" 200 true
place_bet bob "1.21.1" 0 "123" "PPY" 200 true
# RESOLVE
propose_resolve_betting_market_group nathan "2017-08-16-T17:50:01" "1.20.2" [ [ "1.21.4" , 0] [ "1.21.5" , 1] ] 1
approve_proposal nathan 1.10.27 { "active_approvals_to_add" : [ "init0", "init1"] } 1

1113
betting_simulator.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -47,28 +47,15 @@ required-participation = false
# track-account =
# Track market history by grouping orders into buckets of equal size measured in seconds specified as a JSON array of numbers
bucket-size = [15,60,300,3600,86400]
# bucket-size = [15,60,300,3600,86400]
bucket-size = [60,300,900,1800,3600,14400,86400]
# for 1 min, 5 mins, 30 mins, 1h, 4 hs and 1 day. i think this should be the default.
# How far back in time to track history for each bucket size, measured in the number of buckets (default: 1000)
history-per-size = 1000
# declare an appender named "stderr" that writes messages to the console
[log.console_appender.stderr]
stream=std_error
# declare an appender named "p2p" that writes messages to p2p.log
[log.file_appender.p2p]
filename=logs/p2p/p2p.log
# filename can be absolute or relative to this config file
# route any messages logged to the default logger to the "stderr" logger we
# declared above, if they are info level are higher
[logger.default]
level=info
appenders=stderr
# route messages sent to the "p2p" logger to the p2p appender declared above
[logger.p2p]
level=debug
appenders=p2p
# Max amount of operations to store in the database, per account (drastically reduces RAM requirements)
max-ops-per-account = 1000
# Remove old operation history # objects from RAM
partial-operations = true

View file

@ -1,11 +0,0 @@
#!/bin/bash
[ -e /data_dir/config.ini ] || cp /default_config.ini /data_dir/config.ini
[ -e /data_dir/pre_exec ] && bash /data_dir/pre_exec
if [ -e /data_dir/extra_args ]; then
/bitshares-2/programs/witness_node/witness_node --data-dir /data_dir `cat /data_dir/extra_args`
else
/bitshares-2/programs/witness_node/witness_node --data-dir /data_dir
fi
[ -e /data_dir/post_exec ] && bash /data_dir/post_exec

82
docker/peerplaysentry.sh Normal file
View file

@ -0,0 +1,82 @@
#!/bin/bash
PEERPLAYSD="/usr/local/bin/witness_node"
# For blockchain download
VERSION=`cat /etc/peerplays/version`
## Supported Environmental Variables
#
# * $PEERPLAYSD_SEED_NODES
# * $PEERPLAYSD_RPC_ENDPOINT
# * $PEERPLAYSD_PLUGINS
# * $PEERPLAYSD_REPLAY
# * $PEERPLAYSD_RESYNC
# * $PEERPLAYSD_P2P_ENDPOINT
# * $PEERPLAYSD_WITNESS_ID
# * $PEERPLAYSD_PRIVATE_KEY
# * $PEERPLAYSD_TRACK_ACCOUNTS
# * $PEERPLAYSD_PARTIAL_OPERATIONS
# * $PEERPLAYSD_MAX_OPS_PER_ACCOUNT
# * $PEERPLAYSD_TRUSTED_NODE
#
ARGS=""
# Translate environmental variables
if [[ ! -z "$PEERPLAYSD_SEED_NODES" ]]; then
for NODE in $PEERPLAYSD_SEED_NODES ; do
ARGS+=" --seed-node=$NODE"
done
fi
if [[ ! -z "$PEERPLAYSD_RPC_ENDPOINT" ]]; then
ARGS+=" --rpc-endpoint=${PEERPLAYSD_RPC_ENDPOINT}"
fi
if [[ ! -z "$PEERPLAYSD_REPLAY" ]]; then
ARGS+=" --replay-blockchain"
fi
if [[ ! -z "$PEERPLAYSD_RESYNC" ]]; then
ARGS+=" --resync-blockchain"
fi
if [[ ! -z "$PEERPLAYSD_P2P_ENDPOINT" ]]; then
ARGS+=" --p2p-endpoint=${PEERPLAYSD_P2P_ENDPOINT}"
fi
if [[ ! -z "$PEERPLAYSD_WITNESS_ID" ]]; then
ARGS+=" --witness-id=$PEERPLAYSD_WITNESS_ID"
fi
if [[ ! -z "$PEERPLAYSD_PRIVATE_KEY" ]]; then
ARGS+=" --private-key=$PEERPLAYSD_PRIVATE_KEY"
fi
if [[ ! -z "$PEERPLAYSD_TRACK_ACCOUNTS" ]]; then
for ACCOUNT in $PEERPLAYSD_TRACK_ACCOUNTS ; do
ARGS+=" --track-account=$ACCOUNT"
done
fi
if [[ ! -z "$PEERPLAYSD_PARTIAL_OPERATIONS" ]]; then
ARGS+=" --partial-operations=${PEERPLAYSD_PARTIAL_OPERATIONS}"
fi
if [[ ! -z "$PEERPLAYSD_MAX_OPS_PER_ACCOUNT" ]]; then
ARGS+=" --max-ops-per-account=${PEERPLAYSD_MAX_OPS_PER_ACCOUNT}"
fi
if [[ ! -z "$PEERPLAYSD_TRUSTED_NODE" ]]; then
ARGS+=" --trusted-node=${PEERPLAYSD_TRUSTED_NODE}"
fi
## Link the peerplays config file into home
## This link has been created in Dockerfile, already
ln -f -s /etc/peerplays/config.ini /var/lib/peerplays
# Plugins need to be provided in a space-separated list, which
# makes it necessary to write it like this
if [[ ! -z "$PEERPLAYSD_PLUGINS" ]]; then
$PEERPLAYSD --data-dir ${HOME} ${ARGS} ${PEERPLAYSD_ARGS} --plugins "${PEERPLAYSD_PLUGINS}"
else
$PEERPLAYSD --data-dir ${HOME} ${ARGS} ${PEERPLAYSD_ARGS}
fi

View file

@ -1 +1 @@
2.0.160208
2.0.170522

View file

@ -12,7 +12,8 @@ add_library( graphene_app
)
# need to link graphene_debug_witness because plugins aren't sufficiently isolated #246
target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness )
#target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness )
target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie )
target_include_directories( graphene_app
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" )
@ -28,3 +29,4 @@ INSTALL( TARGETS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/app" )

View file

@ -40,6 +40,7 @@
#include <fc/crypto/hex.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/future.hpp>
namespace graphene { namespace app {
@ -80,6 +81,10 @@ namespace graphene { namespace app {
{
_database_api = std::make_shared< database_api >( std::ref( *_app.chain_database() ) );
}
else if( api_name == "block_api" )
{
_block_api = std::make_shared< block_api >( std::ref( *_app.chain_database() ) );
}
else if( api_name == "network_broadcast_api" )
{
_network_broadcast_api = std::make_shared< network_broadcast_api >( std::ref( _app ) );
@ -96,15 +101,39 @@ namespace graphene { namespace app {
{
_crypto_api = std::make_shared< crypto_api >();
}
else if( api_name == "asset_api" )
{
_asset_api = std::make_shared< asset_api >( std::ref( *_app.chain_database() ) );
}
else if( api_name == "debug_api" )
{
// can only enable this API if the plugin was loaded
if( _app.get_plugin( "debug_witness" ) )
_debug_api = std::make_shared< graphene::debug_witness::debug_api >( std::ref(_app) );
}
else if( api_name == "bookie_api" )
{
// can only enable this API if the plugin was loaded
if( _app.get_plugin( "bookie" ) )
_bookie_api = std::make_shared<graphene::bookie::bookie_api>(std::ref(_app));
}
return;
}
// block_api
block_api::block_api(graphene::chain::database& db) : _db(db) { }
block_api::~block_api() { }
vector<optional<signed_block>> block_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to)const
{
FC_ASSERT( block_num_to >= block_num_from );
vector<optional<signed_block>> res;
for(uint32_t block_num=block_num_from; block_num<=block_num_to; block_num++) {
res.push_back(_db.fetch_block_by_number(block_num));
}
return res;
}
network_broadcast_api::network_broadcast_api(application& a):_app(a)
{
_applied_block_connection = _app.chain_database()->applied_block.connect([this](const signed_block& b){ on_applied_block(b); });
@ -138,6 +167,16 @@ namespace graphene { namespace app {
_app.p2p_node()->broadcast_transaction(trx);
}
fc::variant network_broadcast_api::broadcast_transaction_synchronous(const signed_transaction& trx)
{
fc::promise<fc::variant>::ptr prom( new fc::promise<fc::variant>() );
broadcast_transaction_with_callback( [=]( const fc::variant& v ){
prom->set_value(v);
}, trx );
return fc::future<fc::variant>(prom).wait();
}
void network_broadcast_api::broadcast_block( const signed_block& b )
{
_app.chain_database()->push_block(b);
@ -194,6 +233,12 @@ namespace graphene { namespace app {
return *_network_broadcast_api;
}
fc::api<block_api> login_api::block()const
{
FC_ASSERT(_block_api);
return *_block_api;
}
fc::api<network_node_api> login_api::network_node()const
{
FC_ASSERT(_network_node_api);
@ -218,12 +263,24 @@ namespace graphene { namespace app {
return *_crypto_api;
}
fc::api<asset_api> login_api::asset() const
{
FC_ASSERT(_asset_api);
return *_asset_api;
}
fc::api<graphene::debug_witness::debug_api> login_api::debug() const
{
FC_ASSERT(_debug_api);
return *_debug_api;
}
fc::api<graphene::bookie::bookie_api> login_api::bookie() const
{
FC_ASSERT(_bookie_api);
return *_bookie_api;
}
#if 0
vector<account_id_type> get_relevant_accounts( const object* obj )
{
@ -306,6 +363,18 @@ namespace graphene { namespace app {
} case balance_object_type:{
/** these are free from any accounts */
break;
}
case sport_object_type:
case event_group_object_type:
case event_object_type:
case betting_market_group_object_type:
case betting_market_object_type:
/** these are free from any accounts */
break;
case bet_object_type:{
const auto& aobj = dynamic_cast<const bet_object*>(obj);
assert( aobj != nullptr );
result.push_back( aobj->bettor_id );
} case tournament_object_type:{
const tournament_object* tournament_obj = dynamic_cast<const tournament_object*>(obj);
assert(tournament_obj);
@ -372,6 +441,10 @@ namespace graphene { namespace app {
break;
case impl_fba_accumulator_object_type:
break;
case impl_betting_market_position_object_type:
break;
case impl_global_betting_statistics_object_type:
break;
}
}
return result;
@ -403,13 +476,13 @@ namespace graphene { namespace app {
return result;
}
vector<operation_history_object> history_api::get_account_history( account_id_type account,
operation_history_id_type stop,
unsigned limit,
vector<operation_history_object> history_api::get_account_history( account_id_type account,
operation_history_id_type stop,
unsigned limit,
operation_history_id_type start ) const
{
FC_ASSERT( _app.chain_database() );
const auto& db = *_app.chain_database();
const auto& db = *_app.chain_database();
FC_ASSERT( limit <= 100 );
vector<operation_history_object> result;
const auto& stats = account(db).statistics(db);
@ -417,7 +490,7 @@ namespace graphene { namespace app {
const account_transaction_history_object* node = &stats.most_recent_op(db);
if( start == operation_history_id_type() )
start = node->operation_id;
while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit)
{
if( node->operation_id.instance.value <= start.instance.value )
@ -426,38 +499,82 @@ namespace graphene { namespace app {
node = nullptr;
else node = &node->next(db);
}
return result;
}
vector<operation_history_object> history_api::get_relative_account_history( account_id_type account,
uint32_t stop,
unsigned limit,
vector<operation_history_object> history_api::get_account_history_operations( account_id_type account,
int operation_id,
operation_history_id_type start,
operation_history_id_type stop,
unsigned limit) const
{
FC_ASSERT( _app.chain_database() );
const auto& db = *_app.chain_database();
FC_ASSERT( limit <= 100 );
vector<operation_history_object> result;
const auto& stats = account(db).statistics(db);
if( stats.most_recent_op == account_transaction_history_id_type() ) return result;
const account_transaction_history_object* node = &stats.most_recent_op(db);
if( start == operation_history_id_type() )
start = node->operation_id;
while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit)
{
if( node->operation_id.instance.value <= start.instance.value ) {
if(node->operation_id(db).op.which() == operation_id)
result.push_back( node->operation_id(db) );
}
if( node->next == account_transaction_history_id_type() )
node = nullptr;
else node = &node->next(db);
}
return result;
}
vector<operation_history_object> history_api::get_relative_account_history( account_id_type account,
uint32_t stop,
unsigned limit,
uint32_t start) const
{
FC_ASSERT( _app.chain_database() );
const auto& db = *_app.chain_database();
FC_ASSERT(limit <= 100);
vector<operation_history_object> result;
const auto& stats = account(db).statistics(db);
if( start == 0 )
start = account(db).statistics(db).total_ops;
else start = min( account(db).statistics(db).total_ops, start );
const auto& hist_idx = db.get_index_type<account_transaction_history_index>();
const auto& by_seq_idx = hist_idx.indices().get<by_seq>();
auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) );
auto itr_stop = by_seq_idx.lower_bound( boost::make_tuple( account, stop ) );
--itr;
while ( itr != itr_stop && result.size() < limit )
start = stats.total_ops;
else
start = min( stats.total_ops, start );
if( start >= stop && start > stats.removed_ops && limit > 0 )
{
result.push_back( itr->operation_id(db) );
--itr;
const auto& hist_idx = db.get_index_type<account_transaction_history_index>();
const auto& by_seq_idx = hist_idx.indices().get<by_seq>();
auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) );
auto itr_stop = by_seq_idx.lower_bound( boost::make_tuple( account, stop ) );
do
{
--itr;
result.push_back( itr->operation_id(db) );
}
while ( itr != itr_stop && result.size() < limit );
}
return result;
}
vector<account_balance_object> history_api::list_core_accounts()const
{
auto list = _app.get_plugin<accounts_list_plugin>( "accounts_list" );
FC_ASSERT( list );
return list->list_accounts();
}
flat_set<uint32_t> history_api::get_market_history_buckets()const
{
auto hist = _app.get_plugin<market_history_plugin>( "market_history" );
@ -490,14 +607,14 @@ namespace graphene { namespace app {
}
return result;
} FC_CAPTURE_AND_RETHROW( (a)(b)(bucket_seconds)(start)(end) ) }
crypto_api::crypto_api(){};
blind_signature crypto_api::blind_sign( const extended_private_key_type& key, const blinded_hash& hash, int i )
{
return fc::ecc::extended_private_key( key ).blind_sign( hash, i );
}
signature_type crypto_api::unblind_signature( const extended_private_key_type& key,
const extended_public_key_type& bob,
const blind_signature& sig,
@ -506,32 +623,32 @@ namespace graphene { namespace app {
{
return fc::ecc::extended_private_key( key ).unblind_signature( extended_public_key( bob ), sig, hash, i );
}
commitment_type crypto_api::blind( const blind_factor_type& blind, uint64_t value )
{
return fc::ecc::blind( blind, value );
}
blind_factor_type crypto_api::blind_sum( const std::vector<blind_factor_type>& blinds_in, uint32_t non_neg )
{
return fc::ecc::blind_sum( blinds_in, non_neg );
}
bool crypto_api::verify_sum( const std::vector<commitment_type>& commits_in, const std::vector<commitment_type>& neg_commits_in, int64_t excess )
{
return fc::ecc::verify_sum( commits_in, neg_commits_in, excess );
}
verify_range_result crypto_api::verify_range( const commitment_type& commit, const std::vector<char>& proof )
{
verify_range_result result;
result.success = fc::ecc::verify_range( result.min_val, result.max_val, commit, proof );
return result;
}
std::vector<char> crypto_api::range_proof_sign( uint64_t min_value,
const commitment_type& commit,
const blind_factor_type& commit_blind,
std::vector<char> crypto_api::range_proof_sign( uint64_t min_value,
const commitment_type& commit,
const blind_factor_type& commit_blind,
const blind_factor_type& nonce,
int8_t base10_exp,
uint8_t min_bits,
@ -539,26 +656,100 @@ namespace graphene { namespace app {
{
return fc::ecc::range_proof_sign( min_value, commit, commit_blind, nonce, base10_exp, min_bits, actual_value );
}
verify_range_proof_rewind_result crypto_api::verify_range_proof_rewind( const blind_factor_type& nonce,
const commitment_type& commit,
const commitment_type& commit,
const std::vector<char>& proof )
{
verify_range_proof_rewind_result result;
result.success = fc::ecc::verify_range_proof_rewind( result.blind_out,
result.value_out,
result.message_out,
nonce,
result.min_val,
result.max_val,
const_cast< commitment_type& >( commit ),
result.success = fc::ecc::verify_range_proof_rewind( result.blind_out,
result.value_out,
result.message_out,
nonce,
result.min_val,
result.max_val,
const_cast< commitment_type& >( commit ),
proof );
return result;
}
range_proof_info crypto_api::range_get_info( const std::vector<char>& proof )
{
return fc::ecc::range_get_info( proof );
}
// asset_api
asset_api::asset_api(graphene::chain::database& db) : _db(db) { }
asset_api::~asset_api() { }
vector<account_asset_balance> asset_api::get_asset_holders( asset_id_type asset_id, uint32_t start, uint32_t limit ) const {
FC_ASSERT(limit <= 100);
const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();
auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );
vector<account_asset_balance> result;
uint32_t index = 0;
for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) )
{
if( result.size() >= limit )
break;
if( bal.balance.value == 0 )
continue;
if( index++ < start )
continue;
const auto account = _db.find(bal.owner);
account_asset_balance aab;
aab.name = account->name;
aab.account_id = account->id;
aab.amount = bal.balance.value;
result.push_back(aab);
}
return result;
}
// get number of asset holders.
int asset_api::get_asset_holders_count( asset_id_type asset_id ) const {
const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();
auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );
int count = boost::distance(range) - 1;
return count;
}
// function to get vector of system assets with holders count.
vector<asset_holders> asset_api::get_all_asset_holders() const {
vector<asset_holders> result;
vector<asset_id_type> total_assets;
for( const asset_object& asset_obj : _db.get_index_type<asset_index>().indices() )
{
const auto& dasset_obj = asset_obj.dynamic_asset_data_id(_db);
asset_id_type asset_id;
asset_id = dasset_obj.id;
const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();
auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );
int count = boost::distance(range) - 1;
asset_holders ah;
ah.asset_id = asset_id;
ah.count = count;
result.push_back(ah);
}
return result;
}
} } // graphene::app

View file

@ -28,15 +28,12 @@
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/time/time.hpp>
#include <graphene/egenesis/egenesis.hpp>
#include <graphene/net/core_messages.hpp>
#include <graphene/net/exceptions.hpp>
#include <graphene/time/time.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <graphene/chain/worker_evaluator.hpp>
@ -46,10 +43,12 @@
#include <fc/rpc/api_connection.hpp>
#include <fc/rpc/websocket_api.hpp>
#include <fc/network/resolve.hpp>
#include <fc/crypto/base64.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/signals2.hpp>
#include <boost/range/algorithm/reverse.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
@ -198,6 +197,7 @@ namespace detail {
"54.37.235.164:7777", // melea-trust
"peerplays-seed.lukestokes.info:7777" // lukestokes-witness
};
for( const string& endpoint_string : seeds )
{
try {
@ -254,23 +254,49 @@ namespace detail {
FC_CAPTURE_AND_RETHROW((endpoint_string))
}
void new_connection( const fc::http::websocket_connection_ptr& c )
{
auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
auto login = std::make_shared<graphene::app::login_api>( std::ref(*_self) );
login->enable_api("database_api");
wsc->register_api(login->database());
wsc->register_api(fc::api<graphene::app::login_api>(login));
wsc->register_api(fc::api<graphene::app::login_api>(login));
c->set_session_data( wsc );
std::string username = "*";
std::string password = "*";
// Try to extract login information from "Authorization" header if present
std::string auth = c->get_request_header("Authorization");
if( boost::starts_with(auth, "Basic ") ) {
FC_ASSERT( auth.size() > 6 );
auto user_pass = fc::base64_decode(auth.substr(6));
std::vector<std::string> parts;
boost::split( parts, user_pass, boost::is_any_of(":") );
FC_ASSERT(parts.size() == 2);
username = parts[0];
password = parts[1];
}
login->login(username, password);
}
void reset_websocket_server()
{ try {
if( !_options->count("rpc-endpoint") )
return;
bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0;
_websocket_server = std::make_shared<fc::http::websocket_server>();
_websocket_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) );
_websocket_server = std::make_shared<fc::http::websocket_server>(enable_deflate_compression);
_websocket_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){
auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
auto login = std::make_shared<graphene::app::login_api>( std::ref(*_self) );
auto db_api = std::make_shared<graphene::app::database_api>( std::ref(*_self->chain_database()) );
wsc->register_api(fc::api<graphene::app::database_api>(db_api));
wsc->register_api(fc::api<graphene::app::login_api>(login));
c->set_session_data( wsc );
});
ilog("Configured websocket rpc to listen on ${ip}", ("ip",_options->at("rpc-endpoint").as<string>()));
_websocket_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-endpoint").as<string>()) );
_websocket_server->start_accept();
@ -288,17 +314,9 @@ namespace detail {
}
string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as<string>() : "";
bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0;
_websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>( _options->at("server-pem").as<string>(), password, enable_deflate_compression );
_websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>( _options->at("server-pem").as<string>(), password );
_websocket_tls_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) );
_websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){
auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
auto login = std::make_shared<graphene::app::login_api>( std::ref(*_self) );
auto db_api = std::make_shared<graphene::app::database_api>( std::ref(*_self->chain_database()) );
wsc->register_api(fc::api<graphene::app::database_api>(db_api));
wsc->register_api(fc::api<graphene::app::login_api>(login));
c->set_session_data( wsc );
});
ilog("Configured websocket TLS rpc to listen on ${ip}", ("ip",_options->at("rpc-tls-endpoint").as<string>()));
_websocket_tls_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-tls-endpoint").as<string>()) );
_websocket_tls_server->start_accept();
@ -339,7 +357,7 @@ namespace detail {
bool modified_genesis = false;
if( _options->count("genesis-timestamp") )
{
genesis.initial_timestamp = fc::time_point_sec( graphene::time::now() ) + genesis.initial_parameters.block_interval + _options->at("genesis-timestamp").as<uint32_t>();
genesis.initial_timestamp = fc::time_point_sec( fc::time_point::now() ) + genesis.initial_parameters.block_interval + _options->at("genesis-timestamp").as<uint32_t>();
genesis.initial_timestamp -= genesis.initial_timestamp.sec_since_epoch() % genesis.initial_parameters.block_interval;
modified_genesis = true;
std::cerr << "Used genesis timestamp: " << genesis.initial_timestamp.to_iso_string() << " (PLEASE RECORD THIS)\n";
@ -390,79 +408,67 @@ namespace detail {
}
_chain_db->add_checkpoints( loaded_checkpoints );
if( _options->count("replay-blockchain") )
bool replay = false;
std::string replay_reason = "reason not provided";
// never replay if data dir is empty
if( fc::exists( _data_dir ) && fc::directory_iterator( _data_dir ) != fc::directory_iterator() )
{
ilog("Replaying blockchain on user request.");
_chain_db->reindex(_data_dir/"blockchain", initial_state());
} else if( clean ) {
auto is_new = [&]() -> bool
if( _options->count("replay-blockchain") )
{
// directory doesn't exist
if( !fc::exists( _data_dir ) )
return true;
// if directory exists but is empty, return true; else false.
return ( fc::directory_iterator( _data_dir ) == fc::directory_iterator() );
};
auto is_outdated = [&]() -> bool
replay = true;
replay_reason = "replay-blockchain argument specified";
}
else if( !clean )
{
if( !fc::exists( _data_dir / "db_version" ) )
return true;
std::string version_str;
fc::read_file_contents( _data_dir / "db_version", version_str );
return (version_str != GRAPHENE_CURRENT_DB_VERSION);
};
bool need_reindex = (!is_new() && is_outdated());
std::string reindex_reason = "version upgrade";
if( !need_reindex )
replay = true;
replay_reason = "unclean shutdown detected";
}
else if( !fc::exists( _data_dir / "db_version" ) )
{
try
replay = true;
replay_reason = "db_version file not found";
}
else
{
std::string version_string;
fc::read_file_contents( _data_dir / "db_version", version_string );
if( version_string != GRAPHENE_CURRENT_DB_VERSION )
{
_chain_db->open(_data_dir / "blockchain", initial_state);
}
catch( const fc::exception& e )
{
ilog( "caught exception ${e} in open()", ("e", e.to_detail_string()) );
need_reindex = true;
reindex_reason = "exception in open()";
replay = true;
replay_reason = "db_version file content mismatch";
}
}
if( need_reindex )
{
ilog("Replaying blockchain due to ${reason}", ("reason", reindex_reason) );
fc::remove_all( _data_dir / "db_version" );
_chain_db->reindex(_data_dir / "blockchain", initial_state());
// doing this down here helps ensure that DB will be wiped
// if any of the above steps were interrupted on a previous run
if( !fc::exists( _data_dir / "db_version" ) )
{
std::ofstream db_version(
(_data_dir / "db_version").generic_string().c_str(),
std::ios::out | std::ios::binary | std::ios::trunc );
std::string version_string = GRAPHENE_CURRENT_DB_VERSION;
db_version.write( version_string.c_str(), version_string.size() );
db_version.close();
}
}
} else {
wlog("Detected unclean shutdown. Replaying blockchain...");
_chain_db->reindex(_data_dir / "blockchain", initial_state());
}
if (!_options->count("genesis-json") &&
_chain_db->get_chain_id() != graphene::egenesis::get_egenesis_chain_id()) {
elog("Detected old database. Nuking and starting over.");
_chain_db->wipe(_data_dir / "blockchain", true);
_chain_db.reset();
_chain_db = std::make_shared<chain::database>();
_chain_db->add_checkpoints(loaded_checkpoints);
_chain_db->open(_data_dir / "blockchain", initial_state);
if( !replay )
{
try
{
_chain_db->open( _data_dir / "blockchain", initial_state );
}
catch( const fc::exception& e )
{
ilog( "Caught exception ${e} in open()", ("e", e.to_detail_string()) );
replay = true;
replay_reason = "exception in open()";
}
}
if( replay )
{
ilog( "Replaying blockchain due to: ${reason}", ("reason", replay_reason) );
fc::remove_all( _data_dir / "db_version" );
_chain_db->reindex( _data_dir / "blockchain", initial_state() );
const auto mode = std::ios::out | std::ios::binary | std::ios::trunc;
std::ofstream db_version( (_data_dir / "db_version").generic_string().c_str(), mode );
std::string version_string = GRAPHENE_CURRENT_DB_VERSION;
db_version.write( version_string.c_str(), version_string.size() );
db_version.close();
}
if( _options->count("force-validate") )
@ -471,8 +477,6 @@ namespace detail {
_force_validate = true;
}
graphene::time::now();
if( _options->count("api-access") )
_apiaccess = fc::json::from_file( _options->at("api-access").as<boost::filesystem::path>() )
.as<api_access>();
@ -488,6 +492,7 @@ namespace detail {
wild_access.allowed_apis.push_back( "network_broadcast_api" );
wild_access.allowed_apis.push_back( "history_api" );
wild_access.allowed_apis.push_back( "crypto_api" );
wild_access.allowed_apis.push_back( "bookie_api" );
_apiaccess.permission_map["*"] = wild_access;
}
@ -496,6 +501,7 @@ namespace detail {
reset_websocket_tls_server();
} FC_LOG_AND_RETHROW() }
optional< api_access_info > get_api_access_info(const string& username)const
{
optional< api_access_info > result;
@ -540,21 +546,21 @@ namespace detail {
virtual bool handle_block(const graphene::net::block_message& blk_msg, bool sync_mode,
std::vector<fc::uint160_t>& contained_transaction_message_ids) override
{ try {
auto latency = graphene::time::now() - blk_msg.block.timestamp;
auto latency = fc::time_point::now() - blk_msg.block.timestamp;
FC_ASSERT( (latency.count()/1000) > -5000, "Rejecting block with timestamp in the future" );
if (!sync_mode || blk_msg.block.block_num() % 10000 == 0)
{
const auto& witness = blk_msg.block.witness(*_chain_db);
const auto& witness_account = witness.witness_account(*_chain_db);
auto last_irr = _chain_db->get_dynamic_global_properties().last_irreversible_block_num;
ilog("Got block: #${n} time: ${t} latency: ${l} ms from: ${w} irreversible: ${i} (-${d})",
ilog("Got block: #${n} time: ${t} latency: ${l} ms from: ${w} irreversible: ${i} (-${d})",
("t",blk_msg.block.timestamp)
("n", blk_msg.block.block_num())
("l", (latency.count()/1000))
("w",witness_account.name)
("i",last_irr)("d",blk_msg.block.block_num()-last_irr) );
}
FC_ASSERT( (latency.count()/1000) > -5000, "Rejecting block with timestamp in the future" );
try {
// TODO: in the case where this block is valid but on a fork that's too old for us to switch to,
@ -643,7 +649,7 @@ namespace detail {
result.reserve(limit);
block_id_type last_known_block_id;
if (blockchain_synopsis.empty() ||
(blockchain_synopsis.size() == 1 && blockchain_synopsis[0] == block_id_type()))
{
@ -704,13 +710,13 @@ namespace detail {
}
/**
* Returns a synopsis of the blockchain used for syncing. This consists of a list of
* Returns a synopsis of the blockchain used for syncing. This consists of a list of
* block hashes at intervals exponentially increasing towards the genesis block.
* When syncing to a peer, the peer uses this data to determine if we're on the same
* fork as they are, and if not, what blocks they need to send us to get us on their
* fork.
*
* In the over-simplified case, this is a straighforward synopsis of our current
* In the over-simplified case, this is a straighforward synopsis of our current
* preferred blockchain; when we first connect up to a peer, this is what we will be sending.
* It looks like this:
* If the blockchain is empty, it will return the empty list.
@ -726,7 +732,7 @@ namespace detail {
* the last item in the list will be the hash of the most recent block on our preferred chain
* so if the blockchain had 26 blocks labeled a - z, the synopsis would be:
* a n u x z
* the idea being that by sending a small (<30) number of block ids, we can summarize a huge
* the idea being that by sending a small (<30) number of block ids, we can summarize a huge
* blockchain. The block ids are more dense near the end of the chain where because we are
* more likely to be almost in sync when we first connect, and forks are likely to be short.
* If the peer we're syncing with in our example is on a fork that started at block 'v',
@ -741,27 +747,27 @@ namespace detail {
* no reason to fetch the blocks.
*
* Second, when a peer replies to our initial synopsis and gives us a list of the blocks they think
* we are missing, they only send a chunk of a few thousand blocks at once. After we get those
* we are missing, they only send a chunk of a few thousand blocks at once. After we get those
* block ids, we need to request more blocks by sending another synopsis (we can't just say "send me
* the next 2000 ids" because they may have switched forks themselves and they don't track what
* they've sent us). For faster performance, we want to get a fairly long list of block ids first,
* then start downloading the blocks.
* The peer doesn't handle these follow-up block id requests any different from the initial request;
* it treats the synopsis we send as our blockchain and bases its response entirely off that. So to
* get the response we want (the next chunk of block ids following the last one they sent us, or,
* get the response we want (the next chunk of block ids following the last one they sent us, or,
* failing that, the shortest fork off of the last list of block ids they sent), we need to construct
* a synopsis as if our blockchain was made up of:
* 1. the blocks in our block chain up to the fork point (if there is a fork) or the head block (if no fork)
* 2. the blocks we've already pushed from their fork (if there's a fork)
* 3. the block ids they've previously sent us
* Segment 3 is handled in the p2p code, it just tells us the number of blocks it has (in
* Segment 3 is handled in the p2p code, it just tells us the number of blocks it has (in
* number_of_blocks_after_reference_point) so we can leave space in the synopsis for them.
* We're responsible for constructing the synopsis of Segments 1 and 2 from our active blockchain and
* fork database. The reference_point parameter is the last block from that peer that has been
* successfully pushed to the blockchain, so that tells us whether the peer is on a fork or on
* the main chain.
*/
virtual std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point,
virtual std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point,
uint32_t number_of_blocks_after_reference_point) override
{ try {
std::vector<item_hash_t> synopsis;
@ -786,13 +792,13 @@ namespace detail {
if (reference_point_block_num < low_block_num)
{
// we're on the same fork (at least as far as reference_point) but we've passed
// reference point and could no longer undo that far if we diverged after that
// we're on the same fork (at least as far as reference_point) but we've passed
// reference point and could no longer undo that far if we diverged after that
// block. This should probably only happen due to a race condition where
// the network thread calls this function, and then immediately pushes a bunch of blocks,
// the network thread calls this function, and then immediately pushes a bunch of blocks,
// then the main thread finally processes this function.
// with the current framework, there's not much we can do to tell the network
// thread what our current head block is, so we'll just pretend that
// thread what our current head block is, so we'll just pretend that
// our head is actually the reference point.
// this *may* enable us to fetch blocks that we're unable to push, but that should
// be a rare case (and correctly handled)
@ -836,7 +842,7 @@ namespace detail {
if (non_fork_high_block_num < low_block_num)
{
wlog("Unable to generate a usable synopsis because the peer we're generating it for forked too long ago "
"(our chains diverge after block #${non_fork_high_block_num} but only undoable to block #${low_block_num})",
"(our chains diverge after block #${non_fork_high_block_num} but only undoable to block #${low_block_num})",
("low_block_num", low_block_num)
("non_fork_high_block_num", non_fork_high_block_num));
FC_THROW_EXCEPTION(graphene::net::block_older_than_undo_history, "Peer is are on a fork I'm unable to switch to");
@ -856,11 +862,11 @@ namespace detail {
low_block_num = 1;
// at this point:
// low_block_num is the block before the first block we can undo,
// low_block_num is the block before the first block we can undo,
// non_fork_high_block_num is the block before the fork (if the peer is on a fork, or otherwise it is the same as high_block_num)
// high_block_num is the block number of the reference block, or the end of the chain if no reference provided
// true_high_block_num is the ending block number after the network code appends any item ids it
// true_high_block_num is the ending block number after the network code appends any item ids it
// knows about that we don't
uint32_t true_high_block_num = high_block_num + number_of_blocks_after_reference_point;
do
@ -916,12 +922,6 @@ namespace detail {
return fc::time_point_sec::min();
} FC_CAPTURE_AND_RETHROW( (block_id) ) }
/** returns graphene::time::now() */
virtual fc::time_point_sec get_blockchain_now() override
{
return graphene::time::now();
}
virtual item_hash_t get_head_block_id() const override
{
return _chain_db->head_block_id();
@ -987,8 +987,6 @@ void application::set_program_options(boost::program_options::options_descriptio
("checkpoint,c", bpo::value<vector<string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.")
("rpc-endpoint", bpo::value<string>()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on")
("rpc-tls-endpoint", bpo::value<string>()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on")
("enable-permessage-deflate", "Enable support for per-message deflate compression in the websocket servers "
"(--rpc-endpoint and --rpc-tls-endpoint), disabled by default")
("server-pem,p", bpo::value<string>()->implicit_value("server.pem"), "The TLS certificate file for this server")
("server-pem-password,P", bpo::value<string>()->implicit_value(""), "Password for this certificate")
("genesis-json", bpo::value<boost::filesystem::path>(), "File to read Genesis State from")

View file

@ -43,6 +43,8 @@
#define GET_REQUIRED_FEES_MAX_RECURSION 4
typedef std::map< std::pair<graphene::chain::asset_id_type, graphene::chain::asset_id_type>, std::vector<fc::variant> > market_queue_type;
namespace graphene { namespace app {
class database_api_impl;
@ -58,13 +60,14 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
fc::variants get_objects(const vector<object_id_type>& ids)const;
// Subscriptions
void set_subscribe_callback( std::function<void(const variant&)> cb, bool clear_filter );
void set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create );
void set_pending_transaction_callback( std::function<void(const variant&)> cb );
void set_block_applied_callback( std::function<void(const variant& block_id)> cb );
void cancel_all_subscriptions();
// Blocks and transactions
optional<block_header> get_block_header(uint32_t block_num)const;
map<uint32_t, optional<block_header>> get_block_header_batch(const vector<uint32_t> block_nums)const;
optional<signed_block> get_block(uint32_t block_num)const;
processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const;
@ -74,9 +77,11 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
fc::variant_object get_config()const;
chain_id_type get_chain_id()const;
dynamic_global_property_object get_dynamic_global_properties()const;
global_betting_statistics_object get_global_betting_statistics() const;
// Keys
vector<vector<account_id_type>> get_key_references( vector<public_key_type> key )const;
bool is_public_key_registered(string public_key) const;
// Accounts
vector<optional<account_object>> get_accounts(const vector<account_id_type>& account_ids)const;
@ -99,6 +104,15 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
vector<asset_object> list_assets(const string& lower_bound_symbol, uint32_t limit)const;
vector<optional<asset_object>> lookup_asset_symbols(const vector<string>& symbols_or_ids)const;
// Peerplays
vector<sport_object> list_sports() const;
vector<event_group_object> list_event_groups(sport_id_type sport_id) const;
vector<event_object> list_events_in_group(event_group_id_type event_group_id) const;
vector<betting_market_group_object> list_betting_market_groups(event_id_type) const;
vector<betting_market_object> list_betting_markets(betting_market_group_id_type) const;
vector<bet_object> get_unmatched_bets_for_bettor(betting_market_id_type, account_id_type) const;
vector<bet_object> get_all_unmatched_bets_for_bettor(account_id_type) const;
// Markets / feeds
vector<limit_order_object> get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const;
vector<call_order_object> get_call_orders(asset_id_type a, uint32_t limit)const;
@ -168,22 +182,52 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
{
if( !_subscribe_callback )
return false;
return true;
return _subscribe_filter.contains( i );
}
bool is_impacted_account( const flat_set<account_id_type>& accounts)
{
if( !_subscribed_accounts.size() || !accounts.size() )
return false;
return std::any_of(accounts.begin(), accounts.end(), [this](const account_id_type& account) {
return _subscribed_accounts.find(account) != _subscribed_accounts.end();
});
}
template<typename T>
void enqueue_if_subscribed_to_market(const object* obj, market_queue_type& queue, bool full_object=true)
{
const T* order = dynamic_cast<const T*>(obj);
FC_ASSERT( order != nullptr);
auto market = order->get_market();
auto sub = _market_subscriptions.find( market );
if( sub != _market_subscriptions.end() ) {
queue[market].emplace_back( full_object ? obj->to_variant() : fc::variant(obj->id) );
}
}
void broadcast_updates( const vector<variant>& updates );
void broadcast_market_updates( const market_queue_type& queue);
void handle_object_changed(bool force_notify, bool full_object, const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts, std::function<const object*(object_id_type id)> find_object);
/** called every time a block is applied to report the objects that were changed */
void on_objects_changed(const vector<object_id_type>& ids);
void on_objects_removed(const vector<const object*>& objs);
void on_objects_new(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts);
void on_objects_changed(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts);
void on_objects_removed(const vector<object_id_type>& ids, const vector<const object*>& objs, const flat_set<account_id_type>& impacted_accounts);
void on_applied_block();
mutable fc::bloom_filter _subscribe_filter;
bool _notify_remove_create = false;
mutable fc::bloom_filter _subscribe_filter;
std::set<account_id_type> _subscribed_accounts;
std::function<void(const fc::variant&)> _subscribe_callback;
std::function<void(const fc::variant&)> _pending_trx_callback;
std::function<void(const fc::variant&)> _block_applied_callback;
boost::signals2::scoped_connection _new_connection;
boost::signals2::scoped_connection _change_connection;
boost::signals2::scoped_connection _removed_connection;
boost::signals2::scoped_connection _applied_block_connection;
@ -206,11 +250,14 @@ database_api::~database_api() {}
database_api_impl::database_api_impl( graphene::chain::database& db ):_db(db)
{
wlog("creating database api ${x}", ("x",int64_t(this)) );
_change_connection = _db.changed_objects.connect([this](const vector<object_id_type>& ids) {
on_objects_changed(ids);
_new_connection = _db.new_objects.connect([this](const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts) {
on_objects_new(ids, impacted_accounts);
});
_removed_connection = _db.removed_objects.connect([this](const vector<const object*>& objs) {
on_objects_removed(objs);
_change_connection = _db.changed_objects.connect([this](const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts) {
on_objects_changed(ids, impacted_accounts);
});
_removed_connection = _db.removed_objects.connect([this](const vector<object_id_type>& ids, const vector<const object*>& objs, const flat_set<account_id_type>& impacted_accounts) {
on_objects_removed(ids, objs, impacted_accounts);
});
_applied_block_connection = _db.applied_block.connect([this](const signed_block&){ on_applied_block(); });
@ -246,10 +293,6 @@ fc::variants database_api_impl::get_objects(const vector<object_id_type>& ids)co
this->subscribe_to_item( id );
}
}
else
{
elog( "getObjects without subscribe callback??" );
}
fc::variants result;
result.reserve(ids.size());
@ -270,24 +313,24 @@ fc::variants database_api_impl::get_objects(const vector<object_id_type>& ids)co
// //
//////////////////////////////////////////////////////////////////////
void database_api::set_subscribe_callback( std::function<void(const variant&)> cb, bool clear_filter )
void database_api::set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create )
{
my->set_subscribe_callback( cb, clear_filter );
my->set_subscribe_callback( cb, notify_remove_create );
}
void database_api_impl::set_subscribe_callback( std::function<void(const variant&)> cb, bool clear_filter )
void database_api_impl::set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create )
{
edump((clear_filter));
//edump((clear_filter));
_subscribe_callback = cb;
if( clear_filter || !cb )
{
static fc::bloom_parameters param;
param.projected_element_count = 10000;
param.false_positive_probability = 1.0/10000;
param.maximum_size = 1024*8*8*2;
param.compute_optimal_parameters();
_subscribe_filter = fc::bloom_filter(param);
}
_notify_remove_create = notify_remove_create;
_subscribed_accounts.clear();
static fc::bloom_parameters param;
param.projected_element_count = 10000;
param.false_positive_probability = 1.0/100;
param.maximum_size = 1024*8*8*2;
param.compute_optimal_parameters();
_subscribe_filter = fc::bloom_filter(param);
}
void database_api::set_pending_transaction_callback( std::function<void(const variant&)> cb )
@ -339,6 +382,20 @@ optional<block_header> database_api_impl::get_block_header(uint32_t block_num) c
return *result;
return {};
}
map<uint32_t, optional<block_header>> database_api::get_block_header_batch(const vector<uint32_t> block_nums)const
{
return my->get_block_header_batch( block_nums );
}
map<uint32_t, optional<block_header>> database_api_impl::get_block_header_batch(const vector<uint32_t> block_nums) const
{
map<uint32_t, optional<block_header>> results;
for (const uint32_t block_num : block_nums)
{
results[block_num] = get_block_header(block_num);
}
return results;
}
optional<signed_block> database_api::get_block(uint32_t block_num)const
{
@ -428,6 +485,17 @@ dynamic_global_property_object database_api_impl::get_dynamic_global_properties(
return _db.get(dynamic_global_property_id_type());
}
global_betting_statistics_object database_api::get_global_betting_statistics() const
{
return my->get_global_betting_statistics();
}
global_betting_statistics_object database_api_impl::get_global_betting_statistics() const
{
return _db.get(global_betting_statistics_id_type());
}
//////////////////////////////////////////////////////////////////////
// //
// Keys //
@ -498,6 +566,35 @@ vector<vector<account_id_type>> database_api_impl::get_key_references( vector<pu
return final_result;
}
bool database_api::is_public_key_registered(string public_key) const
{
return my->is_public_key_registered(public_key);
}
bool database_api_impl::is_public_key_registered(string public_key) const
{
// Short-circuit
if (public_key.empty()) {
return false;
}
// Search among all keys using an existing map of *current* account keys
public_key_type key;
try {
key = public_key_type(public_key);
} catch ( ... ) {
// An invalid public key was detected
return false;
}
const auto& idx = _db.get_index_type<account_index>();
const auto& aidx = dynamic_cast<const primary_index<account_index>&>(idx);
const auto& refs = aidx.get_secondary_index<graphene::chain::account_member_index>();
auto itr = refs.account_to_key_memberships.find(key);
bool is_known = itr != refs.account_to_key_memberships.end();
return is_known;
}
//////////////////////////////////////////////////////////////////////
// //
// Accounts //
@ -551,7 +648,8 @@ std::map<std::string, full_account> database_api_impl::get_full_accounts( const
if( subscribe )
{
ilog( "subscribe to ${id}", ("id",account->name) );
FC_ASSERT( std::distance(_subscribed_accounts.begin(), _subscribed_accounts.end()) <= 100 );
_subscribed_accounts.insert( account->get_id() );
subscribe_to_item( account->id );
}
@ -613,6 +711,25 @@ std::map<std::string, full_account> database_api_impl::get_full_accounts( const
[&acnt] (const call_order_object& call) {
acnt.call_orders.emplace_back(call);
});
auto settle_range = _db.get_index_type<force_settlement_index>().indices().get<by_account>().equal_range(account->id);
std::for_each(settle_range.first, settle_range.second,
[&acnt] (const force_settlement_object& settle) {
acnt.settle_orders.emplace_back(settle);
});
// get assets issued by user
auto asset_range = _db.get_index_type<asset_index>().indices().get<by_issuer>().equal_range(account->id);
std::for_each(asset_range.first, asset_range.second,
[&acnt] (const asset_object& asset) {
acnt.assets.emplace_back(asset.id);
});
// get withdraws permissions
auto withdraw_range = _db.get_index_type<withdraw_permission_index>().indices().get<by_from>().equal_range(account->id);
std::for_each(withdraw_range.first, withdraw_range.second,
[&acnt] (const withdraw_permission_object& withdraw) {
acnt.withdraws.emplace_back(withdraw);
});
auto pending_payouts_range =
_db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>().indices().get<by_account_dividend_payout>().equal_range(boost::make_tuple(account->id));
@ -895,6 +1012,86 @@ vector<optional<asset_object>> database_api_impl::lookup_asset_symbols(const vec
return result;
}
//////////////////////////////////////////////////////////////////////
// Peerplays //
//////////////////////////////////////////////////////////////////////
vector<sport_object> database_api::list_sports() const
{
return my->list_sports();
}
vector<sport_object> database_api_impl::list_sports() const
{
const auto& sport_object_idx = _db.get_index_type<sport_object_index>().indices().get<by_id>();
return boost::copy_range<vector<sport_object> >(sport_object_idx);
}
vector<event_group_object> database_api::list_event_groups(sport_id_type sport_id) const
{
return my->list_event_groups(sport_id);
}
vector<event_group_object> database_api_impl::list_event_groups(sport_id_type sport_id) const
{
const auto& event_group_idx = _db.get_index_type<event_group_object_index>().indices().get<by_sport_id>();
return boost::copy_range<vector<event_group_object> >(event_group_idx.equal_range(sport_id));
}
vector<event_object> database_api::list_events_in_group(event_group_id_type event_group_id) const
{
return my->list_events_in_group(event_group_id);
}
vector<event_object> database_api_impl::list_events_in_group(event_group_id_type event_group_id) const
{
const auto& event_idx = _db.get_index_type<event_object_index>().indices().get<by_event_group_id>();
return boost::copy_range<vector<event_object> >(event_idx.equal_range(event_group_id));
}
vector<betting_market_group_object> database_api::list_betting_market_groups(event_id_type event_id) const
{
return my->list_betting_market_groups(event_id);
}
vector<betting_market_group_object> database_api_impl::list_betting_market_groups(event_id_type event_id) const
{
const auto& betting_market_group_idx = _db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
return boost::copy_range<vector<betting_market_group_object> >(betting_market_group_idx.equal_range(event_id));
}
vector<betting_market_object> database_api::list_betting_markets(betting_market_group_id_type betting_market_group_id) const
{
return my->list_betting_markets(betting_market_group_id);
}
vector<betting_market_object> database_api_impl::list_betting_markets(betting_market_group_id_type betting_market_group_id) const
{
const auto& betting_market_idx = _db.get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
return boost::copy_range<vector<betting_market_object> >(betting_market_idx.equal_range(betting_market_group_id));
}
vector<bet_object> database_api::get_unmatched_bets_for_bettor(betting_market_id_type betting_market_id, account_id_type bettor_id) const
{
return my->get_unmatched_bets_for_bettor(betting_market_id, bettor_id);
}
vector<bet_object> database_api_impl::get_unmatched_bets_for_bettor(betting_market_id_type betting_market_id, account_id_type bettor_id) const
{
const auto& bet_idx = _db.get_index_type<bet_object_index>().indices().get<by_bettor_and_odds>();
return boost::copy_range<vector<bet_object> >(bet_idx.equal_range(std::make_tuple(bettor_id, betting_market_id)));
}
vector<bet_object> database_api::get_all_unmatched_bets_for_bettor(account_id_type bettor_id) const
{
return my->get_all_unmatched_bets_for_bettor(bettor_id);
}
vector<bet_object> database_api_impl::get_all_unmatched_bets_for_bettor(account_id_type bettor_id) const
{
const auto& bet_idx = _db.get_index_type<bet_object_index>().indices().get<by_bettor_and_odds>();
return boost::copy_range<vector<bet_object> >(bet_idx.equal_range(std::make_tuple(bettor_id)));
}
//////////////////////////////////////////////////////////////////////
// //
// Markets / feeds //
@ -1015,117 +1212,84 @@ void database_api_impl::unsubscribe_from_market(asset_id_type a, asset_id_type b
market_ticker database_api::get_ticker( const string& base, const string& quote )const
{
return my->get_ticker( base, quote );
return my->get_ticker( base, quote );
}
market_ticker database_api_impl::get_ticker( const string& base, const string& quote )const
{
auto assets = lookup_asset_symbols( {base, quote} );
FC_ASSERT( assets[0], "Invalid base asset symbol: ${s}", ("s",base) );
FC_ASSERT( assets[1], "Invalid quote asset symbol: ${s}", ("s",quote) );
const auto assets = lookup_asset_symbols( {base, quote} );
FC_ASSERT( assets[0], "Invalid base asset symbol: ${s}", ("s",base) );
FC_ASSERT( assets[1], "Invalid quote asset symbol: ${s}", ("s",quote) );
auto base_id = assets[0]->id;
auto quote_id = assets[1]->id;
market_ticker result;
result.base = base;
result.quote = quote;
result.latest = 0;
result.lowest_ask = 0;
result.highest_bid = 0;
result.percent_change = 0;
result.base_volume = 0;
result.quote_volume = 0;
market_ticker result;
try {
const fc::time_point_sec now = fc::time_point::now();
const fc::time_point_sec yesterday = fc::time_point_sec( now.sec_since_epoch() - 86400 );
const auto batch_size = 100;
result.base = base;
result.quote = quote;
result.base_volume = 0;
result.quote_volume = 0;
result.percent_change = 0;
result.lowest_ask = 0;
result.highest_bid = 0;
vector<market_trade> trades = get_trade_history( base, quote, now, yesterday, batch_size );
if( !trades.empty() )
{
result.latest = trades[0].price;
auto price_to_real = [&]( const share_type a, int p ) { return double( a.value ) / pow( 10, p ); };
while( !trades.empty() )
{
for( const market_trade& t: trades )
{
result.base_volume += t.value;
result.quote_volume += t.amount;
}
try {
if( base_id > quote_id ) std::swap(base_id, quote_id);
trades = get_trade_history( base, quote, trades.back().date, yesterday, batch_size );
}
uint32_t day = 86400;
auto now = fc::time_point_sec( fc::time_point::now() );
auto orders = get_order_book( base, quote, 1 );
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - day ), 100 );
const auto last_trade_yesterday = get_trade_history( base, quote, yesterday, fc::time_point_sec(), 1 );
if( !last_trade_yesterday.empty() )
{
const auto price_yesterday = last_trade_yesterday[0].price;
result.percent_change = ( (result.latest / price_yesterday) - 1 ) * 100;
}
}
else
{
const auto last_trade = get_trade_history( base, quote, now, fc::time_point_sec(), 1 );
if( !last_trade.empty() )
result.latest = last_trade[0].price;
}
result.latest = trades[0].price;
const auto orders = get_order_book( base, quote, 1 );
if( !orders.asks.empty() ) result.lowest_ask = orders.asks[0].price;
if( !orders.bids.empty() ) result.highest_bid = orders.bids[0].price;
} FC_CAPTURE_AND_RETHROW( (base)(quote) )
for ( market_trade t: trades )
{
result.base_volume += t.value;
result.quote_volume += t.amount;
}
while (trades.size() == 100)
{
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - day ), 100 );
for ( market_trade t: trades )
{
result.base_volume += t.value;
result.quote_volume += t.amount;
}
}
trades = get_trade_history( base, quote, trades.back().date, fc::time_point_sec(), 1 );
result.percent_change = trades.size() > 0 ? ( ( result.latest / trades.back().price ) - 1 ) * 100 : 0;
//if (assets[0]->id == base_id)
{
result.lowest_ask = orders.asks[0].price;
result.highest_bid = orders.bids[0].price;
}
return result;
} FC_CAPTURE_AND_RETHROW( (base)(quote) )
return result;
}
market_volume database_api::get_24_volume( const string& base, const string& quote )const
{
return my->get_24_volume( base, quote );
return my->get_24_volume( base, quote );
}
market_volume database_api_impl::get_24_volume( const string& base, const string& quote )const
{
auto assets = lookup_asset_symbols( {base, quote} );
FC_ASSERT( assets[0], "Invalid base asset symbol: ${s}", ("s",base) );
FC_ASSERT( assets[1], "Invalid quote asset symbol: ${s}", ("s",quote) );
const auto ticker = get_ticker( base, quote );
auto base_id = assets[0]->id;
auto quote_id = assets[1]->id;
market_volume result;
result.base = ticker.base;
result.quote = ticker.quote;
result.base_volume = ticker.base_volume;
result.quote_volume = ticker.quote_volume;
market_volume result;
result.base = base;
result.quote = quote;
result.base_volume = 0;
result.quote_volume = 0;
try {
if( base_id > quote_id ) std::swap(base_id, quote_id);
uint32_t bucket_size = 86400;
auto now = fc::time_point_sec( fc::time_point::now() );
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
for ( market_trade t: trades )
{
result.base_volume += t.value;
result.quote_volume += t.amount;
}
while (trades.size() == 100)
{
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
for ( market_trade t: trades )
{
result.base_volume += t.value;
result.quote_volume += t.amount;
}
}
return result;
} FC_CAPTURE_AND_RETHROW( (base)(quote) )
return result;
}
order_book database_api::get_order_book( const string& base, const string& quote, unsigned limit )const
@ -1175,7 +1339,7 @@ order_book database_api_impl::get_order_book( const string& base, const string&
order ord;
ord.price = price_to_real( o.sell_price );
ord.quote = asset_to_real( o.for_sale, assets[1]->precision );
ord.base = asset_to_real( share_type( ( uint64_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[0]->precision );
ord.base = asset_to_real( share_type( ( uint128_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[0]->precision );
result.asks.push_back( ord );
}
}
@ -1865,109 +2029,113 @@ vector<tournament_id_type> database_api_impl::get_registered_tournaments(account
void database_api_impl::broadcast_updates( const vector<variant>& updates )
{
if( updates.size() ) {
if( updates.size() && _subscribe_callback ) {
auto capture_this = shared_from_this();
fc::async([capture_this,updates](){
capture_this->_subscribe_callback( fc::variant(updates) );
if(capture_this->_subscribe_callback)
capture_this->_subscribe_callback( fc::variant(updates) );
});
}
}
void database_api_impl::on_objects_removed( const vector<const object*>& objs )
void database_api_impl::broadcast_market_updates( const market_queue_type& queue)
{
if( queue.size() )
{
auto capture_this = shared_from_this();
fc::async([capture_this, this, queue](){
for( const auto& item : queue )
{
auto sub = _market_subscriptions.find(item.first);
if( sub != _market_subscriptions.end() )
sub->second( fc::variant(item.second ) );
}
});
}
}
void database_api_impl::on_objects_removed( const vector<object_id_type>& ids, const vector<const object*>& objs, const flat_set<account_id_type>& impacted_accounts)
{
handle_object_changed(_notify_remove_create, false, ids, impacted_accounts,
[objs](object_id_type id) -> const object* {
auto it = std::find_if(
objs.begin(), objs.end(),
[id](const object* o) {return o != nullptr && o->id == id;});
if (it != objs.end())
return *it;
return nullptr;
}
);
}
void database_api_impl::on_objects_new(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts)
{
handle_object_changed(_notify_remove_create, true, ids, impacted_accounts,
std::bind(&object_database::find_object, &_db, std::placeholders::_1)
);
}
void database_api_impl::on_objects_changed(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts)
{
handle_object_changed(false, true, ids, impacted_accounts,
std::bind(&object_database::find_object, &_db, std::placeholders::_1)
);
}
void database_api_impl::handle_object_changed(bool force_notify, bool full_object, const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts, std::function<const object*(object_id_type id)> find_object)
{
/// we need to ensure the database_api is not deleted for the life of the async operation
if( _subscribe_callback )
{
vector<variant> updates;
updates.reserve(objs.size());
vector<variant> updates;
for( auto obj : objs )
updates.emplace_back( obj->id );
broadcast_updates( updates );
for(auto id : ids)
{
if( force_notify || is_subscribed_to_item(id) || is_impacted_account(impacted_accounts) )
{
if( full_object )
{
auto obj = find_object(id);
if( obj )
{
updates.emplace_back( obj->to_variant() );
}
}
else
{
updates.emplace_back( id );
}
}
}
broadcast_updates(updates);
}
if( _market_subscriptions.size() )
{
map< pair<asset_id_type, asset_id_type>, vector<variant> > broadcast_queue;
for( const auto& obj : objs )
{
const limit_order_object* order = dynamic_cast<const limit_order_object*>(obj);
if( order )
{
auto sub = _market_subscriptions.find( order->get_market() );
if( sub != _market_subscriptions.end() )
broadcast_queue[order->get_market()].emplace_back( order->id );
}
}
if( broadcast_queue.size() )
{
auto capture_this = shared_from_this();
fc::async([capture_this,this,broadcast_queue](){
for( const auto& item : broadcast_queue )
{
auto sub = _market_subscriptions.find(item.first);
if( sub != _market_subscriptions.end() )
sub->second( fc::variant(item.second ) );
}
});
}
}
}
void database_api_impl::on_objects_changed(const vector<object_id_type>& ids)
{
vector<variant> updates;
map< pair<asset_id_type, asset_id_type>, vector<variant> > market_broadcast_queue;
for(auto id : ids)
{
const object* obj = nullptr;
if( _subscribe_callback )
{
obj = _db.find_object( id );
if( obj )
{
updates.emplace_back( obj->to_variant() );
}
else
{
updates.emplace_back(id); // send just the id to indicate removal
}
}
if( _market_subscriptions.size() )
{
if( !_subscribe_callback )
obj = _db.find_object( id );
if( obj )
{
const limit_order_object* order = dynamic_cast<const limit_order_object*>(obj);
if( order )
{
auto sub = _market_subscriptions.find( order->get_market() );
if( sub != _market_subscriptions.end() )
market_broadcast_queue[order->get_market()].emplace_back( order->id );
}
}
}
}
auto capture_this = shared_from_this();
market_queue_type broadcast_queue;
/// pushing the future back / popping the prior future if it is complete.
/// if a connection hangs then this could get backed up and result in
/// a failure to exit cleanly.
fc::async([capture_this,this,updates,market_broadcast_queue](){
if( _subscribe_callback )
_subscribe_callback( updates );
//fc::async([capture_this,this,updates,market_broadcast_queue](){
//if( _subscribe_callback )
// _subscribe_callback( updates );
for( const auto& item : market_broadcast_queue )
for(auto id : ids)
{
auto sub = _market_subscriptions.find(item.first);
if( sub != _market_subscriptions.end() )
sub->second( fc::variant(item.second ) );
if( id.is<call_order_object>() )
{
enqueue_if_subscribed_to_market<call_order_object>( find_object(id), broadcast_queue, full_object );
}
else if( id.is<limit_order_object>() )
{
enqueue_if_subscribed_to_market<limit_order_object>( find_object(id), broadcast_queue, full_object );
}
}
});
broadcast_market_updates(broadcast_queue);
}
}
/** note: this method cannot yield because it is called in the middle of

View file

@ -208,6 +208,48 @@ struct get_impacted_account_visitor
{
_impacted.insert( op.account_id );
}
void operator()( const sport_create_operation& op ) {}
void operator()( const sport_update_operation& op ) {}
void operator()( const event_group_create_operation& op ) {}
void operator()( const event_group_update_operation& op ) {}
void operator()( const event_create_operation& op ) {}
void operator()( const event_update_operation& op ) {}
void operator()( const event_update_status_operation& op ) {}
void operator()( const betting_market_rules_create_operation& op ) {}
void operator()( const betting_market_rules_update_operation& op ) {}
void operator()( const betting_market_group_create_operation& op ) {}
void operator()( const betting_market_group_update_operation& op ) {}
void operator()( const betting_market_create_operation& op ) {}
void operator()( const betting_market_update_operation& op ) {}
void operator()( const betting_market_group_resolve_operation& op ) {}
void operator()( const betting_market_group_cancel_unmatched_bets_operation& op ) {}
void operator()( const bet_place_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const bet_cancel_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const bet_canceled_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const bet_adjusted_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const bet_matched_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const betting_market_group_resolved_operation& op )
{
_impacted.insert( op.bettor_id );
}
void operator()( const tournament_create_operation& op )
{
_impacted.insert( op.creator );

View file

@ -29,8 +29,10 @@
#include <graphene/chain/protocol/confidential.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
#include <graphene/accounts_list/accounts_list_plugin.hpp>
#include <graphene/debug_witness/debug_api.hpp>
#include <graphene/bookie/bookie_api.hpp>
#include <graphene/net/node.hpp>
@ -49,6 +51,7 @@
namespace graphene { namespace app {
using namespace graphene::chain;
using namespace graphene::market_history;
using namespace graphene::accounts_list;
using namespace fc::ecc;
using namespace std;
@ -71,6 +74,18 @@ namespace graphene { namespace app {
string message_out;
};
struct account_asset_balance
{
string name;
account_id_type account_id;
share_type amount;
};
struct asset_holders
{
asset_id_type asset_id;
int count;
};
/**
* @brief The history_api class implements the RPC API for account history
*
@ -93,6 +108,22 @@ namespace graphene { namespace app {
operation_history_id_type stop = operation_history_id_type(),
unsigned limit = 100,
operation_history_id_type start = operation_history_id_type())const;
/**
* @brief Get only asked operations relevant to the specified account
* @param account The account whose history should be queried
* @param operation_id The ID of the operation we want to get operations in the account( 0 = transfer , 1 = limit order create, ...)
* @param stop ID of the earliest operation to retrieve
* @param limit Maximum number of operations to retrieve (must not exceed 100)
* @param start ID of the most recent operation to retrieve
* @return A list of operations performed by account, ordered from most recent to oldest.
*/
vector<operation_history_object> get_account_history_operations(account_id_type account,
int operation_id,
operation_history_id_type start = operation_history_id_type(),
operation_history_id_type stop = operation_history_id_type(),
unsigned limit = 100)const;
/**
* @breif Get operations relevant to the specified account referenced
* by an event numbering specific to the account. The current number of operations
@ -113,11 +144,28 @@ namespace graphene { namespace app {
vector<order_history_object> get_fill_order_history( asset_id_type a, asset_id_type b, uint32_t limit )const;
vector<bucket_object> get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds,
fc::time_point_sec start, fc::time_point_sec end )const;
vector<account_balance_object> list_core_accounts()const;
flat_set<uint32_t> get_market_history_buckets()const;
private:
application& _app;
};
/**
* @brief Block api
*/
class block_api
{
public:
block_api(graphene::chain::database& db);
~block_api();
vector<optional<signed_block>> get_blocks(uint32_t block_num_from, uint32_t block_num_to)const;
private:
graphene::chain::database& _db;
};
/**
* @brief The network_broadcast_api class allows broadcasting of transactions.
*/
@ -151,6 +199,12 @@ namespace graphene { namespace app {
*/
void broadcast_transaction_with_callback( confirmation_callback cb, const signed_transaction& trx);
/** this version of broadcast transaction registers a callback method that will be called when the transaction is
* included into a block. The callback method includes the transaction id, block number, and transaction number in the
* block.
*/
fc::variant broadcast_transaction_synchronous(const signed_transaction& trx);
void broadcast_block( const signed_block& block );
/**
@ -252,6 +306,23 @@ namespace graphene { namespace app {
range_proof_info range_get_info( const std::vector<char>& proof );
};
/**
* @brief
*/
class asset_api
{
public:
asset_api(graphene::chain::database& db);
~asset_api();
vector<account_asset_balance> get_asset_holders( asset_id_type asset_id, uint32_t start, uint32_t limit )const;
int get_asset_holders_count( asset_id_type asset_id )const;
vector<asset_holders> get_all_asset_holders() const;
private:
graphene::chain::database& _db;
};
/**
* @brief The login_api class implements the bottom layer of the RPC API
*
@ -273,6 +344,8 @@ namespace graphene { namespace app {
* has sucessfully authenticated.
*/
bool login(const string& user, const string& password);
/// @brief Retrieve the network block API
fc::api<block_api> block()const;
/// @brief Retrieve the network broadcast API
fc::api<network_broadcast_api> network_broadcast()const;
/// @brief Retrieve the database API
@ -283,20 +356,27 @@ namespace graphene { namespace app {
fc::api<network_node_api> network_node()const;
/// @brief Retrieve the cryptography API
fc::api<crypto_api> crypto()const;
/// @brief Retrieve the asset API
fc::api<asset_api> asset()const;
/// @brief Retrieve the debug API (if available)
fc::api<graphene::debug_witness::debug_api> debug()const;
/// @brief Retrieve the bookie API (if available)
fc::api<graphene::bookie::bookie_api> bookie()const;
private:
/// @brief Called to enable an API, not reflected.
void enable_api( const string& api_name );
private:
application& _app;
optional< fc::api<block_api> > _block_api;
optional< fc::api<database_api> > _database_api;
optional< fc::api<network_broadcast_api> > _network_broadcast_api;
optional< fc::api<network_node_api> > _network_node_api;
optional< fc::api<history_api> > _history_api;
optional< fc::api<crypto_api> > _crypto_api;
optional< fc::api<asset_api> > _asset_api;
optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;
optional< fc::api<graphene::bookie::bookie_api> > _bookie_api;
};
}} // graphene::app
@ -310,16 +390,25 @@ FC_REFLECT( graphene::app::verify_range_proof_rewind_result,
//FC_REFLECT_TYPENAME( fc::ecc::compact_signature );
//FC_REFLECT_TYPENAME( fc::ecc::commitment_type );
FC_REFLECT( graphene::app::account_asset_balance, (name)(account_id)(amount) );
FC_REFLECT( graphene::app::asset_holders, (asset_id)(count) );
FC_API(graphene::app::history_api,
(get_account_history)
(get_account_history_operations)
(get_relative_account_history)
(get_fill_order_history)
(get_market_history)
(get_market_history_buckets)
(list_core_accounts)
)
FC_API(graphene::app::block_api,
(get_blocks)
)
FC_API(graphene::app::network_broadcast_api,
(broadcast_transaction)
(broadcast_transaction_with_callback)
(broadcast_transaction_synchronous)
(broadcast_block)
)
FC_API(graphene::app::network_node_api,
@ -341,12 +430,20 @@ FC_API(graphene::app::crypto_api,
(verify_range_proof_rewind)
(range_get_info)
)
FC_API(graphene::app::asset_api,
(get_asset_holders)
(get_asset_holders_count)
(get_all_asset_holders)
)
FC_API(graphene::app::login_api,
(login)
(block)
(network_broadcast)
(database)
(history)
(network_node)
(crypto)
(asset)
(debug)
(bookie)
)

View file

@ -38,6 +38,12 @@
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/global_betting_statistics_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
@ -159,6 +165,14 @@ class database_api
*/
optional<block_header> get_block_header(uint32_t block_num)const;
/**
* @brief Retrieve multiple block header by block numbers
* @param block_num vector containing heights of the block whose header should be returned
* @return array of headers of the referenced blocks, or null if no matching block was found
*/
map<uint32_t, optional<block_header>> get_block_header_batch(const vector<uint32_t> block_nums)const;
/**
* @brief Retrieve a full, signed block
* @param block_num Height of the block to be returned
@ -213,6 +227,15 @@ class database_api
vector<vector<account_id_type>> get_key_references( vector<public_key_type> key )const;
/**
* Determine whether a textual representation of a public key
* (in Base-58 format) is *currently* linked
* to any *registered* (i.e. non-stealth) account on the blockchain
* @param public_key Public key
* @return Whether a public key is known
*/
bool is_public_key_registered(string public_key) const;
//////////////
// Accounts //
//////////////
@ -320,6 +343,50 @@ class database_api
*/
vector<optional<asset_object>> lookup_asset_symbols(const vector<string>& symbols_or_ids)const;
/////////////////////
// Peerplays //
/////////////////////
/**
* @brief Get global betting statistics
*/
global_betting_statistics_object get_global_betting_statistics() const;
/**
* @brief Get a list of all sports
*/
vector<sport_object> list_sports() const;
/**
* @brief Return a list of all event groups for a sport (e.g. all soccer leagues in soccer)
*/
vector<event_group_object> list_event_groups(sport_id_type sport_id) const;
/**
* @brief Return a list of all events in an event group
*/
vector<event_object> list_events_in_group(event_group_id_type event_group_id) const;
/**
* @brief Return a list of all betting market groups for an event
*/
vector<betting_market_group_object> list_betting_market_groups(event_id_type) const;
/**
* @brief Return a list of all betting markets for a betting market group
*/
vector<betting_market_object> list_betting_markets(betting_market_group_id_type) const;
/**
* @brief Return a list of all unmatched bets for a given account on a specific betting market
*/
vector<bet_object> get_unmatched_bets_for_bettor(betting_market_id_type, account_id_type) const;
/**
* @brief Return a list of all unmatched bets for a given account (includes bets on all markets)
*/
vector<bet_object> get_all_unmatched_bets_for_bettor(account_id_type) const;
/////////////////////
// Markets / feeds //
/////////////////////
@ -602,6 +669,7 @@ FC_API(graphene::app::database_api,
// Blocks and transactions
(get_block_header)
(get_block_header_batch)
(get_block)
(get_transaction)
(get_recent_transaction_by_id)
@ -615,6 +683,7 @@ FC_API(graphene::app::database_api,
// Keys
(get_key_references)
(is_public_key_registered)
// Accounts
(get_accounts)
@ -637,6 +706,16 @@ FC_API(graphene::app::database_api,
(list_assets)
(lookup_asset_symbols)
// Peerplays
(list_sports)
(get_global_betting_statistics)
(list_event_groups)
(list_events_in_group)
(list_betting_market_groups)
(list_betting_markets)
(get_unmatched_bets_for_bettor)
(get_all_unmatched_bets_for_bettor)
// Markets / feeds
(get_order_book)
(get_limit_orders)

View file

@ -26,6 +26,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/market_evaluator.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
namespace graphene { namespace app {
using namespace graphene::chain;
@ -43,13 +44,17 @@ namespace graphene { namespace app {
vector<vesting_balance_object> vesting_balances;
vector<limit_order_object> limit_orders;
vector<call_order_object> call_orders;
vector<force_settlement_object> settle_orders;
vector<proposal_object> proposals;
vector<asset_id_type> assets;
vector<withdraw_permission_object> withdraws;
// vector<pending_dividend_payout_balance_object> pending_dividend_payments;
vector<pending_dividend_payout_balance_for_holder_object> pending_dividend_payments;
};
} }
FC_REFLECT( graphene::app::full_account,
FC_REFLECT( graphene::app::full_account,
(account)
(statistics)
(registrar_name)
@ -61,6 +66,10 @@ FC_REFLECT( graphene::app::full_account,
(vesting_balances)
(limit_orders)
(call_orders)
(settle_orders)
(proposals)
(assets)
(withdraws)
(proposals)
(pending_dividend_payments)
)

View file

@ -6,10 +6,12 @@ set_source_files_properties( "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain
add_dependencies( build_hardfork_hpp cat-parts )
file(GLOB HEADERS "include/graphene/chain/*.hpp")
file(GLOB PROTOCOL_HEADERS "include/graphene/chain/protocol/*.hpp")
if( GRAPHENE_DISABLE_UNITY_BUILD )
set( GRAPHENE_DB_FILES
db_balance.cpp
db_bet.cpp
db_block.cpp
db_debug.cpp
db_getter.cpp
@ -94,7 +96,20 @@ add_library( graphene_chain
is_authorized_asset.cpp
protocol/sport.cpp
sport_evaluator.cpp
protocol/event_group.cpp
event_group_evaluator.cpp
protocol/event.cpp
event_evaluator.cpp
event_object.cpp
protocol/betting_market.cpp
betting_market_evaluator.cpp
betting_market_object.cpp
betting_market_group_object.cpp
${HEADERS}
${PROTOCOL_HEADERS}
"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp"
)
@ -114,3 +129,5 @@ INSTALL( TARGETS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/chain" )
INSTALL( FILES ${PROTOCOL_HEADERS} DESTINATION "include/graphene/chain/protocol" )

View file

@ -0,0 +1,393 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define DEFAULT_LOGGER "betting"
#include <fc/smart_ref_impl.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
namespace graphene { namespace chain {
void_result betting_market_rules_create_evaluator::do_evaluate(const betting_market_rules_create_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type betting_market_rules_create_evaluator::do_apply(const betting_market_rules_create_operation& op)
{ try {
const betting_market_rules_object& new_betting_market_rules =
db().create<betting_market_rules_object>([&](betting_market_rules_object& betting_market_rules_obj) {
betting_market_rules_obj.name = op.name;
betting_market_rules_obj.description = op.description;
});
return new_betting_market_rules.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_rules_update_evaluator::do_evaluate(const betting_market_rules_update_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
_rules = &op.betting_market_rules_id(db());
FC_ASSERT(op.new_name.valid() || op.new_description.valid(), "nothing to update");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_rules_update_evaluator::do_apply(const betting_market_rules_update_operation& op)
{ try {
db().modify(*_rules, [&](betting_market_rules_object& betting_market_rules) {
if (op.new_name.valid())
betting_market_rules.name = *op.new_name;
if (op.new_description.valid())
betting_market_rules.description = *op.new_description;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_create_evaluator::do_evaluate(const betting_market_group_create_operation& op)
{ try {
database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
// the event_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly an event
object_id_type resolved_event_id = op.event_id;
if (is_relative(op.event_id))
resolved_event_id = get_relative_id(op.event_id);
FC_ASSERT(resolved_event_id.space() == event_id_type::space_id &&
resolved_event_id.type() == event_id_type::type_id,
"event_id must refer to a event_id_type");
_event_id = resolved_event_id;
FC_ASSERT(d.find_object(_event_id), "Invalid event specified");
FC_ASSERT(d.find_object(op.asset_id), "Invalid asset specified");
// the rules_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly rules
object_id_type resolved_rules_id = op.rules_id;
if (is_relative(op.rules_id))
resolved_rules_id = get_relative_id(op.rules_id);
FC_ASSERT(resolved_rules_id.space() == betting_market_rules_id_type::space_id &&
resolved_rules_id.type() == betting_market_rules_id_type::type_id,
"rules_id must refer to a betting_market_rules_id_type");
_rules_id = resolved_rules_id;
FC_ASSERT(d.find_object(_rules_id), "Invalid rules specified");
return void_result();
} FC_CAPTURE_AND_RETHROW((op)) }
object_id_type betting_market_group_create_evaluator::do_apply(const betting_market_group_create_operation& op)
{ try {
const betting_market_group_object& new_betting_market_group =
db().create<betting_market_group_object>([&](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.event_id = _event_id;
betting_market_group_obj.rules_id = _rules_id;
betting_market_group_obj.description = op.description;
betting_market_group_obj.asset_id = op.asset_id;
betting_market_group_obj.never_in_play = op.never_in_play;
betting_market_group_obj.delay_before_settling = op.delay_before_settling;
});
return new_betting_market_group.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_update_evaluator::do_evaluate(const betting_market_group_update_operation& op)
{ try {
database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
_betting_market_group = &op.betting_market_group_id(d);
FC_ASSERT(op.new_description || op.new_rules_id || op.status, "nothing to change");
if (op.new_rules_id)
{
// the rules_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly rules
object_id_type resolved_rules_id = *op.new_rules_id;
if (is_relative(*op.new_rules_id))
resolved_rules_id = get_relative_id(*op.new_rules_id);
FC_ASSERT(resolved_rules_id.space() == betting_market_rules_id_type::space_id &&
resolved_rules_id.type() == betting_market_rules_id_type::type_id,
"rules_id must refer to a betting_market_rules_id_type");
_rules_id = resolved_rules_id;
FC_ASSERT(d.find_object(_rules_id), "invalid rules specified");
}
if (op.status)
FC_ASSERT(_betting_market_group->get_status() != *op.status, "status would not change the state of the betting market group");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_update_evaluator::do_apply(const betting_market_group_update_operation& op)
{ try {
database& d = db();
d.modify(*_betting_market_group, [&](betting_market_group_object& betting_market_group) {
if (op.new_description)
betting_market_group.description = *op.new_description;
if (op.new_rules_id)
betting_market_group.rules_id = _rules_id;
bool bets_were_delayed = betting_market_group.bets_are_delayed();
if (op.status)
betting_market_group.dispatch_new_status(d, *op.status);
bool bets_are_delayed = betting_market_group.bets_are_delayed();
// if we have transitioned from in-play to not-in-play-but-still-accepting-bets,
// place all delayed bets now
if (betting_market_group.bets_are_allowed() &&
bets_were_delayed && !bets_are_delayed)
{
const auto& bet_odds_idx = d.get_index_type<bet_object_index>().indices().get<by_odds>();
auto bet_iter = bet_odds_idx.begin();
bool last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
while (!last)
{
const bet_object& delayed_bet = *bet_iter;
++bet_iter;
last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
const betting_market_object& betting_market = delayed_bet.betting_market_id(d);
if (betting_market.group_id == op.betting_market_group_id)
{
d.modify(delayed_bet, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
d.place_bet(delayed_bet);
}
}
}
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_create_evaluator::do_evaluate(const betting_market_create_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
// the betting_market_group_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly an betting_market_group
object_id_type resolved_betting_market_group_id = op.group_id;
if (is_relative(op.group_id))
resolved_betting_market_group_id = get_relative_id(op.group_id);
FC_ASSERT(resolved_betting_market_group_id.space() == betting_market_group_id_type::space_id &&
resolved_betting_market_group_id.type() == betting_market_group_id_type::type_id,
"betting_market_group_id must refer to a betting_market_group_id_type");
_group_id = resolved_betting_market_group_id;
FC_ASSERT(db().find_object(_group_id), "Invalid betting_market_group specified");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type betting_market_create_evaluator::do_apply(const betting_market_create_operation& op)
{ try {
const betting_market_object& new_betting_market =
db().create<betting_market_object>([&](betting_market_object& betting_market_obj) {
betting_market_obj.group_id = _group_id;
betting_market_obj.description = op.description;
betting_market_obj.payout_condition = op.payout_condition;
});
return new_betting_market.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_update_evaluator::do_evaluate(const betting_market_update_operation& op)
{ try {
database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
_betting_market = &op.betting_market_id(d);
FC_ASSERT(op.new_group_id.valid() || op.new_description.valid() || op.new_payout_condition.valid(), "nothing to change");
if (op.new_group_id.valid())
{
// the betting_market_group_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly an betting_market_group
object_id_type resolved_betting_market_group_id = *op.new_group_id;
if (is_relative(*op.new_group_id))
resolved_betting_market_group_id = get_relative_id(*op.new_group_id);
FC_ASSERT(resolved_betting_market_group_id.space() == betting_market_group_id_type::space_id &&
resolved_betting_market_group_id.type() == betting_market_group_id_type::type_id,
"betting_market_group_id must refer to a betting_market_group_id_type");
_group_id = resolved_betting_market_group_id;
FC_ASSERT(d.find_object(_group_id), "invalid betting_market_group specified");
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_update_evaluator::do_apply(const betting_market_update_operation& op)
{ try {
db().modify(*_betting_market, [&](betting_market_object& betting_market) {
if (op.new_group_id.valid())
betting_market.group_id = _group_id;
if (op.new_payout_condition.valid())
betting_market.payout_condition = *op.new_payout_condition;
if (op.new_description.valid())
betting_market.description = *op.new_description;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
{ try {
const database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
_betting_market = &op.betting_market_id(d);
_betting_market_group = &_betting_market->group_id(d);
FC_ASSERT( op.amount_to_bet.asset_id == _betting_market_group->asset_id,
"Asset type bet does not match the market's asset type" );
ddump((_betting_market_group->get_status()));
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::frozen,
"Unable to place bets while the market is frozen" );
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::closed,
"Unable to place bets while the market is closed" );
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::graded,
"Unable to place bets while the market is graded" );
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::re_grading,
"Unable to place bets while the market is re-grading" );
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::settled,
"Unable to place bets while the market is settled" );
_asset = &_betting_market_group->asset_id(d);
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
_current_params = &d.get_global_properties().parameters;
// are their odds valid
FC_ASSERT( op.backer_multiplier >= _current_params->min_bet_multiplier &&
op.backer_multiplier <= _current_params->max_bet_multiplier,
"Bet odds are outside the blockchain's limits" );
if (!_current_params->permitted_betting_odds_increments.empty())
{
bet_multiplier_type allowed_increment;
const auto iter = _current_params->permitted_betting_odds_increments.upper_bound(op.backer_multiplier);
if (iter == _current_params->permitted_betting_odds_increments.end())
allowed_increment = std::prev(_current_params->permitted_betting_odds_increments.end())->second;
else
allowed_increment = iter->second;
FC_ASSERT(op.backer_multiplier % allowed_increment == 0, "Bet odds must be a multiple of ${allowed_increment}", ("allowed_increment", allowed_increment));
}
FC_ASSERT(op.amount_to_bet.amount > share_type(), "Cannot place a bet with zero amount");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
{ try {
database& d = db();
const bet_object& new_bet =
d.create<bet_object>([&](bet_object& bet_obj) {
bet_obj.bettor_id = op.bettor_id;
bet_obj.betting_market_id = op.betting_market_id;
bet_obj.amount_to_bet = op.amount_to_bet;
bet_obj.backer_multiplier = op.backer_multiplier;
bet_obj.back_or_lay = op.back_or_lay;
if (_betting_market_group->bets_are_delayed()) {
// the bet will be included in the block at time `head_block_time() + block_interval`, so make the delay relative
// to the time it's included in a block
bet_obj.end_of_delay = d.head_block_time() + _current_params->block_interval + _current_params->live_betting_delay_time;
}
});
bet_id_type new_bet_id = new_bet.id; // save the bet id here, new_bet may be deleted during place_bet()
// place the bet, this may return guaranteed winnings
ddump((_betting_market_group->bets_are_delayed())(_current_params->live_betting_delay_time));
if (!_betting_market_group->bets_are_delayed() || _current_params->live_betting_delay_time <= 0)
d.place_bet(new_bet);
// now that their guaranteed winnings have been returned, check whether they have enough in their account to place the bet
FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= op.amount_to_bet.amount, "insufficient balance",
("balance", d.get_balance(*fee_paying_account, *_asset))("amount_to_bet", op.amount_to_bet.amount) );
// pay for it
d.adjust_balance(fee_paying_account->id, -op.amount_to_bet);
return new_bet_id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result bet_cancel_evaluator::do_evaluate(const bet_cancel_operation& op)
{ try {
const database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
_bet_to_cancel = &op.bet_to_cancel(d);
FC_ASSERT( op.bettor_id == _bet_to_cancel->bettor_id, "You can only cancel your own bets" );
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
{ try {
db().cancel_bet(*_bet_to_cancel);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_resolve_evaluator::do_evaluate(const betting_market_group_resolve_operation& op)
{ try {
database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
_betting_market_group = &op.betting_market_group_id(d);
d.validate_betting_market_group_resolutions(*_betting_market_group, op.resolutions);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_resolve_evaluator::do_apply(const betting_market_group_resolve_operation& op)
{ try {
db().resolve_betting_market_group(*_betting_market_group, op.resolutions);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_cancel_unmatched_bets_evaluator::do_evaluate(const betting_market_group_cancel_unmatched_bets_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
_betting_market_group = &op.betting_market_group_id(db());
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_cancel_unmatched_bets_evaluator::do_apply(const betting_market_group_cancel_unmatched_bets_operation& op)
{ try {
db().cancel_all_unmatched_bets_on_betting_market_group(*_betting_market_group);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} } // graphene::chain

View file

@ -0,0 +1,547 @@
#define DEFAULT_LOGGER "betting"
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/database.hpp>
#include <boost/math/common_factor_rt.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
namespace graphene { namespace chain {
enum class betting_market_group_state {
upcoming,
frozen_upcoming,
in_play,
frozen_in_play,
closed,
graded,
canceled,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::betting_market_group_state,
(upcoming)
(frozen_upcoming)
(in_play)
(frozen_in_play)
(closed)
(graded)
(canceled)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
// betting market object implementation
namespace
{
// Events -- most events happen when the witnesses publish an update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct upcoming_event
{
database& db;
upcoming_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct in_play_event
{
database& db;
in_play_event(database& db) : db(db) {}
};
struct closed_event
{
database& db;
bool closed_by_event;
closed_event(database& db, bool closed_by_event) : db(db), closed_by_event(closed_by_event) {}
};
struct graded_event
{
database& db;
graded_event(database& db) : db(db) {}
};
struct re_grading_event
{
database& db;
re_grading_event(database& db) : db(db) {}
};
struct settled_event
{
database& db;
settled_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
// true if this was triggered by setting event to canceled state,
// false if this was triggered directly on this betting market group
bool canceled_by_event;
canceled_event(database& db, bool canceled_by_event = false) : db(db), canceled_by_event(canceled_by_event) {}
};
// Events
struct betting_market_group_state_machine_ : public msm::front::state_machine_def<betting_market_group_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct upcoming : public msm::front::state<>
{
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> upcoming", ("id", fsm.betting_market_group_obj->id));
// when a betting market group goes from frozen -> upcoming, transition the markets from frozen -> unresolved
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
try
{
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_unresolved_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
}
}
};
struct frozen_upcoming : public msm::front::state<>
{
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> frozen_upcoming", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_frozen_event(event.db);
});
}
};
struct in_play : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> in_play", ("id", fsm.betting_market_group_obj->id));
// when an event goes in-play, cancel all unmatched bets in its betting markets
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.cancel_all_unmatched_bets(event.db);
try {
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_unresolved_event(event.db);
});
} catch (const graphene::chain::no_transition&) {
// if this wasn't a transition from frozen state, this wasn't necessary
}
});
}
};
struct frozen_in_play : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> frozen_in_play", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_frozen_event(event.db);
});
}
};
struct closed : public msm::front::state<> {
template <class Event>
void on_entry(const Event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> closed", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = fsm_event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market) {
betting_market.cancel_all_unmatched_bets(fsm_event.db);
betting_market.on_closed_event(fsm_event.db);
});
// then notify the event that this betting market is now closed so it can change its status accordingly
if (!fsm_event.closed_by_event) {
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_closed(fsm_event.db, fsm.betting_market_group_obj->id);
});
}
}
};
struct graded : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> graded", ("id", fsm.betting_market_group_obj->id));
fsm.betting_market_group_obj->settling_time = event.db.head_block_time() + fsm.betting_market_group_obj->delay_before_settling;
dlog("grading complete, setting settling time to ${settling_time}", ("settling_time", fsm.betting_market_group_obj->settling_time));
}
};
struct re_grading : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> re_grading", ("id", fsm.betting_market_group_obj->id));
}
};
struct settled : public msm::front::state<> {
template <class Event>
void on_entry(const Event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> settled", ("id", fsm.betting_market_group_obj->id));
// TODO: what triggers this? I guess it will be the blockchain when its settling delay expires. So in that case, it should
// trigger the payout in the betting markets
auto& betting_market_index = fsm_event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market) {
betting_market.on_settled_event(fsm_event.db);
});
// then notify the event that this betting market is now resolved so it can change its status accordingly
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_resolved(fsm_event.db, fsm.betting_market_group_obj->id, false);
});
}
};
struct canceled : public msm::front::state<>{
void on_entry(const canceled_event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> canceled", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = fsm_event.db.get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id));
for (const betting_market_object& betting_market : betting_markets_in_group)
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market_obj) {
betting_market_obj.on_canceled_event(fsm_event.db);
});
if (!fsm_event.canceled_by_event) {
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_resolved(fsm_event.db, fsm.betting_market_group_obj->id, true);
});
}
fsm.betting_market_group_obj->settling_time = fsm_event.db.head_block_time();
dlog("cancel complete, setting settling time to ${settling_time}", ("settling_time", fsm.betting_market_group_obj->settling_time));
}
};
typedef upcoming initial_state;
// actions
void cancel_all_unmatched_bets(const in_play_event& event) {
event.db.cancel_all_unmatched_bets_on_betting_market_group(*betting_market_group_obj);
}
// guards
bool in_play_is_allowed(const in_play_event& event) {
return !betting_market_group_obj->never_in_play;
}
typedef betting_market_group_state_machine_ x; // makes transition table cleaner
// Transition table for betting market
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < upcoming, frozen_event, frozen_upcoming >,
row < upcoming, in_play_event, in_play, &x::cancel_all_unmatched_bets, &x::in_play_is_allowed >,
_row < upcoming, closed_event, closed >,
_row < upcoming, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < frozen_upcoming, upcoming_event, upcoming >,
_row < frozen_upcoming, in_play_event, upcoming >,
row < frozen_upcoming, in_play_event, in_play, &x::cancel_all_unmatched_bets, &x::in_play_is_allowed >,
_row < frozen_upcoming, closed_event, closed >,
_row < frozen_upcoming, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < in_play, frozen_event, frozen_in_play >,
_row < in_play, closed_event, closed >,
_row < in_play, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < frozen_in_play, in_play_event, in_play >,
_row < frozen_in_play, closed_event, closed >,
_row < frozen_in_play, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < closed, graded_event, graded >,
_row < closed, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
//_row < graded re_grading_event, re_grading >,
_row < graded, settled_event, settled >,
_row < graded, canceled_event, canceled >
// +-------------------+------------------+---------------------+------------------------------+----------------------+
//_row < re_grading, graded_event, graded >,
//_row < re_grading, canceled_event, canceled >
// +-------------------+------------------+---------------------+------------------------------+----------------------+
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
betting_market_group_object* betting_market_group_obj;
betting_market_group_state_machine_(betting_market_group_object* betting_market_group_obj) : betting_market_group_obj(betting_market_group_obj) {}
};
typedef msm::back::state_machine<betting_market_group_state_machine_> betting_market_group_state_machine;
} // end anonymous namespace
class betting_market_group_object::impl {
public:
betting_market_group_state_machine state_machine;
impl(betting_market_group_object* self) : state_machine(self) {}
};
betting_market_group_object::betting_market_group_object() :
my(new impl(this))
{
dlog("betting_market_group_object ctor");
}
betting_market_group_object::betting_market_group_object(const betting_market_group_object& rhs) :
graphene::db::abstract_object<betting_market_group_object>(rhs),
description(rhs.description),
event_id(rhs.event_id),
rules_id(rhs.rules_id),
asset_id(rhs.asset_id),
total_matched_bets_amount(rhs.total_matched_bets_amount),
never_in_play(rhs.never_in_play),
delay_before_settling(rhs.delay_before_settling),
settling_time(rhs.settling_time),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_group_obj = this;
}
betting_market_group_object& betting_market_group_object::operator=(const betting_market_group_object& rhs)
{
//graphene::db::abstract_object<betting_market_group_object>::operator=(rhs);
id = rhs.id;
description = rhs.description;
event_id = rhs.event_id;
rules_id = rhs.rules_id;
asset_id = rhs.asset_id;
total_matched_bets_amount = rhs.total_matched_bets_amount;
never_in_play = rhs.never_in_play;
delay_before_settling = rhs.delay_before_settling;
settling_time = rhs.settling_time;
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_group_obj = this;
return *this;
}
betting_market_group_object::~betting_market_group_object()
{
}
namespace {
bool verify_betting_market_group_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<betting_market_group_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<betting_market_group_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<betting_market_group_state>::to_string((betting_market_group_state)i);
if (!strstr(filled_state_names[i], fc_reflected_value_name))
{
fc_elog(fc::logger::get("default"),
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
++error_count;
}
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("default"),
"Error, no reflection for value ${int_value} in enum betting_market_group_status",
("int_value", i));
++error_count;
}
}
if (error_count == 0)
dlog("Betting market group status constants are correct");
else
wlog("There were ${count} errors in the betting market group status constants", ("count", error_count));
return error_count == 0;
}
} // end anonymous namespace
betting_market_group_status betting_market_group_object::get_status() const
{
static bool state_constants_are_correct = verify_betting_market_group_status_constants();
(void)&state_constants_are_correct;
betting_market_group_state state = (betting_market_group_state)my->state_machine.current_state()[0];
ddump((state));
switch (state)
{
case betting_market_group_state::upcoming:
return betting_market_group_status::upcoming;
case betting_market_group_state::frozen_upcoming:
return betting_market_group_status::frozen;
case betting_market_group_state::in_play:
return betting_market_group_status::in_play;
case betting_market_group_state::frozen_in_play:
return betting_market_group_status::frozen;
case betting_market_group_state::closed:
return betting_market_group_status::closed;
case betting_market_group_state::graded:
return betting_market_group_status::graded;
case betting_market_group_state::canceled:
return betting_market_group_status::canceled;
case betting_market_group_state::settled:
return betting_market_group_status::settled;
default:
FC_THROW("Unexpected betting market group state");
};
}
void betting_market_group_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void betting_market_group_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
void betting_market_group_object::on_upcoming_event(database& db)
{
my->state_machine.process_event(upcoming_event(db));
}
void betting_market_group_object::on_in_play_event(database& db)
{
my->state_machine.process_event(in_play_event(db));
}
void betting_market_group_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void betting_market_group_object::on_closed_event(database& db, bool closed_by_event)
{
my->state_machine.process_event(closed_event(db, closed_by_event));
}
void betting_market_group_object::on_graded_event(database& db)
{
my->state_machine.process_event(graded_event(db));
}
void betting_market_group_object::on_re_grading_event(database& db)
{
my->state_machine.process_event(re_grading_event(db));
}
void betting_market_group_object::on_settled_event(database& db)
{
my->state_machine.process_event(settled_event(db));
}
void betting_market_group_object::on_canceled_event(database& db, bool canceled_by_event)
{
my->state_machine.process_event(canceled_event(db, canceled_by_event));
}
// These are the only statuses that can be explicitly set by witness operations.
// Other states can only be reached indirectly (i.e., settling happens a fixed
// delay after grading)
void betting_market_group_object::dispatch_new_status(database& db, betting_market_group_status new_status)
{
switch (new_status) {
case betting_market_group_status::upcoming: // by witnesses to unfreeze a bmg
on_upcoming_event(db);
break;
case betting_market_group_status::in_play: // by witnesses to make a bmg in-play
on_in_play_event(db);
break;
case betting_market_group_status::closed: // by witnesses to close a bmg
on_closed_event(db, false);
break;
case betting_market_group_status::frozen: // by witnesses to freeze a bmg
on_frozen_event(db);
break;
case betting_market_group_status::canceled: // by witnesses to cancel a bmg
on_canceled_event(db, false);
break;
default:
FC_THROW("The status ${new_status} cannot be set directly", ("new_status", new_status));
}
}
} } // graphene::chain
namespace fc {
// Manually reflect betting_market_group_object to variant to properly reflect "state"
void to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", betting_market_group_obj.id)
("description", betting_market_group_obj.description)
("event_id", betting_market_group_obj.event_id)
("rules_id", betting_market_group_obj.rules_id)
("asset_id", betting_market_group_obj.asset_id)
("total_matched_bets_amount", betting_market_group_obj.total_matched_bets_amount)
("never_in_play", betting_market_group_obj.never_in_play)
("delay_before_settling", betting_market_group_obj.delay_before_settling)
("settling_time", betting_market_group_obj.settling_time)
("status", betting_market_group_obj.get_status());
v = o;
}
// Manually reflect betting_market_group_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj)
{
betting_market_group_obj.id = v["id"].as<graphene::chain::betting_market_group_id_type>();
betting_market_group_obj.description = v["description"].as<graphene::chain::internationalized_string_type>();
betting_market_group_obj.event_id = v["event_id"].as<graphene::chain::event_id_type>();
betting_market_group_obj.asset_id = v["asset_id"].as<graphene::chain::asset_id_type>();
betting_market_group_obj.total_matched_bets_amount = v["total_matched_bets_amount"].as<graphene::chain::share_type>();
betting_market_group_obj.never_in_play = v["never_in_play"].as<bool>();
betting_market_group_obj.delay_before_settling = v["delay_before_settling"].as<uint32_t>();
betting_market_group_obj.settling_time = v["settling_time"].as<fc::optional<fc::time_point_sec>>();
graphene::chain::betting_market_group_status status = v["status"].as<graphene::chain::betting_market_group_status>();
const_cast<int*>(betting_market_group_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -0,0 +1,450 @@
#define DEFAULT_LOGGER "betting"
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/database.hpp>
#include <boost/math/common_factor_rt.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
namespace graphene { namespace chain {
enum class betting_market_state {
unresolved,
frozen,
closed,
graded,
canceled,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::betting_market_state,
(unresolved)
(frozen)
(closed)
(graded)
(canceled)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
/* static */ share_type bet_object::get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up /* = false */)
{
fc::uint128_t amount_to_match_128 = bet_amount.value;
if (back_or_lay == bet_type::back)
{
amount_to_match_128 *= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION;
if (round_up)
amount_to_match_128 += GRAPHENE_BETTING_ODDS_PRECISION - 1;
amount_to_match_128 /= GRAPHENE_BETTING_ODDS_PRECISION;
}
else
{
amount_to_match_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
if (round_up)
amount_to_match_128 += backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION - 1;
amount_to_match_128 /= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION;
}
return amount_to_match_128.to_uint64();
}
share_type bet_object::get_approximate_matching_amount(bool round_up /* = false */) const
{
return get_approximate_matching_amount(amount_to_bet.amount, backer_multiplier, back_or_lay, round_up);
}
/* static */ share_type bet_object::get_exact_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay)
{
share_type back_ratio;
share_type lay_ratio;
std::tie(back_ratio, lay_ratio) = get_ratio(backer_multiplier);
if (back_or_lay == bet_type::back)
return bet_amount / back_ratio * lay_ratio;
else
return bet_amount / lay_ratio * back_ratio;
}
share_type bet_object::get_exact_matching_amount() const
{
return get_exact_matching_amount(amount_to_bet.amount, backer_multiplier, back_or_lay);
}
/* static */ std::pair<share_type, share_type> bet_object::get_ratio(bet_multiplier_type backer_multiplier)
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return std::make_pair(GRAPHENE_BETTING_ODDS_PRECISION / gcd, (backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd);
}
std::pair<share_type, share_type> bet_object::get_ratio() const
{
return get_ratio(backer_multiplier);
}
share_type bet_object::get_minimum_matchable_amount() const
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return (back_or_lay == bet_type::back ? GRAPHENE_BETTING_ODDS_PRECISION : backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd;
}
share_type bet_object::get_minimum_matching_amount() const
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return (back_or_lay == bet_type::lay ? GRAPHENE_BETTING_ODDS_PRECISION : backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd;
}
share_type betting_market_position_object::reduce()
{
share_type additional_not_cancel_balance = std::min(pay_if_payout_condition, pay_if_not_payout_condition);
if (additional_not_cancel_balance == 0)
return 0;
pay_if_payout_condition -= additional_not_cancel_balance;
pay_if_not_payout_condition -= additional_not_cancel_balance;
pay_if_not_canceled += additional_not_cancel_balance;
share_type immediate_winnings = std::min(pay_if_canceled, pay_if_not_canceled);
if (immediate_winnings == 0)
return 0;
pay_if_canceled -= immediate_winnings;
pay_if_not_canceled -= immediate_winnings;
return immediate_winnings;
}
// betting market object implementation
namespace
{
// Events -- most events happen when the witnesses publish an update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct unresolved_event
{
database& db;
unresolved_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct closed_event
{
database& db;
closed_event(database& db) : db(db) {}
};
struct graded_event
{
database& db;
betting_market_resolution_type new_grading;
graded_event(database& db, betting_market_resolution_type new_grading) : db(db), new_grading(new_grading) {}
};
struct settled_event
{
database& db;
settled_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
canceled_event(database& db) : db(db) {}
};
// Events
struct betting_market_state_machine_ : public msm::front::state_machine_def<betting_market_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct unresolved : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> unresolved", ("id", fsm.betting_market_obj->id));
}
};
struct frozen : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> frozen", ("id", fsm.betting_market_obj->id));
}
};
struct closed : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> closed", ("id", fsm.betting_market_obj->id));
}
};
struct graded : public msm::front::state<>{
void on_entry(const graded_event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> graded", ("id", fsm.betting_market_obj->id));
fsm.betting_market_obj->resolution = event.new_grading;
}
};
struct settled : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> settled", ("id", fsm.betting_market_obj->id));
}
};
struct canceled : public msm::front::state<>{
void on_entry(const canceled_event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> canceled", ("id", fsm.betting_market_obj->id));
fsm.betting_market_obj->resolution = betting_market_resolution_type::cancel;
}
};
typedef unresolved initial_state;
typedef betting_market_state_machine_ x; // makes transition table cleaner
// Transition table for betting market
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < unresolved, frozen_event, frozen >,
_row < unresolved, closed_event, closed >,
_row < unresolved, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < frozen, unresolved_event, unresolved >,
_row < frozen, closed_event, closed >,
_row < frozen, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < closed, graded_event, graded >,
_row < closed, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < graded, settled_event, settled >,
_row < graded, canceled_event, canceled >
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
betting_market_object* betting_market_obj;
betting_market_state_machine_(betting_market_object* betting_market_obj) : betting_market_obj(betting_market_obj) {}
};
typedef msm::back::state_machine<betting_market_state_machine_> betting_market_state_machine;
} // end anonymous namespace
class betting_market_object::impl {
public:
betting_market_state_machine state_machine;
impl(betting_market_object* self) : state_machine(self) {}
};
betting_market_object::betting_market_object() :
my(new impl(this))
{
}
betting_market_object::betting_market_object(const betting_market_object& rhs) :
graphene::db::abstract_object<betting_market_object>(rhs),
group_id(rhs.group_id),
description(rhs.description),
payout_condition(rhs.payout_condition),
resolution(rhs.resolution),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_obj = this;
}
betting_market_object& betting_market_object::operator=(const betting_market_object& rhs)
{
//graphene::db::abstract_object<betting_market_object>::operator=(rhs);
id = rhs.id;
group_id = rhs.group_id;
description = rhs.description;
payout_condition = rhs.payout_condition;
resolution = rhs.resolution;
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_obj = this;
return *this;
}
betting_market_object::~betting_market_object()
{
}
namespace {
bool verify_betting_market_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<betting_market_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<betting_market_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<betting_market_state>::to_string((betting_market_state)i);
if (!strstr(filled_state_names[i], fc_reflected_value_name))
{
fc_elog(fc::logger::get("default"),
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: "
"boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
++error_count;
}
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("default"),
"Error, no reflection for value ${int_value} in enum betting_market_status",
("int_value", i));
++error_count;
}
}
if (error_count == 0)
dlog("Betting market status constants are correct");
else
wlog("There were ${count} errors in the betting market status constants", ("count", error_count));
return error_count == 0;
}
} // end anonymous namespace
betting_market_status betting_market_object::get_status() const
{
static bool state_constants_are_correct = verify_betting_market_status_constants();
(void)&state_constants_are_correct;
betting_market_state state = (betting_market_state)my->state_machine.current_state()[0];
edump((state));
switch (state)
{
case betting_market_state::unresolved:
return betting_market_status::unresolved;
case betting_market_state::frozen:
return betting_market_status::frozen;
case betting_market_state::closed:
return betting_market_status::unresolved;
case betting_market_state::canceled:
return betting_market_status::canceled;
case betting_market_state::graded:
return betting_market_status::graded;
case betting_market_state::settled:
return betting_market_status::settled;
default:
FC_THROW("Unexpected betting market state");
};
}
void betting_market_object::cancel_all_unmatched_bets(database& db) const
{
const auto& bet_odds_idx = db.get_index_type<bet_object_index>().indices().get<by_odds>();
// first, cancel all bets on the active books
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(id));
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(id));
while (book_itr != book_end)
{
auto old_book_itr = book_itr;
++book_itr;
db.cancel_bet(*old_book_itr, true);
}
// then, cancel any delayed bets on that market. We don't have an index for
// that, so walk through all delayed bets
book_itr = bet_odds_idx.begin();
while (book_itr != bet_odds_idx.end() &&
book_itr->end_of_delay)
{
auto old_book_itr = book_itr;
++book_itr;
if (old_book_itr->betting_market_id == id)
db.cancel_bet(*old_book_itr, true);
}
}
void betting_market_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void betting_market_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
void betting_market_object::on_unresolved_event(database& db)
{
my->state_machine.process_event(unresolved_event(db));
}
void betting_market_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void betting_market_object::on_closed_event(database& db)
{
my->state_machine.process_event(closed_event(db));
}
void betting_market_object::on_graded_event(database& db, betting_market_resolution_type new_grading)
{
my->state_machine.process_event(graded_event(db, new_grading));
}
void betting_market_object::on_settled_event(database& db)
{
my->state_machine.process_event(settled_event(db));
}
void betting_market_object::on_canceled_event(database& db)
{
my->state_machine.process_event(canceled_event(db));
}
} } // graphene::chain
namespace fc {
// Manually reflect betting_market_object to variant to properly reflect "state"
void to_variant(const graphene::chain::betting_market_object& event_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", event_obj.id)
("group_id", event_obj.group_id)
("description", event_obj.description)
("payout_condition", event_obj.payout_condition)
("resolution", event_obj.resolution)
("status", event_obj.get_status());
v = o;
}
// Manually reflect betting_market_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::betting_market_object& event_obj)
{
event_obj.id = v["id"].as<graphene::chain::betting_market_id_type>();
event_obj.group_id = v["name"].as<graphene::chain::betting_market_group_id_type>();
event_obj.description = v["description"].as<graphene::chain::internationalized_string_type>();
event_obj.payout_condition = v["payout_condition"].as<graphene::chain::internationalized_string_type>();
event_obj.resolution = v["resolution"].as<fc::optional<graphene::chain::betting_market_resolution_type>>();
graphene::chain::betting_market_status status = v["status"].as<graphene::chain::betting_market_status>();
const_cast<int*>(event_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -29,6 +29,8 @@
#include <graphene/chain/fba_accumulator_id.hpp>
#include <graphene/chain/hardfork.hpp>
#include <fc/smart_ref_impl.hpp>
namespace graphene { namespace chain {
void_result transfer_to_blind_evaluator::do_evaluate( const transfer_to_blind_operation& o )

View file

@ -23,6 +23,7 @@
*/
#include <fc/smart_ref_impl.hpp>
#include "db_balance.cpp"
#include "db_bet.cpp"
#include "db_block.cpp"
#include "db_debug.cpp"
#include "db_getter.cpp"
@ -32,3 +33,4 @@
#include "db_market.cpp"
#include "db_update.cpp"
#include "db_witness_schedule.cpp"
#include "db_notify.cpp"

613
libraries/chain/db_bet.cpp Normal file
View file

@ -0,0 +1,613 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/combine.hpp>
#include <boost/range/join.hpp>
#include <boost/tuple/tuple.hpp>
namespace graphene { namespace chain {
void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
{
asset amount_to_refund = bet.amount_to_bet;
//TODO: update global statistics
adjust_balance(bet.bettor_id, amount_to_refund);
if (create_virtual_op)
{
bet_canceled_operation bet_canceled_virtual_op(bet.bettor_id, bet.id,
bet.amount_to_bet);
//fc_idump(fc::logger::get("betting"), (bet_canceled_virtual_op));
push_applied_operation(std::move(bet_canceled_virtual_op));
}
remove(bet);
}
void database::cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market)
{
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
// first, cancel all bets on the active books
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(betting_market.id));
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(betting_market.id));
while (book_itr != book_end)
{
auto old_book_itr = book_itr;
++book_itr;
cancel_bet(*old_book_itr, true);
}
// then, cancel any delayed bets on that market. We don't have an index for
// that, so walk through all delayed bets
book_itr = bet_odds_idx.begin();
while (book_itr != bet_odds_idx.end() &&
book_itr->end_of_delay)
{
auto old_book_itr = book_itr;
++book_itr;
if (old_book_itr->betting_market_id == betting_market.id)
cancel_bet(*old_book_itr, true);
}
}
void database::validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
{
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(betting_market_group.id));
// we must have one resolution for each betting market
FC_ASSERT(resolutions.size() == boost::size(betting_markets_in_group),
"You must publish resolutions for all ${size} markets in the group, you published ${published}", ("size", boost::size(betting_markets_in_group))("published", resolutions.size()));
// both are sorted by id, we can walk through both and verify that they match
unsigned number_of_wins = 0;
unsigned number_of_cancels = 0;
for (const auto& zipped : boost::combine(resolutions, betting_markets_in_group))
{
const auto& resolution = boost::get<0>(zipped);
const auto& betting_market = boost::get<1>(zipped);
FC_ASSERT(resolution.first == betting_market.id, "Missing resolution for betting market ${id}", ("id", betting_market.id));
if (resolution.second == betting_market_resolution_type::cancel)
++number_of_cancels;
else if (resolution.second == betting_market_resolution_type::win)
++number_of_wins;
else
FC_ASSERT(resolution.second == betting_market_resolution_type::not_win);
}
if (number_of_cancels != 0)
FC_ASSERT(number_of_cancels == resolutions.size(), "You must cancel all betting markets or none of the betting markets in the group");
else
FC_ASSERT(number_of_wins == 1, "There must be exactly one winning market");
}
void database::cancel_all_unmatched_bets_on_betting_market_group(const betting_market_group_object& betting_market_group)
{
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
{
const betting_market_object& betting_market = *betting_market_itr;
++betting_market_itr;
cancel_all_unmatched_bets_on_betting_market(betting_market);
}
}
void database::resolve_betting_market_group(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
{
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(betting_market_group.id));
bool group_was_canceled = resolutions.begin()->second == betting_market_resolution_type::cancel;
if (group_was_canceled)
modify(betting_market_group, [group_was_canceled,this](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_canceled_event(*this, false); // this cancels the betting markets
});
else {
// TODO: this should be pushed into the bmg's on_graded_event
// both are sorted by id, we can walk through both and verify that they match
for (const auto& zipped : boost::combine(resolutions, betting_markets_in_group))
{
const auto& resolution = boost::get<0>(zipped);
const auto& betting_market = boost::get<1>(zipped);
modify(betting_market, [this,&resolution](betting_market_object& betting_market_obj) {
betting_market_obj.on_graded_event(*this, resolution.second);
});
}
modify(betting_market_group, [group_was_canceled,this](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_graded_event(*this);
});
}
}
void database::settle_betting_market_group(const betting_market_group_object& betting_market_group)
{
fc_ilog(fc::logger::get("betting"), "Settling betting market group ${id}", ("id", betting_market_group.id));
// we pay the rake fee to the dividend distribution account for the core asset, go ahead
// and look up that account now
fc::optional<account_id_type> rake_account_id;
const asset_object& core_asset_obj = asset_id_type(0)(*this);
if (core_asset_obj.dividend_data_id)
{
const asset_dividend_data_object& core_asset_dividend_data_obj = (*core_asset_obj.dividend_data_id)(*this);
rake_account_id = core_asset_dividend_data_obj.dividend_distribution_account;
}
// collect the resolutions of all markets in the BMG: they were previously published and
// stored in the individual betting markets
std::map<betting_market_id_type, betting_market_resolution_type> resolutions_by_market_id;
// collecting bettors and their positions
std::map<account_id_type, std::vector<const betting_market_position_object*> > bettor_positions_map;
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
// [ROL] it seems to be my mistake - wrong index used
//auto& position_index = get_index_type<betting_market_position_index>().indices().get<by_bettor_betting_market>();
auto& position_index = get_index_type<betting_market_position_index>().indices().get<by_betting_market_bettor>();
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
{
const betting_market_object& betting_market = *betting_market_itr;
FC_ASSERT(betting_market_itr->resolution, "Unexpected error settling betting market ${market_id}: no published resolution",
("market_id", betting_market_itr->id));
resolutions_by_market_id.emplace(betting_market.id, *betting_market_itr->resolution);
++betting_market_itr;
cancel_all_unmatched_bets_on_betting_market(betting_market);
auto position_itr = position_index.lower_bound(betting_market.id);
while (position_itr != position_index.end() && position_itr->betting_market_id == betting_market.id)
{
const betting_market_position_object& position = *position_itr;
++position_itr;
bettor_positions_map[position.bettor_id].push_back(&position);
}
}
// walking through bettors' positions and collecting winings and fees respecting asset_id
for (const auto& bettor_positions_pair: bettor_positions_map)
{
uint16_t rake_fee_percentage = get_global_properties().parameters.betting_rake_fee_percentage;
share_type net_profits;
share_type payout_amounts;
account_id_type bettor_id = bettor_positions_pair.first;
const std::vector<const betting_market_position_object*>& bettor_positions = bettor_positions_pair.second;
for (const betting_market_position_object* position : bettor_positions)
{
betting_market_resolution_type resolution;
try
{
resolution = resolutions_by_market_id.at(position->betting_market_id);
}
catch (std::out_of_range&)
{
FC_THROW_EXCEPTION(fc::key_not_found_exception, "Unexpected betting market ID, shouldn't happen");
}
///if (cancel)
/// resolution = betting_market_resolution_type::cancel;
///else
///{
/// // checked in evaluator, should never happen, see above
/// assert(resolutions.count(position->betting_market_id));
/// resolution = resolutions.at(position->betting_market_id);
///}
switch (resolution)
{
case betting_market_resolution_type::win:
{
share_type total_payout = position->pay_if_payout_condition + position->pay_if_not_canceled;
payout_amounts += total_payout;
net_profits += total_payout - position->pay_if_canceled;
break;
}
case betting_market_resolution_type::not_win:
{
share_type total_payout = position->pay_if_not_payout_condition + position->pay_if_not_canceled;
payout_amounts += total_payout;
net_profits += total_payout - position->pay_if_canceled;
break;
}
case betting_market_resolution_type::cancel:
payout_amounts += position->pay_if_canceled;
break;
default:
continue;
}
remove(*position);
}
// pay the fees to the dividend-distribution account if net profit
share_type rake_amount;
if (net_profits.value > 0 && rake_account_id)
{
rake_amount = ((fc::uint128_t(net_profits.value) * rake_fee_percentage + GRAPHENE_100_PERCENT - 1) / GRAPHENE_100_PERCENT).to_uint64();
if (rake_amount.value)
adjust_balance(*rake_account_id, asset(rake_amount, betting_market_group.asset_id));
}
// pay winning - rake
adjust_balance(bettor_id, asset(payout_amounts - rake_amount, betting_market_group.asset_id));
// [ROL]
//fc_idump(fc::logger::get("betting"), (payout_amounts)(net_profits.value)(rake_amount.value));
push_applied_operation(betting_market_group_resolved_operation(bettor_id,
betting_market_group.id,
resolutions_by_market_id,
payout_amounts,
rake_amount));
}
// At this point, the betting market group will either be in the "graded" or "canceled" state,
// if it was graded, mark it as settled. if it's canceled, let it remain canceled.
bool was_canceled = betting_market_group.get_status() == betting_market_group_status::canceled;
if (!was_canceled)
modify(betting_market_group, [&](betting_market_group_object& group) {
group.on_settled_event(*this);
});
betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id) {
const betting_market_object& betting_market = *betting_market_itr;
++betting_market_itr;
fc_dlog(fc::logger::get("betting"), "removing betting market ${id}", ("id", betting_market.id));
remove(betting_market);
}
const event_object& event = betting_market_group.event_id(*this);
fc_dlog(fc::logger::get("betting"), "removing betting market group ${id}", ("id", betting_market_group.id));
remove(betting_market_group);
}
void database::remove_completed_events()
{
const auto& event_index = get_index_type<event_object_index>().indices().get<by_event_status>();
auto canceled_event_iter = event_index.lower_bound(event_status::canceled);
while (canceled_event_iter != event_index.end() && canceled_event_iter->get_status() == event_status::canceled)
{
const event_object& event = *canceled_event_iter;
++canceled_event_iter;
fc_dlog(fc::logger::get("betting"), "removing canceled event ${id}", ("id", event.id));
remove(event);
}
auto settled_event_iter = event_index.lower_bound(event_status::settled);
while (settled_event_iter != event_index.end() && settled_event_iter->get_status() == event_status::settled)
{
const event_object& event = *settled_event_iter;
++settled_event_iter;
fc_dlog(fc::logger::get("betting"), "removing settled event ${id}", ("id", event.id));
remove(event);
}
}
share_type adjust_betting_position(database& db,
account_id_type bettor_id,
betting_market_id_type betting_market_id,
bet_type back_or_lay,
share_type bet_amount,
share_type matched_amount)
{ try {
assert(bet_amount >= 0);
share_type guaranteed_winnings_returned = 0;
if (bet_amount == 0)
return guaranteed_winnings_returned;
auto& index = db.get_index_type<betting_market_position_index>().indices().get<by_bettor_betting_market>();
auto itr = index.find(boost::make_tuple(bettor_id, betting_market_id));
if (itr == index.end())
{
db.create<betting_market_position_object>([&](betting_market_position_object& position) {
position.bettor_id = bettor_id;
position.betting_market_id = betting_market_id;
position.pay_if_payout_condition = back_or_lay == bet_type::back ? bet_amount + matched_amount : 0;
position.pay_if_not_payout_condition = back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0;
position.pay_if_canceled = bet_amount;
position.pay_if_not_canceled = 0;
// this should not be reducible
});
} else {
db.modify(*itr, [&](betting_market_position_object& position) {
assert(position.bettor_id == bettor_id);
assert(position.betting_market_id == betting_market_id);
position.pay_if_payout_condition += back_or_lay == bet_type::back ? bet_amount + matched_amount : 0;
position.pay_if_not_payout_condition += back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0;
position.pay_if_canceled += bet_amount;
guaranteed_winnings_returned = position.reduce();
});
}
return guaranteed_winnings_returned;
} FC_CAPTURE_AND_RETHROW((bettor_id)(betting_market_id)(bet_amount)) }
// called twice when a bet is matched, once for the taker, once for the maker
bool bet_was_matched(database& db, const bet_object& bet,
share_type amount_bet, share_type amount_matched,
bet_multiplier_type actual_multiplier,
bool refund_unmatched_portion)
{
// record their bet, modifying their position, and return any winnings
share_type guaranteed_winnings_returned = adjust_betting_position(db, bet.bettor_id, bet.betting_market_id,
bet.back_or_lay, amount_bet, amount_matched);
db.adjust_balance(bet.bettor_id, asset(guaranteed_winnings_returned, bet.amount_to_bet.asset_id));
// generate a virtual "match" op
asset asset_amount_bet(amount_bet, bet.amount_to_bet.asset_id);
bet_matched_operation bet_matched_virtual_op(bet.bettor_id, bet.id,
asset_amount_bet,
actual_multiplier,
guaranteed_winnings_returned);
//fc_edump(fc::logger::get("betting"), (bet_matched_virtual_op));
db.push_applied_operation(std::move(bet_matched_virtual_op));
// update the bet on the books
if (asset_amount_bet == bet.amount_to_bet)
{
db.remove(bet);
return true;
}
else
{
db.modify(bet, [&](bet_object& bet_obj) {
bet_obj.amount_to_bet -= asset_amount_bet;
});
if (refund_unmatched_portion)
{
db.cancel_bet(bet);
return true;
}
else
return false;
}
}
/**
* Matches the two orders,
*
* @return a bit field indicating which orders were filled (and thus removed)
*
* 0 - no bet was matched (this will never happen)
* 1 - taker_bet was filled and removed from the books
* 2 - maker_bet was filled and removed from the books
* 3 - both were filled and removed from the books
*/
int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker_bet )
{
//fc_idump(fc::logger::get("betting"), (taker_bet)(maker_bet));
assert(taker_bet.amount_to_bet.asset_id == maker_bet.amount_to_bet.asset_id);
assert(taker_bet.amount_to_bet.amount > 0 && maker_bet.amount_to_bet.amount > 0);
assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier :
taker_bet.backer_multiplier >= maker_bet.backer_multiplier);
assert(taker_bet.back_or_lay != maker_bet.back_or_lay);
int result = 0;
//fc_idump(fc::logger::get("betting"), (taker_bet)(maker_bet));
// using the maker's odds, figure out how much of the maker's bet we would match, rounding down
// go ahead and get look up the ratio for the bet (a bet with odds 1.92 will have a ratio 25:23)
share_type back_odds_ratio;
share_type lay_odds_ratio;
std::tie(back_odds_ratio, lay_odds_ratio) = maker_bet.get_ratio();
// and make some shortcuts to get to the maker's and taker's side of the ratio
const share_type& maker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? back_odds_ratio : lay_odds_ratio;
const share_type& taker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? lay_odds_ratio : back_odds_ratio;
// we need to figure out how much of the bet matches. the smallest amount
// that could match is one maker_odds_ratio to one taker_odds_ratio,
// but we can match any integer multiple of that ratio (called the 'factor' below),
// limited only by the bet amounts.
//
//fc_idump(fc::logger::get("betting"), (back_odds_ratio)(lay_odds_ratio));
//fc_idump(fc::logger::get("betting"), (maker_odds_ratio)(taker_odds_ratio));
// now figure out how much of the maker bet we'll consume. We don't yet know whether the maker or taker
// will be the limiting factor.
share_type maximum_factor_taker_is_willing_to_pay = taker_bet.amount_to_bet.amount / taker_odds_ratio;
share_type maximum_taker_factor = maximum_factor_taker_is_willing_to_pay;
if (taker_bet.back_or_lay == bet_type::lay) {
share_type maximum_factor_taker_is_willing_to_receive = taker_bet.get_exact_matching_amount() / maker_odds_ratio;
//fc_idump(fc::logger::get("betting"), (maximum_factor_taker_is_willing_to_pay));
bool taker_was_limited_by_matching_amount = maximum_factor_taker_is_willing_to_receive < maximum_factor_taker_is_willing_to_pay;
if (taker_was_limited_by_matching_amount)
maximum_taker_factor = maximum_factor_taker_is_willing_to_receive;
}
//fc_idump(fc::logger::get("betting"), (maximum_factor_taker_is_willing_to_pay)(maximum_taker_factor));
share_type maximum_maker_factor = maker_bet.amount_to_bet.amount / maker_odds_ratio;
share_type maximum_factor = std::min(maximum_taker_factor, maximum_maker_factor);
share_type maker_amount_to_match = maximum_factor * maker_odds_ratio;
share_type taker_amount_to_match = maximum_factor * taker_odds_ratio;
fc_idump(fc::logger::get("betting"), (maker_amount_to_match)(taker_amount_to_match));
// TODO: analyze whether maximum_maker_amount_to_match can ever be zero here
assert(maker_amount_to_match != 0);
if (maker_amount_to_match == 0)
return 0;
#ifndef NDEBUG
assert(taker_amount_to_match <= taker_bet.amount_to_bet.amount);
assert(taker_amount_to_match / taker_odds_ratio * taker_odds_ratio == taker_amount_to_match);
{
// verify we're getting the odds we expect
fc::uint128_t payout_128 = maker_amount_to_match.value;
payout_128 += taker_amount_to_match.value;
payout_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
payout_128 /= maker_bet.back_or_lay == bet_type::back ? maker_amount_to_match.value : taker_amount_to_match.value;
assert(payout_128.to_uint64() == maker_bet.backer_multiplier);
}
#endif
//fc_idump(fc::logger::get("betting"), (taker_amount_to_match)(maker_amount_to_match));
// maker bets will always be an exact multiple of maker_odds_ratio, so they will either completely match or remain on the books
bool maker_bet_will_completely_match = maker_amount_to_match == maker_bet.amount_to_bet.amount;
if (maker_bet_will_completely_match && taker_amount_to_match != taker_bet.amount_to_bet.amount)
{
// then the taker bet will stay on the books. If the taker odds != the maker odds, we will
// need to refund the stake the taker was expecting to pay but didn't.
// compute how much of the taker's bet should still be left on the books and how much
// the taker should pay for the remaining amount; refund any amount that won't remain
// on the books and isn't used to pay the bet we're currently matching.
share_type takers_odds_back_odds_ratio;
share_type takers_odds_lay_odds_ratio;
std::tie(takers_odds_back_odds_ratio, takers_odds_lay_odds_ratio) = taker_bet.get_ratio();
const share_type& takers_odds_taker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_back_odds_ratio : takers_odds_lay_odds_ratio;
const share_type& takers_odds_maker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_lay_odds_ratio : takers_odds_back_odds_ratio;
share_type taker_refund_amount;
if (taker_bet.back_or_lay == bet_type::back)
{
// because we matched at the maker's odds and not the taker's odds, the remaining amount to match
// may not be an even multiple of the taker's odds; round it down.
share_type taker_remaining_factor = (taker_bet.amount_to_bet.amount - taker_amount_to_match) / takers_odds_taker_odds_ratio;
share_type taker_remaining_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio;
taker_refund_amount = taker_bet.amount_to_bet.amount - taker_amount_to_match - taker_remaining_bet_amount;
//idump((taker_remaining_factor)(taker_remaining_bet_amount)(taker_refund_amount));
}
else
{
// the taker bet is a lay bet. because we matched at the maker's odds and not the taker's odds,
// there are two things we need to take into account. First, we may have achieved more of a position
// than we expected had we matched at our taker odds. If so, we can refund the unused stake.
// Second, the remaining amount to match may not be an even multiple of the taker's odds; round it down.
share_type unrounded_taker_remaining_amount_to_match = taker_bet.get_exact_matching_amount() - maker_amount_to_match;
//idump((unrounded_taker_remaining_amount_to_match));
// because we matched at the maker's odds and not the taker's odds, the remaining amount to match
// may not be an even multiple of the taker's odds; round it down.
share_type taker_remaining_factor = unrounded_taker_remaining_amount_to_match / takers_odds_maker_odds_ratio;
share_type taker_remaining_maker_amount_to_match = taker_remaining_factor * takers_odds_maker_odds_ratio;
share_type taker_remaining_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio;
taker_refund_amount = taker_bet.amount_to_bet.amount - taker_amount_to_match - taker_remaining_bet_amount;
//idump((taker_remaining_factor)(taker_remaining_maker_amount_to_match)(taker_remaining_bet_amount)(taker_refund_amount));
}
if (taker_refund_amount > share_type())
{
db.modify(taker_bet, [&taker_refund_amount](bet_object& taker_bet_object) {
taker_bet_object.amount_to_bet.amount -= taker_refund_amount;
});
fc_dlog(fc::logger::get("betting"), "Refunding ${taker_refund_amount} to taker because we matched at the maker's odds of "
"${maker_odds} instead of the taker's odds ${taker_odds}",
("taker_refund_amount", taker_refund_amount)
("maker_odds", maker_bet.backer_multiplier)
("taker_odds", taker_bet.backer_multiplier));
fc_ddump(fc::logger::get("betting"), (taker_bet));
db.adjust_balance(taker_bet.bettor_id, asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
// TODO: update global statistics
bet_adjusted_operation bet_adjusted_op(taker_bet.bettor_id, taker_bet.id,
asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
db.push_applied_operation(std::move(bet_adjusted_op));
}
}
// if the maker bet stays on the books, we need to make sure the taker bet is removed from the books (either it fills completely,
// or any un-filled amount is canceled)
result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier, !maker_bet_will_completely_match);
result |= bet_was_matched(db, maker_bet, maker_amount_to_match, taker_amount_to_match, maker_bet.backer_multiplier, false) << 1;
assert(result != 0);
return result;
}
// called from the bet_place_evaluator
bool database::place_bet(const bet_object& new_bet_object)
{
// We allow users to place bets for any amount, but only amounts that are exact multiples of the odds
// ratio can be matched. Immediately return any unmatchable amount in this bet.
share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount();
share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount;
share_type rounded_bet_amount = scale_factor * minimum_matchable_amount;
if (rounded_bet_amount == share_type())
{
// the bet was too small to match at all, cancel the bet
cancel_bet(new_bet_object, true);
return true;
}
else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount)
{
asset stake_returned = new_bet_object.amount_to_bet;
stake_returned.amount -= rounded_bet_amount;
modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) {
modified_bet_object.amount_to_bet.amount = rounded_bet_amount;
});
adjust_balance(new_bet_object.bettor_id, stake_returned);
// TODO: update global statistics
bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id,
stake_returned);
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
push_applied_operation(std::move(bet_adjusted_op));
fc_dlog(fc::logger::get("betting"), "Refunded ${refund_amount} to round the bet down to something that can match exactly, new bet: ${new_bet}",
("refund_amount", stake_returned.amount)
("new_bet", new_bet_object));
}
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
bet_type bet_type_to_match = new_bet_object.back_or_lay == bet_type::back ? bet_type::lay : bet_type::back;
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match));
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match, new_bet_object.backer_multiplier));
// fc_ilog(fc::logger::get("betting"), "");
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
// for (auto itr = book_itr; itr != book_end; ++itr)
// fc_idump(fc::logger::get("betting"), (*itr));
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
int orders_matched_flags = 0;
bool finished = false;
while (!finished && book_itr != book_end)
{
auto old_book_itr = book_itr;
++book_itr;
orders_matched_flags = match_bet(*this, new_bet_object, *old_book_itr);
// we continue if the maker bet was completely consumed AND the taker bet was not
finished = orders_matched_flags != 2;
}
if (!(orders_matched_flags & 1))
fc_ddump(fc::logger::get("betting"), (new_bet_object));
// return true if the taker bet was completely consumed
return (orders_matched_flags & 1) != 0;
}
} }

View file

@ -29,6 +29,7 @@
#include <graphene/chain/block_summary_object.hpp>
#include <graphene/chain/global_property_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/transaction_object.hpp>
#include <graphene/chain/witness_object.hpp>
@ -241,7 +242,7 @@ processed_transaction database::_push_transaction( const signed_transaction& trx
auto processed_trx = _apply_transaction( trx );
_pending_tx.push_back(processed_trx);
notify_changed_objects();
// notify_changed_objects();
// The transaction applied successfully. Merge its changes into the pending block session.
temp_session.merge();
@ -285,13 +286,13 @@ processed_transaction database::push_proposal(const proposal_object& proposal)
{
_applied_ops.resize( old_applied_ops_size );
}
elog( "e", ("e",e.to_detail_string() ) );
edump((e));
throw;
}
ptrx.operation_results = std::move(eval_state.operation_results);
return ptrx;
} FC_CAPTURE_AND_RETHROW( (proposal) ) }
} FC_CAPTURE_AND_RETHROW() }
signed_block database::generate_block(
fc::time_point_sec when,
@ -478,6 +479,10 @@ const vector<optional< operation_history_object > >& database::get_applied_opera
return _applied_ops;
}
vector<optional< operation_history_object > >& database::get_applied_operations()
{
return _applied_ops;
}
//////////////////// private methods ////////////////////
void database::apply_block( const signed_block& next_block, uint32_t skip )
@ -539,12 +544,14 @@ void database::_apply_block( const signed_block& next_block )
perform_chain_maintenance(next_block, global_props);
create_block_summary(next_block);
place_delayed_bets(); // must happen after update_global_dynamic_data() updates the time
clear_expired_transactions();
clear_expired_proposals();
clear_expired_orders();
update_expired_feeds();
update_withdraw_permissions();
update_tournaments();
update_betting_markets(next_block.timestamp);
// n.b., update_maintenance_flag() happens this late
// because get_slot_time() / get_slot_at_time() is needed above
@ -562,27 +569,9 @@ void database::_apply_block( const signed_block& next_block )
_applied_ops.clear();
notify_changed_objects();
} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) }
void database::notify_changed_objects()
{ try {
if( _undo_db.enabled() )
{
const auto& head_undo = _undo_db.head();
vector<object_id_type> changed_ids; changed_ids.reserve(head_undo.old_values.size());
for( const auto& item : head_undo.old_values ) changed_ids.push_back(item.first);
for( const auto& item : head_undo.new_ids ) changed_ids.push_back(item);
vector<const object*> removed;
removed.reserve( head_undo.removed.size() );
for( const auto& item : head_undo.removed )
{
changed_ids.push_back( item.first );
removed.emplace_back( item.second.get() );
}
changed_objects(changed_ids);
}
} FC_CAPTURE_AND_RETHROW() }
processed_transaction database::apply_transaction(const signed_transaction& trx, uint32_t skip)
{

View file

@ -47,7 +47,7 @@ const chain_property_object& database::get_chain_properties()const
return get( chain_property_id_type() );
}
const dynamic_global_property_object&database::get_dynamic_global_properties() const
const dynamic_global_property_object& database::get_dynamic_global_properties() const
{
return get( dynamic_global_property_id_type() );
}

View file

@ -50,6 +50,13 @@
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/global_betting_statistics_object.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/asset_evaluator.hpp>
#include <graphene/chain/assert_evaluator.hpp>
@ -64,6 +71,10 @@
#include <graphene/chain/withdraw_permission_evaluator.hpp>
#include <graphene/chain/witness_evaluator.hpp>
#include <graphene/chain/worker_evaluator.hpp>
#include <graphene/chain/sport_evaluator.hpp>
#include <graphene/chain/event_group_evaluator.hpp>
#include <graphene/chain/event_evaluator.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
#include <graphene/chain/tournament_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -130,6 +141,33 @@ const uint8_t witness_object::type_id;
const uint8_t worker_object::space_id;
const uint8_t worker_object::type_id;
const uint8_t sport_object::space_id;
const uint8_t sport_object::type_id;
const uint8_t event_group_object::space_id;
const uint8_t event_group_object::type_id;
const uint8_t event_object::space_id;
const uint8_t event_object::type_id;
const uint8_t betting_market_rules_object::space_id;
const uint8_t betting_market_rules_object::type_id;
const uint8_t betting_market_group_object::space_id;
const uint8_t betting_market_group_object::type_id;
const uint8_t betting_market_object::space_id;
const uint8_t betting_market_object::type_id;
const uint8_t bet_object::space_id;
const uint8_t bet_object::type_id;
const uint8_t betting_market_position_object::space_id;
const uint8_t betting_market_position_object::type_id;
const uint8_t global_betting_statistics_object::space_id;
const uint8_t global_betting_statistics_object::type_id;
void database::initialize_evaluators()
{
@ -176,6 +214,23 @@ void database::initialize_evaluators()
register_evaluator<transfer_from_blind_evaluator>();
register_evaluator<blind_transfer_evaluator>();
register_evaluator<asset_claim_fees_evaluator>();
register_evaluator<sport_create_evaluator>();
register_evaluator<sport_update_evaluator>();
register_evaluator<event_group_create_evaluator>();
register_evaluator<event_group_update_evaluator>();
register_evaluator<event_create_evaluator>();
register_evaluator<event_update_evaluator>();
register_evaluator<event_update_status_evaluator>();
register_evaluator<betting_market_rules_create_evaluator>();
register_evaluator<betting_market_rules_update_evaluator>();
register_evaluator<betting_market_group_create_evaluator>();
register_evaluator<betting_market_group_update_evaluator>();
register_evaluator<betting_market_create_evaluator>();
register_evaluator<betting_market_update_evaluator>();
register_evaluator<bet_place_evaluator>();
register_evaluator<bet_cancel_evaluator>();
register_evaluator<betting_market_group_resolve_evaluator>();
register_evaluator<betting_market_group_cancel_unmatched_bets_evaluator>();
register_evaluator<tournament_create_evaluator>();
register_evaluator<tournament_join_evaluator>();
register_evaluator<game_move_evaluator>();
@ -208,6 +263,13 @@ void database::initialize_indexes()
add_index< primary_index<worker_index> >();
add_index< primary_index<balance_index> >();
add_index< primary_index<blinded_balance_index> >();
add_index< primary_index<sport_object_index > >();
add_index< primary_index<event_group_object_index > >();
add_index< primary_index<event_object_index > >();
add_index< primary_index<betting_market_rules_object_index > >();
add_index< primary_index<betting_market_group_object_index > >();
add_index< primary_index<betting_market_object_index > >();
add_index< primary_index<bet_object_index > >();
add_index< primary_index<tournament_index> >();
auto tournament_details_idx = add_index< primary_index<tournament_details_index> >();
@ -230,8 +292,11 @@ void database::initialize_indexes()
add_index< primary_index<simple_index<budget_record_object > > >();
add_index< primary_index< special_authority_index > >();
add_index< primary_index< buyback_index > >();
add_index< primary_index< simple_index< fba_accumulator_object > > >();
add_index< primary_index< betting_market_position_index > >();
add_index< primary_index< global_betting_statistics_object_index > >();
//add_index< primary_index<pending_dividend_payout_balance_object_index > >();
//add_index< primary_index<distributed_dividend_balance_object_index > >();
add_index< primary_index<pending_dividend_payout_balance_for_holder_object_index > >();
add_index< primary_index<total_distributed_dividend_balance_object_index > >();
}
@ -329,7 +394,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
}).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT);
FC_ASSERT(create<account_object>([this](account_object& a) {
a.name = "test-dividend-distribution";
a.name = "default-dividend-distribution";
a.statistics = create<account_statistics_object>([&](account_statistics_object& s){s.owner = a.id;}).id;
a.owner.weight_threshold = 1;
a.active.weight_threshold = 1;
@ -337,7 +402,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.membership_expiration_date = time_point_sec::maximum();
a.network_fee_percentage = 0;
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
}).get_id() == TOURNAMENT_RAKE_FEE_ACCOUNT_ID);
}).get_id() == GRAPHENE_RAKE_FEE_ACCOUNT_ID);
// Create more special accounts
while( true )
{
@ -368,9 +433,11 @@ void database::init_genesis(const genesis_state_type& genesis_state)
create<asset_dividend_data_object>([&](asset_dividend_data_object& a) {
a.options.minimum_distribution_interval = 3*24*60*60;
a.options.minimum_fee_percentage = 10*GRAPHENE_1_PERCENT;
a.options.next_payout_time = genesis_state.initial_timestamp + fc::days(1);
a.options.payout_interval = 30*24*60*60;
a.dividend_distribution_account = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
a.options.payout_interval = 7*24*60*60;
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
//a.options.next_payout_time = genesis_state.initial_timestamp + fc::days(1);
//a.options.payout_interval = 30*24*60*60;
});
const asset_object& core_asset =
@ -387,7 +454,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.options.core_exchange_rate.quote.asset_id = asset_id_type(0);
a.dynamic_asset_data_id = dyn_asset.id;
a.dividend_data_id = div_asset.id;
});
});
assert( asset_id_type(core_asset.id) == asset().asset_id );
assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) );
@ -403,18 +470,18 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.options.minimum_fee_percentage = 10*GRAPHENE_1_PERCENT;
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
a.options.payout_interval = 7*24*60*60;
a.dividend_distribution_account = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
});
const asset_object& default_asset =
create<asset_object>( [&]( asset_object& a ) {
a.symbol = "DEFAULT";
a.symbol = "DEF";
a.options.max_market_fee =
a.options.max_supply = genesis_state.max_core_supply;
a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
a.options.flags = 0;
a.options.issuer_permissions = 79;
a.issuer = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
a.issuer = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
a.options.core_exchange_rate.base.amount = 1;
a.options.core_exchange_rate.base.asset_id = asset_id_type(0);
a.options.core_exchange_rate.quote.amount = 1;
@ -424,6 +491,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
});
assert( default_asset.id == asset_id_type(1) );
#endif
// Create more special assets
while( true )
{
@ -467,6 +535,9 @@ void database::init_genesis(const genesis_state_type& genesis_state)
p.witness_budget = 0;
p.recent_slots_filled = fc::uint128::max_value();
});
create<global_betting_statistics_object>([&](global_betting_statistics_object& betting_statistics) {
betting_statistics.number_of_active_events = 0;
});
FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, "min_witness_count must be odd" );
FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1, "min_committee_member_count must be odd" );

View file

@ -48,6 +48,8 @@
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/worker_object.hpp>
#define USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // vesting_balance_object by_asset_balance index needed
namespace graphene { namespace chain {
template<class Index>
@ -226,7 +228,7 @@ void database::update_active_witnesses()
{
vote_counter vc;
for( const witness_object& wit : wits )
vc.add( wit.witness_account, _vote_tally_buffer[wit.vote_id] );
vc.add( wit.witness_account, std::max(_vote_tally_buffer[wit.vote_id], UINT64_C(1)) );
vc.finish( a.active );
}
} );
@ -308,7 +310,7 @@ void database::update_active_committee_members()
{
vote_counter vc;
for( const committee_member_object& cm : committee_members )
vc.add( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );
vc.add( cm.committee_member_account, std::max(_vote_tally_buffer[cm.vote_id], UINT64_C(1)) );
vc.finish( a.active );
}
} );
@ -735,7 +737,7 @@ void schedule_pending_dividend_balances(database& db,
const vesting_balance_index& vesting_index,
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index,
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index)
{
{ try {
dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}",
("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time()));
auto current_distribution_account_balance_range =
@ -759,6 +761,7 @@ void schedule_pending_dividend_balances(database& db,
uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder;
std::map<account_id_type, share_type> vesting_amounts;
#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX
// get only once a collection of accounts that hold nonzero vesting balances of the dividend asset
auto vesting_balances_begin =
vesting_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id));
@ -771,6 +774,20 @@ void schedule_pending_dividend_balances(database& db,
// ("owner", vesting_balance_obj.owner(db).name)
// ("amount", vesting_balance_obj.balance.amount));
}
#else
// get only once a collection of accounts that hold nonzero vesting balances of the dividend asset
const auto& vesting_balances = vesting_index.indices().get<by_id>();
for (const vesting_balance_object& vesting_balance_obj : vesting_balances)
{
if (vesting_balance_obj.balance.asset_id == dividend_holder_asset_obj.id && vesting_balance_obj.balance.amount)
{
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
("owner", vesting_balance_obj.owner(db).name)
("amount", vesting_balance_obj.balance.amount));
}
}
#endif
auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first;
auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first;
@ -1042,10 +1059,10 @@ void schedule_pending_dividend_balances(database& db,
dividend_data_obj.last_distribution_time = current_head_block_time;
});
}
} FC_CAPTURE_AND_RETHROW() }
void process_dividend_assets(database& db)
{
{ try {
ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time()));
const account_balance_index& balance_index = db.get_index_type<account_balance_index>();
@ -1067,143 +1084,146 @@ void process_dividend_assets(database& db)
if (dividend_data.options.next_payout_time &&
db.head_block_time() >= *dividend_data.options.next_payout_time)
{
dlog("Dividend payout time has arrived for asset ${holder_asset}",
("holder_asset", dividend_holder_asset_obj.symbol));
try
{
dlog("Dividend payout time has arrived for asset ${holder_asset}",
("holder_asset", dividend_holder_asset_obj.symbol));
#ifndef NDEBUG
// dump balances before the payouts for debugging
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
// dump balances before the payouts for debugging
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
#endif
// when we do the payouts, we first increase the balances in all of the receiving accounts
// and use this map to keep track of the total amount of each asset paid out.
// Afterwards, we decrease the distribution account's balance by the total amount paid out,
// and modify the distributed_balances accordingly
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
// when we do the payouts, we first increase the balances in all of the receiving accounts
// and use this map to keep track of the total amount of each asset paid out.
// Afterwards, we decrease the distribution account's balance by the total amount paid out,
// and modify the distributed_balances accordingly
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
auto pending_payouts_range =
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
// we iterate in this order so we can build up a list of payouts for each account to put in the
// virtual op
flat_set<asset> payouts_for_this_holder;
fc::optional<account_id_type> last_holder_account_id;
auto pending_payouts_range =
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
// we iterate in this order so we can build up a list of payouts for each account to put in the
// virtual op
vector<asset> payouts_for_this_holder;
fc::optional<account_id_type> last_holder_account_id;
// cache the assets the distribution account is approved to send, we will be asking
// for these often
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
auto approved_assets_iter = approved_assets.find(asset_id);
if (approved_assets_iter != approved_assets.end())
return approved_assets_iter->second;
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
asset_id(db));
approved_assets[asset_id] = is_approved;
return is_approved;
};
// cache the assets the distribution account is approved to send, we will be asking
// for these often
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
auto approved_assets_iter = approved_assets.find(asset_id);
if (approved_assets_iter != approved_assets.end())
return approved_assets_iter->second;
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
asset_id(db));
approved_assets[asset_id] = is_approved;
return is_approved;
};
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
{
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
{
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size())
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size())
{
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
*last_holder_account_id,
payouts_for_this_holder));
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
payouts_for_this_holder.clear();
last_holder_account_id.reset();
}
if (pending_balance_object.pending_balance.value &&
is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
{
dlog("Processing payout of ${asset} to account ${account}",
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
("account", pending_balance_object.owner(db).name));
db.adjust_balance(pending_balance_object.owner,
asset(pending_balance_object.pending_balance,
pending_balance_object.dividend_payout_asset_type));
payouts_for_this_holder.push_back(asset(pending_balance_object.pending_balance,
pending_balance_object.dividend_payout_asset_type));
last_holder_account_id = pending_balance_object.owner;
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
pending_balance.pending_balance = 0;
});
}
++pending_balance_object_iter;
}
// we will always be left with the last holder's data, generate the virtual op for it now.
if (last_holder_account_id && payouts_for_this_holder.size())
{
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
*last_holder_account_id,
payouts_for_this_holder));
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
payouts_for_this_holder.clear();
last_holder_account_id.reset();
}
// now debit the total amount of dividends paid out from the distribution account
// and reduce the distributed_balances accordingly
if (pending_balance_object.pending_balance.value &&
is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
for (const auto& value : amounts_paid_out_by_asset)
{
dlog("Processing payout of ${asset} to account ${account}",
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
("account", pending_balance_object.owner(db).name));
const asset_id_type& asset_paid_out = value.first;
const share_type& amount_paid_out = value.second;
db.adjust_balance(pending_balance_object.owner,
asset(pending_balance_object.pending_balance,
pending_balance_object.dividend_payout_asset_type));
payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance,
pending_balance_object.dividend_payout_asset_type));
last_holder_account_id = pending_balance_object.owner;
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
db.adjust_balance(dividend_data.dividend_distribution_account,
asset(-amount_paid_out,
asset_paid_out));
auto distributed_balance_iter =
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
asset_paid_out));
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero
});
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
pending_balance.pending_balance = 0;
});
}
++pending_balance_object_iter;
}
// we will always be left with the last holder's data, generate the virtual op for it now.
if (last_holder_account_id && payouts_for_this_holder.size())
{
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
*last_holder_account_id,
payouts_for_this_holder));
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
}
// now schedule the next payout time
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
dividend_data_obj.last_payout_time = current_head_block_time;
fc::optional<fc::time_point_sec> next_payout_time;
if (dividend_data_obj.options.payout_interval)
{
// if there was a previous payout, make our next payment one interval
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
do
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
while (next_possible_time_sec <= current_time_sec);
// now debit the total amount of dividends paid out from the distribution account
// and reduce the distributed_balances accordingly
for (const auto& value : amounts_paid_out_by_asset)
{
const asset_id_type& asset_paid_out = value.first;
const share_type& amount_paid_out = value.second;
db.adjust_balance(dividend_data.dividend_distribution_account,
asset(-amount_paid_out,
asset_paid_out));
auto distributed_balance_iter =
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
asset_paid_out));
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero
});
}
// now schedule the next payout time
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
dividend_data_obj.last_payout_time = current_head_block_time;
fc::optional<fc::time_point_sec> next_payout_time;
if (dividend_data_obj.options.payout_interval)
{
// if there was a previous payout, make our next payment one interval
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
do
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
while (next_possible_time_sec <= current_time_sec);
next_payout_time = next_possible_time_sec;
}
dividend_data_obj.options.next_payout_time = next_payout_time;
idump((dividend_data_obj.last_scheduled_payout_time)
(dividend_data_obj.last_payout_time)
(dividend_data_obj.options.next_payout_time));
});
next_payout_time = next_possible_time_sec;
}
dividend_data_obj.options.next_payout_time = next_payout_time;
idump((dividend_data_obj.last_scheduled_payout_time)
(dividend_data_obj.last_payout_time)
(dividend_data_obj.options.next_payout_time));
});
}
FC_RETHROW_EXCEPTIONS(error, "Error while paying out dividends for holder asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol))
}
}
}
} FC_CAPTURE_AND_RETHROW() }
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
{
{ try {
const auto& gpo = get_global_properties();
distribute_fba_balances(*this);
@ -1225,6 +1245,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
d._total_voting_stake = 0;
const vesting_balance_index& vesting_index = d.get_index_type<vesting_balance_index>();
#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX
auto vesting_balances_begin =
vesting_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(asset_id_type()));
auto vesting_balances_end =
@ -1236,6 +1257,19 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
// ("owner", vesting_balance_obj.owner(d).name)
// ("amount", vesting_balance_obj.balance.amount));
}
#else
const auto& vesting_balances = vesting_index.indices().get<by_id>();
for (const vesting_balance_object& vesting_balance_obj : vesting_balances)
{
if (vesting_balance_obj.balance.asset_id == asset_id_type() && vesting_balance_obj.balance.amount)
{
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
("owner", vesting_balance_obj.owner(d).name)
("amount", vesting_balance_obj.balance.amount));
}
}
#endif
}
void operator()(const account_object& stake_account) {
@ -1262,7 +1296,6 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
auto itr = vesting_amounts.find(stake_account.id);
if (itr != vesting_amounts.end())
voting_stake += itr->second.value;
for( vote_id_type id : opinion_account.options.votes )
{
uint32_t offset = id.instance();
@ -1384,12 +1417,13 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
});
// Reset all BitAsset force settlement volumes to zero
for( const asset_bitasset_data_object* d : get_index_type<asset_bitasset_data_index>() )
modify(*d, [](asset_bitasset_data_object& d) { d.force_settled_volume = 0; });
//for( const asset_bitasset_data_object* d : get_index_type<asset_bitasset_data_index>() )
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; });
// process_budget needs to run at the bottom because
// it needs to know the next_maintenance_time
process_budget();
}
} FC_CAPTURE_AND_RETHROW() }
} }

View file

@ -64,10 +64,16 @@ void database::reindex(fc::path data_dir, const genesis_state_type& initial_allo
const auto last_block_num = last_block->block_num();
ilog( "Replaying blocks..." );
_undo_db.disable();
// Right now, we leave undo_db enabled when replaying when the bookie plugin is
// enabled. It depends on new/changed/removed object notifications, and those are
// only fired when the undo_db is enabled
if (!_slow_replays)
_undo_db.disable();
for( uint32_t i = 1; i <= last_block_num; ++i )
{
if( i % 2000 == 0 ) std::cerr << " " << double(i*100)/last_block_num << "% "<<i << " of " <<last_block_num<<" \n";
if( i == 1 ||
i % 10000 == 0 )
std::cerr << " " << double(i*100)/last_block_num << "% "<< i << " of " <<last_block_num<<" \n";
fc::optional< signed_block > block = _block_id_to_block.fetch_by_number(i);
if( !block.valid() )
{
@ -88,14 +94,24 @@ void database::reindex(fc::path data_dir, const genesis_state_type& initial_allo
wlog( "Dropped ${n} blocks from after the gap", ("n", dropped_count) );
break;
}
apply_block(*block, skip_witness_signature |
skip_transaction_signatures |
skip_transaction_dupe_check |
skip_tapos_check |
skip_witness_schedule_check |
skip_authority_check);
if (_slow_replays)
push_block(*block, skip_fork_db |
skip_witness_signature |
skip_transaction_signatures |
skip_transaction_dupe_check |
skip_tapos_check |
skip_witness_schedule_check |
skip_authority_check);
else
apply_block(*block, skip_witness_signature |
skip_transaction_signatures |
skip_transaction_dupe_check |
skip_tapos_check |
skip_witness_schedule_check |
skip_authority_check);
}
_undo_db.enable();
if (!_slow_replays)
_undo_db.enable();
auto end = fc::time_point::now();
ilog( "Done reindexing, elapsed time: ${t} sec", ("t",double((end-start).count())/1000000.0 ) );
} FC_CAPTURE_AND_RETHROW( (data_dir) ) }
@ -166,8 +182,9 @@ void database::close(bool rewind)
}
}
}
catch (...)
catch ( const fc::exception& e )
{
wlog( "Database close unexpected exception: ${e}", ("e", e) );
}
}
@ -185,4 +202,10 @@ void database::close(bool rewind)
_fork_db.reset();
}
void database::force_slow_replays()
{
ilog("enabling slow replays");
_slow_replays = true;
}
} }

View file

@ -146,7 +146,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order )
*/
if( order.amount_to_receive().amount == 0 )
{
ilog( "applied epsilon logic" );
//ilog( "applied epsilon logic" );
db.cancel_order(order);
return true;
}

View file

@ -0,0 +1,444 @@
#include <fc/container/flat.hpp>
#include <graphene/chain/protocol/authority.hpp>
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/protocol/transaction.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/confidential_object.hpp>
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/committee_member_object.hpp>
using namespace fc;
using namespace graphene::chain;
// TODO: Review all of these, especially no-ops
struct get_impacted_account_visitor
{
flat_set<account_id_type>& _impacted;
get_impacted_account_visitor( flat_set<account_id_type>& impact ):_impacted(impact) {}
typedef void result_type;
void operator()( const transfer_operation& op )
{
_impacted.insert( op.to );
}
void operator()( const asset_claim_fees_operation& op ){}
void operator()( const limit_order_create_operation& op ) {}
void operator()( const limit_order_cancel_operation& op )
{
_impacted.insert( op.fee_paying_account );
}
void operator()( const call_order_update_operation& op ) {}
void operator()( const fill_order_operation& op )
{
_impacted.insert( op.account_id );
}
void operator()( const account_create_operation& op )
{
_impacted.insert( op.registrar );
_impacted.insert( op.referrer );
add_authority_accounts( _impacted, op.owner );
add_authority_accounts( _impacted, op.active );
}
void operator()( const account_update_operation& op )
{
_impacted.insert( op.account );
if( op.owner )
add_authority_accounts( _impacted, *(op.owner) );
if( op.active )
add_authority_accounts( _impacted, *(op.active) );
}
void operator()( const account_whitelist_operation& op )
{
_impacted.insert( op.account_to_list );
}
void operator()( const account_upgrade_operation& op ) {}
void operator()( const account_transfer_operation& op )
{
_impacted.insert( op.new_owner );
}
void operator()( const asset_create_operation& op ) {}
void operator()( const asset_update_operation& op )
{
if( op.new_issuer )
_impacted.insert( *(op.new_issuer) );
}
void operator()( const asset_update_bitasset_operation& op ) {}
void operator()( const asset_update_dividend_operation& op ) {}
void operator()( const asset_dividend_distribution_operation& op )
{
_impacted.insert( op.account_id );
}
void operator()( const asset_update_feed_producers_operation& op ) {}
void operator()( const asset_issue_operation& op )
{
_impacted.insert( op.issue_to_account );
}
void operator()( const asset_reserve_operation& op ) {}
void operator()( const asset_fund_fee_pool_operation& op ) {}
void operator()( const asset_settle_operation& op ) {}
void operator()( const asset_global_settle_operation& op ) {}
void operator()( const asset_publish_feed_operation& op ) {}
void operator()( const witness_create_operation& op )
{
_impacted.insert( op.witness_account );
}
void operator()( const witness_update_operation& op )
{
_impacted.insert( op.witness_account );
}
void operator()( const proposal_create_operation& op )
{
vector<authority> other;
for( const auto& proposed_op : op.proposed_ops )
operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other );
for( auto& o : other )
add_authority_accounts( _impacted, o );
}
void operator()( const proposal_update_operation& op ) {}
void operator()( const proposal_delete_operation& op ) {}
void operator()( const withdraw_permission_create_operation& op )
{
_impacted.insert( op.authorized_account );
}
void operator()( const withdraw_permission_update_operation& op )
{
_impacted.insert( op.authorized_account );
}
void operator()( const withdraw_permission_claim_operation& op )
{
_impacted.insert( op.withdraw_from_account );
}
void operator()( const withdraw_permission_delete_operation& op )
{
_impacted.insert( op.authorized_account );
}
void operator()( const committee_member_create_operation& op )
{
_impacted.insert( op.committee_member_account );
}
void operator()( const committee_member_update_operation& op )
{
_impacted.insert( op.committee_member_account );
}
void operator()( const committee_member_update_global_parameters_operation& op ) {}
void operator()( const vesting_balance_create_operation& op )
{
_impacted.insert( op.owner );
}
void operator()( const vesting_balance_withdraw_operation& op ) {}
void operator()( const worker_create_operation& op ) {}
void operator()( const custom_operation& op ) {}
void operator()( const assert_operation& op ) {}
void operator()( const balance_claim_operation& op ) {}
void operator()( const override_transfer_operation& op )
{
_impacted.insert( op.to );
_impacted.insert( op.from );
_impacted.insert( op.issuer );
}
void operator()( const transfer_to_blind_operation& op )
{
_impacted.insert( op.from );
for( const auto& out : op.outputs )
add_authority_accounts( _impacted, out.owner );
}
void operator()( const blind_transfer_operation& op )
{
for( const auto& in : op.inputs )
add_authority_accounts( _impacted, in.owner );
for( const auto& out : op.outputs )
add_authority_accounts( _impacted, out.owner );
}
void operator()( const transfer_from_blind_operation& op )
{
_impacted.insert( op.to );
for( const auto& in : op.inputs )
add_authority_accounts( _impacted, in.owner );
}
void operator()( const asset_settle_cancel_operation& op )
{
_impacted.insert( op.account );
}
void operator()( const fba_distribute_operation& op )
{
_impacted.insert( op.account_id );
}
void operator()(const sport_create_operation&){}
void operator()(const sport_update_operation&){}
void operator()(const event_group_create_operation&){}
void operator()(const event_group_update_operation& op ) {}
void operator()(const event_create_operation&){}
void operator()(const event_update_operation& op ) {}
void operator()(const event_update_status_operation& op ) {}
void operator()(const betting_market_rules_create_operation&){}
void operator()(const betting_market_rules_update_operation& op ) {}
void operator()(const betting_market_group_create_operation&){}
void operator()(const betting_market_group_update_operation& op ) {}
void operator()(const betting_market_create_operation&){}
void operator()(const betting_market_update_operation&){}
void operator()(const bet_place_operation&){}
void operator()(const betting_market_group_resolve_operation&){}
void operator()(const betting_market_group_resolved_operation &){}
void operator()(const betting_market_group_cancel_unmatched_bets_operation&){}
void operator()(const bet_matched_operation &){}
void operator()(const bet_cancel_operation&){}
void operator()(const bet_canceled_operation &){}
void operator()(const bet_adjusted_operation &){}
void operator()( const tournament_create_operation& op )
{
_impacted.insert( op.creator );
_impacted.insert( op.options.whitelist.begin(), op.options.whitelist.end() );
}
void operator()( const tournament_join_operation& op )
{
_impacted.insert( op.payer_account_id );
_impacted.insert( op.player_account_id );
}
void operator()( const tournament_leave_operation& op )
{
//if account canceling registration is not the player, it must be the payer
if (op.canceling_account_id != op.player_account_id)
_impacted.erase( op.canceling_account_id );
_impacted.erase( op.player_account_id );
}
void operator()( const game_move_operation& op )
{
_impacted.insert( op.player_account_id );
}
void operator()( const tournament_payout_operation& op )
{
_impacted.insert( op.payout_account_id );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
{
get_impacted_account_visitor vtor = get_impacted_account_visitor( result );
op.visit( vtor );
}
void transaction_get_impacted_accounts( const transaction& tx, flat_set<account_id_type>& result )
{
for( const auto& op : tx.operations )
operation_get_impacted_accounts( op, result );
}
void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts )
{
if( obj->id.space() == protocol_ids )
{
switch( (object_type)obj->id.type() )
{
case null_object_type:
case base_object_type:
case OBJECT_TYPE_COUNT:
return;
case account_object_type:{
accounts.insert( obj->id );
break;
} case asset_object_type:{
const auto& aobj = dynamic_cast<const asset_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->issuer );
break;
} case force_settlement_object_type:{
const auto& aobj = dynamic_cast<const force_settlement_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->owner );
break;
} case committee_member_object_type:{
const auto& aobj = dynamic_cast<const committee_member_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->committee_member_account );
break;
} case witness_object_type:{
const auto& aobj = dynamic_cast<const witness_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->witness_account );
break;
} case limit_order_object_type:{
const auto& aobj = dynamic_cast<const limit_order_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->seller );
break;
} case call_order_object_type:{
const auto& aobj = dynamic_cast<const call_order_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->borrower );
break;
} case custom_object_type:{
break;
} case proposal_object_type:{
const auto& aobj = dynamic_cast<const proposal_object*>(obj);
assert( aobj != nullptr );
transaction_get_impacted_accounts( aobj->proposed_transaction, accounts );
break;
} case operation_history_object_type:{
const auto& aobj = dynamic_cast<const operation_history_object*>(obj);
assert( aobj != nullptr );
operation_get_impacted_accounts( aobj->op, accounts );
break;
} case withdraw_permission_object_type:{
const auto& aobj = dynamic_cast<const withdraw_permission_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->withdraw_from_account );
accounts.insert( aobj->authorized_account );
break;
} case vesting_balance_object_type:{
const auto& aobj = dynamic_cast<const vesting_balance_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->owner );
break;
} case worker_object_type:{
const auto& aobj = dynamic_cast<const worker_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->worker_account );
break;
} case balance_object_type:{
/** these are free from any accounts */
break;
}
}
}
else if( obj->id.space() == implementation_ids )
{
switch( (impl_object_type)obj->id.type() )
{
case impl_global_property_object_type:
break;
case impl_dynamic_global_property_object_type:
break;
case impl_reserved0_object_type:
break;
case impl_asset_dynamic_data_type:
break;
case impl_asset_bitasset_data_type:
break;
case impl_account_balance_object_type:{
const auto& aobj = dynamic_cast<const account_balance_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->owner );
break;
} case impl_account_statistics_object_type:{
const auto& aobj = dynamic_cast<const account_statistics_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->owner );
break;
} case impl_transaction_object_type:{
const auto& aobj = dynamic_cast<const transaction_object*>(obj);
assert( aobj != nullptr );
transaction_get_impacted_accounts( aobj->trx, accounts );
break;
} case impl_blinded_balance_object_type:{
const auto& aobj = dynamic_cast<const blinded_balance_object*>(obj);
assert( aobj != nullptr );
for( const auto& a : aobj->owner.account_auths )
accounts.insert( a.first );
break;
} case impl_block_summary_object_type:
break;
case impl_account_transaction_history_object_type:
break;
case impl_chain_property_object_type:
break;
case impl_witness_schedule_object_type:
break;
case impl_budget_record_object_type:
break;
case impl_special_authority_object_type:
break;
case impl_buyback_object_type:
break;
case impl_fba_accumulator_object_type:
break;
}
}
} // end get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts )
namespace graphene { namespace chain {
void database::notify_changed_objects()
{ try {
if( _undo_db.enabled() )
{
const auto& head_undo = _undo_db.head();
// New
if( !new_objects.empty() )
{
vector<object_id_type> new_ids; new_ids.reserve(head_undo.new_ids.size());
flat_set<account_id_type> new_accounts_impacted;
for( const auto& item : head_undo.new_ids )
{
new_ids.push_back(item);
auto obj = find_object(item);
if(obj != nullptr)
get_relevant_accounts(obj, new_accounts_impacted);
}
new_objects(new_ids, new_accounts_impacted);
}
// Changed
if( !changed_objects.empty() )
{
vector<object_id_type> changed_ids; changed_ids.reserve(head_undo.old_values.size());
flat_set<account_id_type> changed_accounts_impacted;
for( const auto& item : head_undo.old_values )
{
changed_ids.push_back(item.first);
get_relevant_accounts(item.second.get(), changed_accounts_impacted);
}
changed_objects(changed_ids, changed_accounts_impacted);
}
// Removed
if( !removed_objects.empty() )
{
vector<object_id_type> removed_ids; removed_ids.reserve( head_undo.removed.size() );
vector<const object*> removed; removed.reserve( head_undo.removed.size() );
flat_set<account_id_type> removed_accounts_impacted;
for( const auto& item : head_undo.removed )
{
removed_ids.emplace_back( item.first );
auto obj = item.second.get();
removed.emplace_back( obj );
get_relevant_accounts(obj, removed_accounts_impacted);
}
removed_objects(removed_ids, removed, removed_accounts_impacted);
}
}
} FC_CAPTURE_AND_LOG( (0) ) }
} }

View file

@ -35,6 +35,7 @@
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -188,6 +189,52 @@ void database::clear_expired_transactions()
transaction_idx.remove(*dedupe_index.begin());
} FC_CAPTURE_AND_RETHROW() }
void database::place_delayed_bets()
{ try {
// If any bets have been placed during live betting where bets are delayed for a few seconds, see if there are
// any bets whose delays have expired.
// Delayed bets are sorted to the beginning of the order book, so if there are any bets that need placing,
// they're right at the front of the book
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
auto iter = bet_odds_idx.begin();
// we use an awkward looping mechanism here because there's a case where we are processing the
// last delayed bet before the "real" order book starts and `iter` was pointing at the first
// real order. The place_bet() call can cause the that real order to be deleted, so we need
// to decide whether this is the last delayed bet before `place_bet` is called.
bool last = iter == bet_odds_idx.end() ||
!iter->end_of_delay ||
*iter->end_of_delay > head_block_time();
while (!last)
{
const bet_object& bet_to_place = *iter;
++iter;
last = iter == bet_odds_idx.end() ||
!iter->end_of_delay ||
*iter->end_of_delay > head_block_time();
// it's possible that the betting market was active when the bet was placed,
// but has been frozen before the delay expired. If that's the case here,
// don't try to match the bet.
// Since this check happens every block, this could impact performance if a
// market with many delayed bets is frozen for a long time.
// Our current understanding is that the witnesses will typically cancel all unmatched
// bets on frozen markets to avoid this.
const betting_market_object& betting_market = bet_to_place.betting_market_id(*this);
if (betting_market.get_status() == betting_market_status::unresolved)
{
modify(bet_to_place, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
place_bet(bet_to_place);
}
}
} FC_CAPTURE_AND_RETHROW() }
void database::clear_expired_proposals()
{
const auto& proposal_expiration_index = get_index_type<proposal_index>().indices().get<by_expiration>();
@ -608,4 +655,29 @@ void database::update_tournaments()
initiate_next_games(*this);
}
void process_settled_betting_markets(database& db, fc::time_point_sec current_block_time)
{
// after a betting market is graded, it goes through a delay period in which it
// can be flagged for re-grading. If it isn't flagged during this interval,
// it is automatically settled (paid). Process these now.
const auto& betting_market_group_index = db.get_index_type<betting_market_group_object_index>().indices().get<by_settling_time>();
// this index will be sorted with all bmgs with no settling time set first, followed by
// ones with the settling time set by increasing time. Start at the first bmg with a time set
auto betting_market_group_iter = betting_market_group_index.upper_bound(fc::optional<fc::time_point_sec>());
while (betting_market_group_iter != betting_market_group_index.end() &&
*betting_market_group_iter->settling_time <= current_block_time)
{
auto next_iter = std::next(betting_market_group_iter);
db.settle_betting_market_group(*betting_market_group_iter);
betting_market_group_iter = next_iter;
}
}
void database::update_betting_markets(fc::time_point_sec current_block_time)
{
process_settled_betting_markets(*this, current_block_time);
remove_completed_events();
}
} }

View file

@ -128,4 +128,18 @@ database& generic_evaluator::db()const { return trx_state->db(); }
db().adjust_balance(fee_payer, fee_from_account);
}
object_id_type generic_evaluator::get_relative_id( object_id_type rel_id )const
{
if (!is_relative(rel_id))
FC_THROW("get_relative_id() called for non-relative id ${id}", ("id", rel_id));
if (rel_id.instance() >= trx_state->operation_results.size())
FC_THROW("get_relative_id() asked for id of operation ${op_num} (zero-based), but we only have ${count} operations",
("op_num", rel_id.instance())("count", trx_state->operation_results.size()));
if (trx_state->operation_results[rel_id.instance()].which() != operation_result::tag<object_id_type>::value)
FC_THROW("get_relative_id() asked for the result of operation ${op_num}, but that operation did not return an object_id",
("op_num", rel_id.instance()));
return trx_state->operation_results[rel_id.instance()].get<object_id_type>();
}
} }

View file

@ -0,0 +1,140 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/event_evaluator.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
#include <graphene/chain/global_betting_statistics_object.hpp>
namespace graphene { namespace chain {
void_result event_create_evaluator::do_evaluate(const event_create_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
//database& d = db();
// the event_group_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly an event_group
object_id_type resolved_event_group_id = op.event_group_id;
if (is_relative(op.event_group_id))
resolved_event_group_id = get_relative_id(op.event_group_id);
FC_ASSERT(resolved_event_group_id.space() == event_group_id_type::space_id &&
resolved_event_group_id.type() == event_group_id_type::type_id,
"event_group_id must refer to a event_group_id_type");
event_group_id = resolved_event_group_id;
//const event_group_object& event_group = event_group_id(d);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type event_create_evaluator::do_apply(const event_create_operation& op)
{ try {
database& d = db();
const event_object& new_event =
d.create<event_object>( [&]( event_object& event_obj ) {
event_obj.name = op.name;
event_obj.season = op.season;
event_obj.start_time = op.start_time;
event_obj.event_group_id = event_group_id;
});
//increment number of active events in global betting statistics object
const global_betting_statistics_object& betting_statistics = global_betting_statistics_id_type()(d);
d.modify( betting_statistics, [&](global_betting_statistics_object& bso) {
bso.number_of_active_events += 1;
});
return new_event.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_update_evaluator::do_evaluate(const event_update_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_event_group_id || op.new_name || op.new_season ||
op.new_start_time || op.new_status, "nothing to change");
if (op.new_event_group_id)
{
object_id_type resolved_event_group_id = *op.new_event_group_id;
if (is_relative(*op.new_event_group_id))
resolved_event_group_id = get_relative_id(*op.new_event_group_id);
FC_ASSERT(resolved_event_group_id.space() == event_group_id_type::space_id &&
resolved_event_group_id.type() == event_group_id_type::type_id,
"event_group_id must refer to a event_group_id_type");
event_group_id = resolved_event_group_id;
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_update_evaluator::do_apply(const event_update_operation& op)
{ try {
database& _db = db();
_db.modify(_db.get(op.event_id),
[&](event_object& eo) {
if( op.new_name )
eo.name = *op.new_name;
if( op.new_season )
eo.season = *op.new_season;
if( op.new_start_time )
eo.start_time = *op.new_start_time;
if( op.new_event_group_id )
eo.event_group_id = event_group_id;
if( op.new_status )
eo.dispatch_new_status(_db, *op.new_status);
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_update_status_evaluator::do_evaluate(const event_update_status_operation& op)
{ try {
FC_ASSERT(trx_state->_is_proposed_trx);
database& d = db();
FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME);
//check that the event to update exists
_event_to_update = &op.event_id(d);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_update_status_evaluator::do_apply(const event_update_status_operation& op)
{ try {
database& d = db();
d.modify( *_event_to_update, [&](event_object& event_obj) {
if (_event_to_update->get_status() != op.status)
event_obj.dispatch_new_status(d, op.status);
event_obj.scores = op.scores;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} } // graphene::chain

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/event_group_evaluator.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
namespace graphene { namespace chain {
void_result event_group_create_evaluator::do_evaluate(const event_group_create_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
// the sport id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly a sport
object_id_type resolved_id = op.sport_id;
if (is_relative(op.sport_id))
resolved_id = get_relative_id(op.sport_id);
FC_ASSERT(resolved_id.space() == sport_id_type::space_id &&
resolved_id.type() == sport_id_type::type_id, "sport_id must refer to a sport_id_type");
sport_id = resolved_id;
FC_ASSERT( db().find_object(sport_id), "Invalid sport specified" );
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type event_group_create_evaluator::do_apply(const event_group_create_operation& op)
{ try {
const event_group_object& new_event_group =
db().create<event_group_object>( [&]( event_group_object& event_group_obj ) {
event_group_obj.name = op.name;
event_group_obj.sport_id = sport_id;
});
return new_event_group.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_group_update_evaluator::do_evaluate(const event_group_update_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_sport_id.valid() || op.new_name.valid(), "nothing to change");
if( op.new_sport_id.valid() )
{
object_id_type resolved_id = *op.new_sport_id;
if (is_relative(*op.new_sport_id))
resolved_id = get_relative_id(*op.new_sport_id);
FC_ASSERT(resolved_id.space() == sport_id_type::space_id &&
resolved_id.type() == sport_id_type::type_id, "sport_id must refer to a sport_id_type");
sport_id = resolved_id;
FC_ASSERT( db().find_object(sport_id), "invalid sport specified" );
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_group_update_evaluator::do_apply(const event_group_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.event_group_id),
[&]( event_group_object& ego )
{
if( op.new_name.valid() )
ego.name = *op.new_name;
if( op.new_sport_id.valid() )
ego.sport_id = sport_id;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} } // graphene::chain

View file

@ -0,0 +1,553 @@
#define DEFAULT_LOGGER "betting"
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 30
#define BOOST_MPL_LIMIT_MAP_SIZE 30
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
namespace graphene { namespace chain {
enum class event_state {
upcoming,
frozen_upcoming,
in_progress,
frozen_in_progress,
finished,
canceled,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::event_state,
(upcoming)
(frozen_upcoming)
(in_progress)
(frozen_in_progress)
(finished)
(canceled)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace
{
// Events -- most events happen when the witnesses publish an event_update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct upcoming_event
{
database& db;
upcoming_event(database& db) : db(db) {}
};
struct in_progress_event
{
database& db;
in_progress_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct finished_event
{
database& db;
finished_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
canceled_event(database& db) : db(db) {}
};
// event triggered when a betting market group in this event is resolved,
// when we get this, check and see if all betting market groups are now
// canceled/settled; if so, transition the event to canceled/settled,
// otherwise remain in the current state
struct betting_market_group_resolved_event
{
database& db;
betting_market_group_id_type resolved_group;
bool was_canceled;
betting_market_group_resolved_event(database& db, betting_market_group_id_type resolved_group, bool was_canceled) : db(db), resolved_group(resolved_group), was_canceled(was_canceled) {}
};
// event triggered when a betting market group is closed. When we get this,
// if all child betting market groups are closed, transition to finished
struct betting_market_group_closed_event
{
database& db;
betting_market_group_id_type closed_group;
betting_market_group_closed_event(database& db, betting_market_group_id_type closed_group) : db(db), closed_group(closed_group) {}
};
// Events
struct event_state_machine_ : public msm::front::state_machine_def<event_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct upcoming : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
void on_entry(const upcoming_event& event, event_state_machine_& fsm) {
dlog("event ${id} -> upcoming", ("id", fsm.event_obj->id));
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(fsm.event_obj->id)))
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_upcoming_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
};
struct in_progress : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
void on_entry(const in_progress_event& event, event_state_machine_& fsm) {
dlog("event ${id} -> in_progress", ("id", fsm.event_obj->id));
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(fsm.event_obj->id)))
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_in_play_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
};
struct frozen_upcoming : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> frozen_upcoming", ("id", fsm.event_obj->id));
}
};
struct frozen_in_progress : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> frozen_in_progress", ("id", fsm.event_obj->id));
}
};
struct finished : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> finished", ("id", fsm.event_obj->id));
}
};
struct settled : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> settled", ("id", fsm.event_obj->id));
}
};
struct canceled : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> canceled", ("id", fsm.event_obj->id));
}
};
// actions
void record_whether_group_settled_or_canceled(const betting_market_group_resolved_event& event) {
if (!event.was_canceled)
event_obj->at_least_one_betting_market_group_settled = true;
}
void freeze_betting_market_groups(const frozen_event& event) {
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
{
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_frozen_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
}
void close_all_betting_market_groups(const finished_event& event) {
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
{
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_closed_event(event.db, true);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't close the group. just ignore the exception
}
}
}
void cancel_all_betting_market_groups(const canceled_event& event) {
auto& betting_market_group_index = event.db.template get_index_type<betting_market_group_object_index>().indices().template get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_canceled_event(event.db, true);
});
}
// Guards
bool all_betting_market_groups_are_closed(const betting_market_group_closed_event& event)
{
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
if (betting_market_group.id != event.closed_group)
{
betting_market_group_status status = betting_market_group.get_status();
if (status != betting_market_group_status::closed &&
status != betting_market_group_status::graded &&
status != betting_market_group_status::re_grading &&
status != betting_market_group_status::settled &&
status != betting_market_group_status::canceled)
return false;
}
return true;
}
bool all_betting_market_groups_are_canceled(const betting_market_group_resolved_event& event)
{
// if the bmg that just resolved was settled, obviously all didn't cancel
if (!event.was_canceled)
return false;
// if a previously-resolved group was settled, all didn't cancel
if (event_obj->at_least_one_betting_market_group_settled)
return false;
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
if (betting_market_group.id != event.resolved_group)
if (betting_market_group.get_status() != betting_market_group_status::canceled)
return false;
return true;
}
bool all_betting_market_groups_are_resolved(const betting_market_group_resolved_event& event)
{
if (!event.was_canceled)
event_obj->at_least_one_betting_market_group_settled = true;
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id))) {
if (betting_market_group.id != event.resolved_group) {
betting_market_group_status status = betting_market_group.get_status();
if (status != betting_market_group_status::canceled && status != betting_market_group_status::settled)
return false;
}
}
return true;
}
typedef upcoming initial_state;
typedef event_state_machine_ x; // makes transition table cleaner
// Transition table for tournament
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < upcoming, in_progress_event, in_progress >,
a_row< upcoming, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< upcoming, frozen_event, frozen_upcoming, &x::freeze_betting_market_groups >,
a_row< upcoming, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< upcoming, betting_market_group_resolved_event,upcoming, &x::record_whether_group_settled_or_canceled >,
g_row< upcoming, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < frozen_upcoming, upcoming_event, upcoming >,
_row < frozen_upcoming, in_progress_event, in_progress >,
a_row< frozen_upcoming, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< frozen_upcoming, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< frozen_upcoming, betting_market_group_resolved_event,frozen_upcoming, &x::record_whether_group_settled_or_canceled >,
g_row< frozen_upcoming, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
a_row< in_progress, frozen_event, frozen_in_progress, &x::freeze_betting_market_groups >,
a_row< in_progress, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< in_progress, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< in_progress, betting_market_group_resolved_event,in_progress, &x::record_whether_group_settled_or_canceled >,
g_row< in_progress, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < frozen_in_progress, in_progress_event, in_progress >,
a_row< frozen_in_progress, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< frozen_in_progress, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< frozen_in_progress, betting_market_group_resolved_event,frozen_in_progress, &x::record_whether_group_settled_or_canceled >,
g_row< frozen_in_progress, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
a_row< finished, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
g_row< finished, betting_market_group_resolved_event,settled, &x::all_betting_market_groups_are_resolved >,
g_row< finished, betting_market_group_resolved_event,canceled, &x::all_betting_market_groups_are_canceled >
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
event_object* event_obj;
event_state_machine_(event_object* event_obj) : event_obj(event_obj) {}
};
typedef msm::back::state_machine<event_state_machine_> event_state_machine;
} // end anonymous namespace
class event_object::impl {
public:
event_state_machine state_machine;
impl(event_object* self) : state_machine(self) {}
};
event_object::event_object() :
at_least_one_betting_market_group_settled(false),
my(new impl(this))
{
}
event_object::event_object(const event_object& rhs) :
graphene::db::abstract_object<event_object>(rhs),
name(rhs.name),
season(rhs.season),
start_time(rhs.start_time),
event_group_id(rhs.event_group_id),
at_least_one_betting_market_group_settled(rhs.at_least_one_betting_market_group_settled),
scores(rhs.scores),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.event_obj = this;
}
event_object& event_object::operator=(const event_object& rhs)
{
//graphene::db::abstract_object<event_object>::operator=(rhs);
id = rhs.id;
name = rhs.name;
season = rhs.season;
start_time = rhs.start_time;
event_group_id = rhs.event_group_id;
at_least_one_betting_market_group_settled = rhs.at_least_one_betting_market_group_settled;
scores = rhs.scores;
my->state_machine = rhs.my->state_machine;
my->state_machine.event_obj = this;
return *this;
}
event_object::~event_object()
{
}
namespace {
bool verify_event_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<event_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<event_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<event_state>::to_string((event_state)i);
if (!strstr(filled_state_names[i], fc_reflected_value_name))
{
fc_elog(fc::logger::get("default"),
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
++error_count;
}
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("default"),
"Error, no reflection for value ${int_value} in enum event_status",
("int_value", i));
++error_count;
}
}
if (error_count == 0)
dlog("Event status constants are correct");
else
wlog("There were ${count} errors in the event status constants", ("count", error_count));
return error_count == 0;
}
} // end anonymous namespace
event_status event_object::get_status() const
{
static bool state_constants_are_correct = verify_event_status_constants();
(void)&state_constants_are_correct;
event_state state = (event_state)my->state_machine.current_state()[0];
ddump((state));
switch (state)
{
case event_state::upcoming:
return event_status::upcoming;
case event_state::frozen_upcoming:
case event_state::frozen_in_progress:
return event_status::frozen;
case event_state::in_progress:
return event_status::in_progress;
case event_state::finished:
return event_status::finished;
case event_state::canceled:
return event_status::canceled;
case event_state::settled:
return event_status::settled;
default:
FC_THROW("Unexpected event state");
};
}
void event_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void event_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
void event_object::on_upcoming_event(database& db)
{
my->state_machine.process_event(upcoming_event(db));
}
void event_object::on_in_progress_event(database& db)
{
my->state_machine.process_event(in_progress_event(db));
}
void event_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void event_object::on_finished_event(database& db)
{
my->state_machine.process_event(finished_event(db));
}
void event_object::on_canceled_event(database& db)
{
my->state_machine.process_event(canceled_event(db));
}
void event_object::on_betting_market_group_resolved(database& db, betting_market_group_id_type resolved_group, bool was_canceled)
{
my->state_machine.process_event(betting_market_group_resolved_event(db, resolved_group, was_canceled));
}
void event_object::on_betting_market_group_closed(database& db, betting_market_group_id_type closed_group)
{
my->state_machine.process_event(betting_market_group_closed_event(db, closed_group));
}
// These are the only statuses that can be explicitly set by witness operations. The missing
// status, 'settled', is automatically set when all of the betting market groups have
// settled/canceled
void event_object::dispatch_new_status(database& db, event_status new_status)
{
switch (new_status) {
case event_status::upcoming: // by witnesses to unfreeze a frozen event
on_upcoming_event(db);
break;
case event_status::in_progress: // by witnesses when the event starts
on_in_progress_event(db);
break;
case event_status::frozen: // by witnesses when the event needs to be frozen
on_frozen_event(db);
break;
case event_status::finished: // by witnesses when the event is complete
on_finished_event(db);
break;
case event_status::canceled: // by witnesses to cancel the event
on_canceled_event(db);
break;
default:
FC_THROW("Status ${new_status} cannot be explicitly set", ("new_status", new_status));
}
}
} } // graphene::chain
namespace fc {
// Manually reflect event_object to variant to properly reflect "state"
void to_variant(const graphene::chain::event_object& event_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", event_obj.id)
("name", event_obj.name)
("season", event_obj.season)
("start_time", event_obj.start_time)
("event_group_id", event_obj.event_group_id)
("scores", event_obj.scores)
("status", event_obj.get_status());
v = o;
}
// Manually reflect event_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::event_object& event_obj)
{
event_obj.id = v["id"].as<graphene::chain::event_id_type>();
event_obj.name = v["name"].as<graphene::chain::internationalized_string_type>();
event_obj.season = v["season"].as<graphene::chain::internationalized_string_type>();
event_obj.start_time = v["start_time"].as<optional<time_point_sec> >();
event_obj.event_group_id = v["event_group_id"].as<graphene::chain::event_group_id_type>();
event_obj.scores = v["scores"].as<std::vector<std::string>>();
graphene::chain::event_status status = v["status"].as<graphene::chain::event_status>();
const_cast<int*>(event_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -0,0 +1,4 @@
// #615 Fix price feed expiration check, so websocket server will never spam too much data
#ifndef HARDFORK_1000_TIME
#define HARDFORK_1000_TIME (fc::time_point_sec( 1523534400 ))
#endif

View file

@ -1,4 +1,4 @@
// #599 Unpacking of extension is incorrect
#ifndef HARDFORK_599_TIME
#define HARDFORK_599_TIME (fc::time_point_sec( 1458061200 ))
#define HARDFORK_599_TIME (fc::time_point_sec( 1459789200 ))
#endif

View file

@ -1,4 +1,4 @@
// #607 Disable negative voting on workers
#ifndef HARDFORK_607_TIME
#define HARDFORK_607_TIME (fc::time_point_sec( 1458061200 ))
#define HARDFORK_607_TIME (fc::time_point_sec( 1458752400 ))
#endif

View file

@ -1,4 +1,4 @@
// #613 Deprecate annual membership
#ifndef HARDFORK_613_TIME
#define HARDFORK_613_TIME (fc::time_point_sec( 1458061200 ))
#define HARDFORK_613_TIME (fc::time_point_sec( 1458752400 ))
#endif

View file

@ -1,4 +1,4 @@
// #615 Fix price feed expiration check, so websocket server will never spam too much data
#ifndef HARDFORK_615_TIME
#define HARDFORK_615_TIME (fc::time_point_sec( 1457550000 ))
#define HARDFORK_615_TIME (fc::time_point_sec( 1458752400 ))
#endif

View file

@ -50,7 +50,10 @@ namespace graphene { namespace chain {
* Keep the most recent operation as a root pointer to a linked list of the transaction history.
*/
account_transaction_history_id_type most_recent_op;
/** Total operations related to this account. */
uint32_t total_ops = 0;
/** Total operations related to this account that has been removed from the database. */
uint32_t removed_ops = 0;
/**
* When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for
@ -454,7 +457,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object,
(graphene::chain::object),
(owner)
(most_recent_op)
(total_ops)
(total_ops)(removed_ops)
(total_core_in_orders)
(lifetime_fees_paid)
(pending_fees)(pending_vested_fees)

View file

@ -235,15 +235,18 @@ namespace graphene { namespace chain {
>
>
> asset_bitasset_data_object_multi_index_type;
typedef flat_index<asset_bitasset_data_object> asset_bitasset_data_index;
//typedef flat_index<asset_bitasset_data_object> asset_bitasset_data_index;
typedef generic_index<asset_bitasset_data_object, asset_bitasset_data_object_multi_index_type> asset_bitasset_data_index;
struct by_symbol;
struct by_type;
struct by_issuer;
typedef multi_index_container<
asset_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_symbol>, member<asset_object, string, &asset_object::symbol> >,
ordered_non_unique< tag<by_issuer>, member<asset_object, account_id_type, &asset_object::issuer > >,
ordered_unique< tag<by_type>,
composite_key< asset_object,
const_mem_fun<asset_object, bool, &asset_object::is_market_issued>,

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
class betting_market_rules_create_evaluator : public evaluator<betting_market_rules_create_evaluator>
{
public:
typedef betting_market_rules_create_operation operation_type;
void_result do_evaluate( const betting_market_rules_create_operation& o );
object_id_type do_apply( const betting_market_rules_create_operation& o );
};
class betting_market_rules_update_evaluator : public evaluator<betting_market_rules_update_evaluator>
{
public:
typedef betting_market_rules_update_operation operation_type;
void_result do_evaluate( const betting_market_rules_update_operation& o );
void_result do_apply( const betting_market_rules_update_operation& o );
private:
const betting_market_rules_object* _rules;
};
class betting_market_group_create_evaluator : public evaluator<betting_market_group_create_evaluator>
{
public:
typedef betting_market_group_create_operation operation_type;
void_result do_evaluate(const betting_market_group_create_operation& o);
object_id_type do_apply(const betting_market_group_create_operation& o);
private:
event_id_type _event_id;
betting_market_rules_id_type _rules_id;
};
class betting_market_group_update_evaluator : public evaluator<betting_market_group_update_evaluator>
{
public:
typedef betting_market_group_update_operation operation_type;
void_result do_evaluate(const betting_market_group_update_operation& o);
void_result do_apply(const betting_market_group_update_operation& o);
private:
betting_market_rules_id_type _rules_id;
const betting_market_group_object* _betting_market_group;
};
class betting_market_create_evaluator : public evaluator<betting_market_create_evaluator>
{
public:
typedef betting_market_create_operation operation_type;
void_result do_evaluate( const betting_market_create_operation& o );
object_id_type do_apply( const betting_market_create_operation& o );
private:
betting_market_group_id_type _group_id;
};
class betting_market_update_evaluator : public evaluator<betting_market_update_evaluator>
{
public:
typedef betting_market_update_operation operation_type;
void_result do_evaluate( const betting_market_update_operation& o );
void_result do_apply( const betting_market_update_operation& o );
private:
const betting_market_object* _betting_market;
betting_market_group_id_type _group_id;
};
class bet_place_evaluator : public evaluator<bet_place_evaluator>
{
public:
typedef bet_place_operation operation_type;
void_result do_evaluate( const bet_place_operation& o );
object_id_type do_apply( const bet_place_operation& o );
private:
const betting_market_group_object* _betting_market_group;
const betting_market_object* _betting_market;
const chain_parameters* _current_params;
const asset_object* _asset;
share_type _stake_plus_fees;
};
class bet_cancel_evaluator : public evaluator<bet_cancel_evaluator>
{
public:
typedef bet_cancel_operation operation_type;
void_result do_evaluate( const bet_cancel_operation& o );
void_result do_apply( const bet_cancel_operation& o );
private:
const bet_object* _bet_to_cancel;
};
class betting_market_group_resolve_evaluator : public evaluator<betting_market_group_resolve_evaluator>
{
public:
typedef betting_market_group_resolve_operation operation_type;
void_result do_evaluate( const betting_market_group_resolve_operation& o );
void_result do_apply( const betting_market_group_resolve_operation& o );
private:
const betting_market_group_object* _betting_market_group;
};
class betting_market_group_cancel_unmatched_bets_evaluator : public evaluator<betting_market_group_cancel_unmatched_bets_evaluator>
{
public:
typedef betting_market_group_cancel_unmatched_bets_operation operation_type;
void_result do_evaluate( const betting_market_group_cancel_unmatched_bets_operation& o );
void_result do_apply( const betting_market_group_cancel_unmatched_bets_operation& o );
private:
const betting_market_group_object* _betting_market_group;
};
} } // graphene::chain

View file

@ -0,0 +1,713 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/betting_market.hpp>
#include <boost/multi_index/composite_key.hpp>
namespace graphene { namespace chain {
class betting_market_object;
class betting_market_group_object;
} }
namespace fc {
void to_variant(const graphene::chain::betting_market_object& betting_market_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::betting_market_object& betting_market_obj);
void to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj);
} //end namespace fc
namespace graphene { namespace chain {
FC_DECLARE_EXCEPTION(no_transition, 100000, "Invalid state transition");
class database;
struct by_event_id;
struct by_settling_time;
struct by_betting_market_group_id;
class betting_market_rules_object : public graphene::db::abstract_object< betting_market_rules_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = betting_market_rules_object_type;
internationalized_string_type name;
internationalized_string_type description;
};
class betting_market_group_object : public graphene::db::abstract_object< betting_market_group_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = betting_market_group_object_type;
betting_market_group_object();
betting_market_group_object(const betting_market_group_object& rhs);
~betting_market_group_object();
betting_market_group_object& operator=(const betting_market_group_object& rhs);
internationalized_string_type description;
event_id_type event_id;
betting_market_rules_id_type rules_id;
asset_id_type asset_id;
share_type total_matched_bets_amount;
bool never_in_play;
uint32_t delay_before_settling;
fc::optional<fc::time_point_sec> settling_time; // the time the payout will occur (set after grading)
bool bets_are_allowed() const {
return get_status() == betting_market_group_status::upcoming ||
get_status() == betting_market_group_status::in_play;
}
bool bets_are_delayed() const {
return get_status() == betting_market_group_status::in_play;
}
betting_market_group_status get_status() const;
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const betting_market_group_object& betting_market_group_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, betting_market_group_object& betting_market_group_obj );
friend void ::fc::to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_upcoming_event(database& db);
void on_in_play_event(database& db);
void on_frozen_event(database& db);
void on_closed_event(database& db, bool closed_by_event);
void on_graded_event(database& db);
void on_re_grading_event(database& db);
void on_settled_event(database& db);
void on_canceled_event(database& db, bool canceled_by_event);
void dispatch_new_status(database& db, betting_market_group_status new_status);
private:
class impl;
std::unique_ptr<impl> my;
};
class betting_market_object : public graphene::db::abstract_object< betting_market_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = betting_market_object_type;
betting_market_object();
betting_market_object(const betting_market_object& rhs);
~betting_market_object();
betting_market_object& operator=(const betting_market_object& rhs);
betting_market_group_id_type group_id;
internationalized_string_type description;
internationalized_string_type payout_condition;
// once the market is graded, this holds the proposed grading
// after settling/canceling, this is the actual grading
fc::optional<betting_market_resolution_type> resolution;
betting_market_status get_status() const;
void cancel_all_unmatched_bets(database& db) const;
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const betting_market_object& betting_market_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, betting_market_object& betting_market_obj );
friend void ::fc::to_variant(const graphene::chain::betting_market_object& betting_market_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::betting_market_object& betting_market_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_unresolved_event(database& db);
void on_frozen_event(database& db);
void on_closed_event(database& db);
void on_graded_event(database& db, betting_market_resolution_type new_grading);
void on_settled_event(database& db);
void on_canceled_event(database& db);
private:
class impl;
std::unique_ptr<impl> my;
};
class bet_object : public graphene::db::abstract_object< bet_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = bet_object_type;
account_id_type bettor_id;
betting_market_id_type betting_market_id;
asset amount_to_bet;
bet_multiplier_type backer_multiplier;
bet_type back_or_lay;
fc::optional<fc::time_point_sec> end_of_delay;
static share_type get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up = false);
// returns the amount of a bet that completely matches this bet
share_type get_approximate_matching_amount(bool round_up = false) const;
static share_type get_exact_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay);
share_type get_exact_matching_amount() const;
static std::pair<share_type, share_type> get_ratio(bet_multiplier_type backer_multiplier);
std::pair<share_type, share_type> get_ratio() const;
// returns the minimum amount this bet could have that could be matched at these odds
share_type get_minimum_matchable_amount() const;
// returns the minimum amount another user could bet to match this bet at these odds
share_type get_minimum_matching_amount() const;
};
class betting_market_position_object : public graphene::db::abstract_object< betting_market_position_object >
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_betting_market_position_object_type;
account_id_type bettor_id;
betting_market_id_type betting_market_id;
share_type pay_if_payout_condition;
share_type pay_if_not_payout_condition;
share_type pay_if_canceled;
share_type pay_if_not_canceled;
share_type fees_collected;
share_type reduce();
};
typedef multi_index_container<
betting_market_rules_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >
> > betting_market_rules_object_multi_index_type;
typedef generic_index<betting_market_rules_object, betting_market_rules_object_multi_index_type> betting_market_rules_object_index;
typedef multi_index_container<
betting_market_group_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_event_id>, member<betting_market_group_object, event_id_type, &betting_market_group_object::event_id> >,
ordered_non_unique< tag<by_settling_time>, member<betting_market_group_object, fc::optional<fc::time_point_sec>, &betting_market_group_object::settling_time> >
> > betting_market_group_object_multi_index_type;
typedef generic_index<betting_market_group_object, betting_market_group_object_multi_index_type> betting_market_group_object_index;
typedef multi_index_container<
betting_market_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_betting_market_group_id>, member<betting_market_object, betting_market_group_id_type, &betting_market_object::group_id> >
> > betting_market_object_multi_index_type;
typedef generic_index<betting_market_object, betting_market_object_multi_index_type> betting_market_object_index;
struct compare_bet_by_odds {
bool operator()(const bet_object& lhs, const bet_object& rhs) const
{
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0>
bool operator() (const std::tuple<T0>& lhs, const bet_object& rhs) const
{
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), rhs.end_of_delay, rhs.betting_market_id);
}
template<typename T0>
bool operator() (const bet_object& lhs, const std::tuple<T0>& rhs) const
{
return compare(lhs.end_of_delay, lhs.betting_market_id, fc::optional<time_point_sec>(), std::get<0>(rhs));
}
template<typename T0, typename T1>
bool operator() (const std::tuple<T0, T1>& lhs, const bet_object& rhs) const
{
return compare(fc::optional<fc::time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay);
}
template<typename T0, typename T1>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1>& rhs) const
{
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs));
}
template<typename T0, typename T1, typename T2>
bool operator() (const std::tuple<T0, T1, T2>& lhs, const bet_object& rhs) const
{
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs),
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier);
}
template<typename T0, typename T1, typename T2>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2>& rhs) const
{
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier,
fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs));
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const std::tuple<T0, T1, T2, T3>& lhs, const bet_object& rhs) const
{
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs), std::get<3>(lhs),
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2, T3>& rhs) const
{
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs), std::get<3>(rhs));
}
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
return lhs_betting_market_id < rhs_betting_market_id;
}
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
return lhs_bet_type < rhs_bet_type;
}
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
if (lhs_bet_type < rhs_bet_type)
return true;
if (lhs_bet_type > rhs_bet_type)
return false;
if (lhs_bet_type == bet_type::back)
return lhs_backer_multiplier < rhs_backer_multiplier;
else
return lhs_backer_multiplier > rhs_backer_multiplier;
}
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier, const bet_id_type& lhs_bet_id,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier, const bet_id_type& rhs_bet_id) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
if (*lhs_end_of_delay < *rhs_end_of_delay)
return true;
if (*lhs_end_of_delay > *rhs_end_of_delay)
return false;
// if both bets have the same delay, prefer the one
// that was placed first (lowest id)
return lhs_bet_id < rhs_bet_id;
}
// if neither bet was delayed
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
if (lhs_bet_type < rhs_bet_type)
return true;
if (lhs_bet_type > rhs_bet_type)
return false;
if (lhs_backer_multiplier < rhs_backer_multiplier)
return lhs_bet_type == bet_type::back;
if (lhs_backer_multiplier > rhs_backer_multiplier)
return lhs_bet_type == bet_type::lay;
return lhs_bet_id < rhs_bet_id;
}
};
struct compare_bet_by_bettor_then_odds {
bool operator()(const bet_object& lhs, const bet_object& rhs) const
{
return compare(lhs.bettor_id, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
rhs.bettor_id, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0>
bool operator() (const std::tuple<T0>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), rhs.bettor_id);
}
template<typename T0>
bool operator() (const bet_object& lhs, const std::tuple<T0>& rhs) const
{
return compare(lhs.bettor_id, std::get<0>(rhs));
}
template<typename T0, typename T1>
bool operator() (const std::tuple<T0, T1>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), rhs.bettor_id, rhs.betting_market_id);
}
template<typename T0, typename T1>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1>& rhs) const
{
return compare(lhs.bettor_id, lhs.betting_market_id, std::get<0>(rhs), std::get<1>(rhs));
}
template<typename T0, typename T1, typename T2>
bool operator() (const std::tuple<T0, T1, T2>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs),
rhs.bettor_id, rhs.betting_market_id, rhs.back_or_lay);
}
template<typename T0, typename T1, typename T2>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2>& rhs) const
{
return compare(lhs.bettor_id, lhs.betting_market_id, lhs.back_or_lay,
std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs));
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const std::tuple<T0, T1, T2, T3>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs), std::get<3>(lhs),
rhs.bettor_id, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier);
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2, T3>& rhs) const
{
return compare(lhs.bettor_id, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs), std::get<3>(rhs));
}
template<typename T0, typename T1, typename T2, typename T3, typename T4>
bool operator() (const std::tuple<T0, T1, T2, T3, T4>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs), std::get<3>(lhs), std::get<4>(lhs),
rhs.bettor_id, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0, typename T1, typename T2, typename T3, typename T4>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2, T3, T4>& rhs) const
{
return compare(lhs.bettor_id, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs), std::get<3>(rhs), std::get<4>(rhs));
}
bool compare(const account_id_type& lhs_bettor_id, const account_id_type& rhs_bettor_id) const
{
return lhs_bettor_id < rhs_bettor_id;
}
bool compare(const account_id_type& lhs_bettor_id, const betting_market_id_type& lhs_betting_market_id,
const account_id_type& rhs_bettor_id, const betting_market_id_type& rhs_betting_market_id) const
{
if (lhs_bettor_id < rhs_bettor_id)
return true;
if (lhs_bettor_id > rhs_bettor_id)
return false;
return lhs_betting_market_id < rhs_betting_market_id;
}
bool compare(const account_id_type& lhs_bettor_id, const betting_market_id_type& lhs_betting_market_id,
bet_type lhs_bet_type,
const account_id_type& rhs_bettor_id, const betting_market_id_type& rhs_betting_market_id,
bet_type rhs_bet_type) const
{
if (lhs_bettor_id < rhs_bettor_id)
return true;
if (lhs_bettor_id > rhs_bettor_id)
return false;
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
return lhs_bet_type < rhs_bet_type;
}
bool compare(const account_id_type& lhs_bettor_id, const betting_market_id_type& lhs_betting_market_id,
bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier,
const account_id_type& rhs_bettor_id, const betting_market_id_type& rhs_betting_market_id,
bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier) const
{
if (lhs_bettor_id < rhs_bettor_id)
return true;
if (lhs_bettor_id > rhs_bettor_id)
return false;
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
if (lhs_bet_type < rhs_bet_type)
return true;
if (lhs_bet_type > rhs_bet_type)
return false;
if (lhs_bet_type == bet_type::back)
return lhs_backer_multiplier < rhs_backer_multiplier;
else
return lhs_backer_multiplier > rhs_backer_multiplier;
}
bool compare(const account_id_type& lhs_bettor_id, const betting_market_id_type& lhs_betting_market_id,
bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier, const bet_id_type& lhs_bet_id,
const account_id_type& rhs_bettor_id, const betting_market_id_type& rhs_betting_market_id,
bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier, const bet_id_type& rhs_bet_id) const
{
if (lhs_bettor_id < rhs_bettor_id)
return true;
if (lhs_bettor_id > rhs_bettor_id)
return false;
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
if (lhs_bet_type < rhs_bet_type)
return true;
if (lhs_bet_type > rhs_bet_type)
return false;
if (lhs_backer_multiplier < rhs_backer_multiplier)
return lhs_bet_type == bet_type::back;
if (lhs_backer_multiplier > rhs_backer_multiplier)
return lhs_bet_type == bet_type::lay;
return lhs_bet_id < rhs_bet_id;
}
};
struct by_odds {};
struct by_bettor_and_odds {};
typedef multi_index_container<
bet_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_odds>, identity<bet_object>, compare_bet_by_odds >,
ordered_unique< tag<by_bettor_and_odds>, identity<bet_object>, compare_bet_by_bettor_then_odds > > > bet_object_multi_index_type;
typedef generic_index<bet_object, bet_object_multi_index_type> bet_object_index;
struct by_bettor_betting_market{};
struct by_betting_market_bettor{};
typedef multi_index_container<
betting_market_position_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_bettor_betting_market>,
composite_key<
betting_market_position_object,
member<betting_market_position_object, account_id_type, &betting_market_position_object::bettor_id>,
member<betting_market_position_object, betting_market_id_type, &betting_market_position_object::betting_market_id> > >,
ordered_unique< tag<by_betting_market_bettor>,
composite_key<
betting_market_position_object,
member<betting_market_position_object, betting_market_id_type, &betting_market_position_object::betting_market_id>,
member<betting_market_position_object, account_id_type, &betting_market_position_object::bettor_id> > >
> > betting_market_position_multi_index_type;
typedef generic_index<betting_market_position_object, betting_market_position_multi_index_type> betting_market_position_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const betting_market_object& betting_market_obj )
{
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<betting_market_object> >(s, betting_market_obj);
fc::raw::pack(s, betting_market_obj.id);
fc::raw::pack(s, betting_market_obj.group_id);
fc::raw::pack(s, betting_market_obj.description);
fc::raw::pack(s, betting_market_obj.payout_condition);
fc::raw::pack(s, betting_market_obj.resolution);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
betting_market_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, betting_market_object& betting_market_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<betting_market_object> >(s, betting_market_obj);
fc::raw::unpack(s, betting_market_obj.id);
fc::raw::unpack(s, betting_market_obj.group_id);
fc::raw::unpack(s, betting_market_obj.description);
fc::raw::unpack(s, betting_market_obj.payout_condition);
fc::raw::unpack(s, betting_market_obj.resolution);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
betting_market_obj.unpack_impl(stream);
return s;
}
template<typename Stream>
inline Stream& operator<<( Stream& s, const betting_market_group_object& betting_market_group_obj )
{
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<betting_market_group_object> >(s, betting_market_group_obj);
fc::raw::pack(s, betting_market_group_obj.id);
fc::raw::pack(s, betting_market_group_obj.description);
fc::raw::pack(s, betting_market_group_obj.event_id);
fc::raw::pack(s, betting_market_group_obj.rules_id);
fc::raw::pack(s, betting_market_group_obj.asset_id);
fc::raw::pack(s, betting_market_group_obj.total_matched_bets_amount);
fc::raw::pack(s, betting_market_group_obj.never_in_play);
fc::raw::pack(s, betting_market_group_obj.delay_before_settling);
fc::raw::pack(s, betting_market_group_obj.settling_time);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
betting_market_group_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, betting_market_group_object& betting_market_group_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<betting_market_group_object> >(s, betting_market_group_obj);
fc::raw::unpack(s, betting_market_group_obj.id);
fc::raw::unpack(s, betting_market_group_obj.description);
fc::raw::unpack(s, betting_market_group_obj.event_id);
fc::raw::unpack(s, betting_market_group_obj.rules_id);
fc::raw::unpack(s, betting_market_group_obj.asset_id);
fc::raw::unpack(s, betting_market_group_obj.total_matched_bets_amount);
fc::raw::unpack(s, betting_market_group_obj.never_in_play);
fc::raw::unpack(s, betting_market_group_obj.delay_before_settling);
fc::raw::unpack(s, betting_market_group_obj.settling_time);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
betting_market_group_obj.unpack_impl(stream);
return s;
}
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::betting_market_rules_object, (graphene::db::object), (name)(description) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_group_object, (graphene::db::object), (description) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_object, (graphene::db::object), (group_id) )
FC_REFLECT_DERIVED( graphene::chain::bet_object, (graphene::db::object), (bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_position_object, (graphene::db::object), (bettor_id)(betting_market_id)(pay_if_payout_condition)(pay_if_not_payout_condition)(pay_if_canceled)(pay_if_not_canceled)(fees_collected) )

View file

@ -151,7 +151,7 @@
#define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4
#define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3
#define GRAPHENE_CURRENT_DB_VERSION "BTS2.8"
#define GRAPHENE_CURRENT_DB_VERSION "PPY1.11"
#define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT)
@ -172,13 +172,45 @@
/// Represents the canonical account for specifying you will vote directly (as opposed to a proxy)
#define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5))
///
#define TOURNAMENT_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6))
#define GRAPHENE_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6))
/// Sentinel value used in the scheduler.
#define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0))
///@}
#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743))
#define GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE (3*GRAPHENE_1_PERCENT)
/**
* Betting-related constants.
*
* We store bet multipliers as fixed-precision uint32_t. These values are
* the maximum power-of-ten bet we can have on a "symmetric" market:
* (decimal) 1.0001 - 10001
* (fractional) 1:10000 - 10000:1
*/
///@{
/// betting odds (multipliers) are stored as fixed-precision, divide by this to get the actual multiplier
#define GRAPHENE_BETTING_ODDS_PRECISION 10000
/// the smallest bet multiplier we will accept
#define GRAPHENE_BETTING_MIN_MULTIPLIER 10001
/// the largest bet multiplier we will accept
#define GRAPHENE_BETTING_MAX_MULTIPLIER 100010000
///@}
#define GRAPHENE_DEFAULT_MIN_BET_MULTIPLIER 10100
#define GRAPHENE_DEFAULT_MAX_BET_MULTIPLIER 10000000
#define GRAPHENE_DEFAULT_PERMITTED_BETTING_ODDS_INCREMENTS { { 20000, 100}, /* <= 2: 0.01 */ \
{ 30000, 200}, /* <= 3: 0.02 */ \
{ 40000, 500}, /* <= 4: 0.05 */ \
{ 60000, 1000}, /* <= 6: 0.10 */ \
{ 100000, 2000}, /* <= 10: 0.20 */ \
{ 200000, 5000}, /* <= 20: 0.50 */ \
{ 300000, 10000}, /* <= 30: 1.00 */ \
{ 500000, 20000}, /* <= 50: 2.00 */ \
{ 1000000, 50000}, /* <= 100: 5.00 */ \
{ 10000000, 100000} } /* <= 1000: 10.00 */
#define GRAPHENE_DEFAULT_BETTING_PERCENT_FEE (2 * GRAPHENE_1_PERCENT)
#define GRAPHENE_DEFAULT_LIVE_BETTING_DELAY_TIME 5 // seconds
#define TOURNAMENT_MIN_ROUND_DELAY 0
#define TOURNAMENT_MAX_ROUND_DELAY 600
#define TOURNAMENT_MIN_TIME_PER_COMMIT_MOVE 0

View file

@ -171,8 +171,18 @@ namespace graphene { namespace chain {
*/
uint32_t push_applied_operation( const operation& op );
void set_applied_operation_result( uint32_t op_id, const operation_result& r );
// most plugins should use the const version of get_applied_operations
const vector<optional< operation_history_object > >& get_applied_operations()const;
// the account_history plugin uses the non-const version. When it decides to track an
// operation and assigns an operation_id to it, it will store that id into the operation
// history object so other plugins that evaluate later can reference it.
vector<optional< operation_history_object > >& get_applied_operations();
// the bookie plugin depends on change notifications that are skipped during normal replays
void force_slow_replays();
string to_pretty_string( const asset& a )const;
/**
@ -195,12 +205,18 @@ namespace graphene { namespace chain {
* Emitted After a block has been applied and committed. The callback
* should not yield and should execute quickly.
*/
fc::signal<void(const vector<object_id_type>&)> changed_objects;
fc::signal<void(const vector<object_id_type>&, const flat_set<account_id_type>&)> new_objects;
/**
* Emitted After a block has been applied and committed. The callback
* should not yield and should execute quickly.
*/
fc::signal<void(const vector<object_id_type>&, const flat_set<account_id_type>&)> changed_objects;
/** this signal is emitted any time an object is removed and contains a
* pointer to the last value of every object that was removed.
*/
fc::signal<void(const vector<const object*>&)> removed_objects;
fc::signal<void(const vector<object_id_type>&, const vector<const object*>&, const flat_set<account_id_type>&)> removed_objects;
//////////////////// db_witness_schedule.cpp ////////////////////
@ -369,6 +385,29 @@ namespace graphene { namespace chain {
asset max_settlement);
///@}
//////////////////// db_bet.cpp ////////////////////
/// @{ @group Betting Market Helpers
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
void cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market);
void cancel_all_unmatched_bets_on_betting_market_group(const betting_market_group_object& betting_market_group);
void validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
void resolve_betting_market_group(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
void settle_betting_market_group(const betting_market_group_object& betting_market_group);
void remove_completed_events();
/**
* @brief Process a new bet
* @param new_bet_object The new bet to process
* @return true if order was completely filled; false otherwise
*
* This function takes a new bet and attempts to match it with existing
* bets already on the books.
*/
bool place_bet(const bet_object& new_bet_object);
///@}
/**
* @return true if the order was completely filled and thus freed.
*/
@ -435,12 +474,14 @@ namespace graphene { namespace chain {
void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block);
void update_last_irreversible_block();
void clear_expired_transactions();
void place_delayed_bets();
void clear_expired_proposals();
void clear_expired_orders();
void update_expired_feeds();
void update_maintenance_flag( bool new_maintenance_flag );
void update_withdraw_permissions();
void update_tournaments();
void update_betting_markets(fc::time_point_sec current_block_time);
bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true );
///Steps performed only at maintenance intervals
@ -497,6 +538,7 @@ namespace graphene { namespace chain {
node_property_object _node_property_object;
fc::hash_ctr_rng<secret_hash_type, 20> _random_number_generator;
bool _slow_replays = false;
};
namespace detail

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
class event_create_evaluator : public evaluator<event_create_evaluator>
{
public:
typedef event_create_operation operation_type;
void_result do_evaluate( const event_create_operation& o );
object_id_type do_apply( const event_create_operation& o );
private:
event_group_id_type event_group_id;
};
class event_update_evaluator : public evaluator<event_update_evaluator>
{
public:
typedef event_update_operation operation_type;
void_result do_evaluate( const event_update_operation& o );
void_result do_apply( const event_update_operation& o );
private:
event_group_id_type event_group_id;
};
class event_update_status_evaluator : public evaluator<event_update_status_evaluator>
{
public:
typedef event_update_status_operation operation_type;
void_result do_evaluate( const event_update_status_operation& o );
void_result do_apply( const event_update_status_operation& o );
private:
const event_object* _event_to_update = nullptr;
};
} } // graphene::chain

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
class event_group_create_evaluator : public evaluator<event_group_create_evaluator>
{
public:
typedef event_group_create_operation operation_type;
void_result do_evaluate( const event_group_create_operation& o );
object_id_type do_apply( const event_group_create_operation& o );
private:
sport_id_type sport_id;
};
class event_group_update_evaluator : public evaluator<event_group_update_evaluator>
{
public:
typedef event_group_update_operation operation_type;
void_result do_evaluate( const event_group_update_operation& o );
void_result do_apply( const event_group_update_operation& o );
private:
sport_id_type sport_id;
};
} } // graphene::chain

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
class database;
struct by_sport_id;
class event_group_object : public graphene::db::abstract_object< event_group_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = event_group_object_type;
internationalized_string_type name;
sport_id_type sport_id;
};
typedef multi_index_container<
event_group_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_sport_id>, member< event_group_object, sport_id_type, &event_group_object::sport_id > > >
> event_group_object_multi_index_type;
typedef generic_index<event_group_object, event_group_object_multi_index_type> event_group_object_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::event_group_object, (graphene::db::object), (name)(sport_id) )

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/event.hpp>
namespace graphene { namespace chain {
class event_object;
} }
namespace fc {
void to_variant(const graphene::chain::event_object& event_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::event_object& event_obj);
} //end namespace fc
namespace graphene { namespace chain {
class database;
class event_object : public graphene::db::abstract_object< event_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = event_object_type;
event_object();
event_object(const event_object& rhs);
~event_object();
event_object& operator=(const event_object& rhs);
internationalized_string_type name;
internationalized_string_type season;
optional<time_point_sec> start_time;
event_group_id_type event_group_id;
bool at_least_one_betting_market_group_settled;
event_status get_status() const;
vector<string> scores;
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const event_object& event_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, event_object& event_obj );
friend void ::fc::to_variant(const graphene::chain::event_object& event_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::event_object& event_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_upcoming_event(database& db);
void on_in_progress_event(database& db);
void on_frozen_event(database& db);
void on_finished_event(database& db);
void on_canceled_event(database& db);
void on_settled_event(database& db);
void on_betting_market_group_resolved(database& db, betting_market_group_id_type resolved_group, bool was_canceled);
void on_betting_market_group_closed(database& db, betting_market_group_id_type closed_group);
void dispatch_new_status(database& db, event_status new_status);
private:
class impl;
std::unique_ptr<impl> my;
};
struct by_event_group_id;
struct by_event_status;
typedef multi_index_container<
event_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_event_group_id>, member< event_object, event_group_id_type, &event_object::event_group_id > >,
ordered_non_unique< tag<by_event_status>, const_mem_fun< event_object, event_status, &event_object::get_status > > > > event_object_multi_index_type;
typedef generic_index<event_object, event_object_multi_index_type> event_object_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const event_object& event_obj )
{
fc_elog(fc::logger::get("event"), "In event_obj to_raw");
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<event_object> >(s, event_obj);
fc::raw::pack(s, event_obj.id);
fc::raw::pack(s, event_obj.name);
fc::raw::pack(s, event_obj.season);
fc::raw::pack(s, event_obj.start_time);
fc::raw::pack(s, event_obj.event_group_id);
fc::raw::pack(s, event_obj.at_least_one_betting_market_group_settled);
fc::raw::pack(s, event_obj.scores);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
event_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, event_object& event_obj )
{
fc_elog(fc::logger::get("event"), "In event_obj from_raw");
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<event_object> >(s, event_obj);
fc::raw::unpack(s, event_obj.id);
fc::raw::unpack(s, event_obj.name);
fc::raw::unpack(s, event_obj.season);
fc::raw::unpack(s, event_obj.start_time);
fc::raw::unpack(s, event_obj.event_group_id);
fc::raw::unpack(s, event_obj.at_least_one_betting_market_group_settled);
fc::raw::unpack(s, event_obj.scores);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
event_obj.unpack_impl(stream);
return s;
}
} } // graphene::chain
FC_REFLECT(graphene::chain::event_object, (name))

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
class database;
class global_betting_statistics_object : public graphene::db::abstract_object< global_betting_statistics_object >
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_global_betting_statistics_object_type;
uint32_t number_of_active_events;
map<asset_id_type, share_type> total_amount_staked;
};
typedef multi_index_container<
global_betting_statistics_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > > > global_betting_statistics_object_multi_index_type;
typedef generic_index<global_betting_statistics_object, global_betting_statistics_object_multi_index_type> global_betting_statistics_object_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::global_betting_statistics_object, (graphene::db::object), (number_of_active_events)(total_amount_staked) )

View file

@ -120,6 +120,13 @@ class call_order_object : public abstract_object<call_order_object>
share_type collateral; ///< call_price.base.asset_id, access via get_collateral
share_type debt; ///< call_price.quote.asset_id, access via get_collateral
price call_price; ///< Debt / Collateral
pair<asset_id_type,asset_id_type> get_market()const
{
auto tmp = std::make_pair( call_price.base.asset_id, call_price.quote.asset_id );
if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );
return tmp;
}
};
/**

View file

@ -102,6 +102,7 @@ namespace graphene { namespace chain {
struct by_id;
struct by_seq;
struct by_op;
struct by_opid;
typedef multi_index_container<
account_transaction_history_object,
indexed_by<
@ -117,6 +118,9 @@ typedef multi_index_container<
member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>,
member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id>
>
>,
ordered_non_unique< tag<by_opid>,
member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id>
>
>
> account_transaction_history_multi_index_type;

View file

@ -50,6 +50,7 @@ class proposal_object : public abstract_object<proposal_object>
flat_set<account_id_type> required_owner_approvals;
flat_set<account_id_type> available_owner_approvals;
flat_set<public_key_type> available_key_approvals;
account_id_type proposer;
bool is_authorized_to_execute(database& db)const;
};
@ -93,4 +94,4 @@ typedef generic_index<proposal_object, proposal_multi_index_container> proposal_
FC_REFLECT_DERIVED( graphene::chain::proposal_object, (graphene::chain::object),
(expiration_time)(review_period_time)(proposed_transaction)(required_active_approvals)
(available_active_approvals)(required_owner_approvals)(available_owner_approvals)
(available_key_approvals) )
(available_key_approvals)(proposer) )

View file

@ -264,7 +264,7 @@ namespace graphene { namespace chain {
} } // graphene::chain
FC_REFLECT(graphene::chain::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions))
FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing)
// FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing)
FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing,
(no_listing)(white_listed)(black_listed)(white_and_black_listed))

View file

@ -291,7 +291,7 @@ namespace graphene { namespace chain {
asset_dividend_distribution_operation() {}
asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id,
const account_id_type& account_id,
const flat_set<asset>& amounts) :
const vector<asset>& amounts) :
dividend_asset_id(dividend_asset_id),
account_id(account_id),
amounts(amounts)
@ -323,7 +323,7 @@ namespace graphene { namespace chain {
account_id_type account_id;
/// The amounts received
flat_set<asset> amounts;
vector<asset> amounts;
extensions_type extensions;

View file

@ -132,5 +132,5 @@ void add_authority_accounts(
} } // namespace graphene::chain
FC_REFLECT( graphene::chain::authority, (weight_threshold)(account_auths)(key_auths)(address_auths) )
FC_REFLECT_TYPENAME( graphene::chain::authority::classification )
// FC_REFLECT_TYPENAME( graphene::chain::authority::classification )
FC_REFLECT_ENUM( graphene::chain::authority::classification, (owner)(active)(key) )

View file

@ -0,0 +1,493 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
struct betting_market_rules_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* A short name for the rules, like "Premier League Rules 1.0", probably not
* displayed to the user
*/
internationalized_string_type name;
/**
* The full text of the rules to be displayed to the user. As yet, there is
* no standard format (html, markdown, etc)
*/
internationalized_string_type description;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct betting_market_rules_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
betting_market_rules_id_type betting_market_rules_id;
fc::optional<internationalized_string_type> new_name;
fc::optional<internationalized_string_type> new_description;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
enum class betting_market_status
{
unresolved, /// no grading has been published for this betting market
frozen, /// bets are suspended, no bets allowed
graded, /// grading of win or not_win has been published
canceled, /// the betting market is canceled, no further bets are allowed
settled, /// the betting market has been paid out
BETTING_MARKET_STATUS_COUNT
};
/**
* The status of a betting market group. This controls the behavior of the betting
* markets in the group.
*/
enum class betting_market_group_status
{
upcoming, /// betting markets are accepting bets, will never go "in_play"
in_play, /// betting markets are delaying bets
closed, /// betting markets are no longer accepting bets
graded, /// witnesses have published win/not win for the betting markets
re_grading, /// initial win/not win grading has been challenged
settled, /// paid out
frozen, /// betting markets are not accepting bets
canceled, /// canceled
BETTING_MARKET_GROUP_STATUS_COUNT
};
struct betting_market_group_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* A description of the betting market, like "Moneyline", "Over/Under 180",
* used for display
*/
internationalized_string_type description;
/**
* This can be a event_id_type, or a
* relative object id that resolves to a event_id_type
*/
object_id_type event_id;
/**
* This can be a betting_market_rules_id_type, or a
* relative object id that resolves to a betting_market_rules_id_type
*/
object_id_type rules_id;
/**
* The asset used to place bets for all betting markets in this group
*/
asset_id_type asset_id;
/**
* If true, this market will never go "in-play"
*/
bool never_in_play;
/**
* After a grading has been published, the blockchain will wait this many
* seconds before settling (paying the winners).
* If the published grading is flagged (challenged) during this period,
* settling will be delayed indefinitely until the betting market
* group is re-graded (not implemented)
*/
uint32_t delay_before_settling;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct betting_market_group_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
betting_market_group_id_type betting_market_group_id;
optional<internationalized_string_type> new_description;
optional<object_id_type> new_rules_id;
optional<betting_market_group_status> status;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct betting_market_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* This can be a betting_market_group_id_type, or a
* relative object id that resolves to a betting_market_group_id_type
*/
object_id_type group_id;
internationalized_string_type description;
internationalized_string_type payout_condition;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct betting_market_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
betting_market_id_type betting_market_id;
optional<object_id_type> new_group_id;
optional<internationalized_string_type> new_description;
optional<internationalized_string_type> new_payout_condition;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
enum class betting_market_resolution_type {
win,
not_win,
cancel,
BETTING_MARKET_RESOLUTION_COUNT
};
struct betting_market_group_resolve_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
betting_market_group_id_type betting_market_group_id;
std::map<betting_market_id_type, betting_market_resolution_type> resolutions;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct betting_market_group_resolved_operation : public base_operation
{
struct fee_parameters_type {};
account_id_type bettor_id;
betting_market_group_id_type betting_market_group_id;
std::map<betting_market_id_type, betting_market_resolution_type> resolutions;
share_type winnings; // always the asset type of the betting market group
share_type fees_paid; // always the asset type of the betting market group
asset fee; // unused in a virtual operation
betting_market_group_resolved_operation() {}
betting_market_group_resolved_operation(account_id_type bettor_id,
betting_market_group_id_type betting_market_group_id,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
share_type winnings,
share_type fees_paid) :
bettor_id(bettor_id),
betting_market_group_id(betting_market_group_id),
resolutions(resolutions),
winnings(winnings),
fees_paid(fees_paid)
{
}
account_id_type fee_payer()const { return bettor_id; }
void validate()const { FC_ASSERT(false, "virtual operation"); }
/// This is a virtual operation; there is no fee
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
struct betting_market_group_cancel_unmatched_bets_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
betting_market_group_id_type betting_market_group_id;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
enum class bet_type { back, lay };
struct bet_place_operation : public base_operation
{
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; // fixed fee charged upon placing the bet
};
asset fee;
account_id_type bettor_id;
betting_market_id_type betting_market_id;
/// the bettor's stake
asset amount_to_bet;
// decimal odds as seen by the backer, even if this is a lay bet.
// this is a fixed-precision number scaled by GRAPHENE_BETTING_ODDS_PRECISION.
//
// For example, an even 1/1 bet would be decimal odds 2.0, so backer_multiplier
// would be 2 * GRAPHENE_BETTING_ODDS_PRECISION.
bet_multiplier_type backer_multiplier;
bet_type back_or_lay;
extensions_type extensions;
account_id_type fee_payer()const { return bettor_id; }
void validate()const;
};
/**
* virtual op generated when a bet is matched
*/
struct bet_matched_operation : public base_operation
{
struct fee_parameters_type {};
bet_matched_operation(){}
bet_matched_operation(account_id_type bettor_id, bet_id_type bet_id,
asset amount_bet,
bet_multiplier_type backer_multiplier,
share_type guaranteed_winnings_returned) :
bettor_id(bettor_id),
bet_id(bet_id),
amount_bet(amount_bet),
backer_multiplier(backer_multiplier),
guaranteed_winnings_returned(guaranteed_winnings_returned)
{}
account_id_type bettor_id;
bet_id_type bet_id;
asset amount_bet;
bet_multiplier_type backer_multiplier; // the actual odds received
share_type guaranteed_winnings_returned; // same asset type as amount_bet
asset fee; // unimportant for a virtual op
account_id_type fee_payer()const { return bettor_id; }
void validate()const { FC_ASSERT(false, "virtual operation"); }
/// This is a virtual operation; there is no fee
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
struct bet_cancel_operation : public base_operation
{
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
//uint64_t fee = 0;
};
asset fee;
/// the bettor who is cancelling the bet
account_id_type bettor_id;
/// the bet being canceled
bet_id_type bet_to_cancel;
extensions_type extensions;
account_id_type fee_payer()const { return bettor_id; }
void validate()const;
};
/**
* virtual op generated when a bet is canceled
*/
struct bet_canceled_operation : public base_operation
{
struct fee_parameters_type {};
bet_canceled_operation(){}
bet_canceled_operation(account_id_type bettor_id, bet_id_type bet_id,
asset stake_returned) :
bettor_id(bettor_id),
bet_id(bet_id),
stake_returned(stake_returned)
{}
account_id_type bettor_id;
bet_id_type bet_id;
asset stake_returned;
asset fee; // unimportant for a virtual op
account_id_type fee_payer()const { return bettor_id; }
void validate()const { FC_ASSERT(false, "virtual operation"); }
/// This is a virtual operation; there is no fee
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
/**
* virtual op generated when a bet amount is rounded down to an amount that can
* match evenly at a given odds (the blockchain does this automatically at the time
* the bet is placed on the order books). (note: there is no way a user can adjust their bet
* after placing it, aside from canceling the bet and placing a new one)
*/
struct bet_adjusted_operation : public base_operation
{
struct fee_parameters_type {};
bet_adjusted_operation(){}
bet_adjusted_operation(account_id_type bettor_id, bet_id_type bet_id,
asset stake_returned) :
bettor_id(bettor_id),
bet_id(bet_id),
stake_returned(stake_returned)
{}
account_id_type bettor_id;
bet_id_type bet_id;
asset stake_returned;
asset fee; // unimportant for a virtual op
account_id_type fee_payer()const { return bettor_id; }
void validate()const { FC_ASSERT(false, "virtual operation"); }
/// This is a virtual operation; there is no fee
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
} }
FC_REFLECT( graphene::chain::betting_market_rules_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_rules_create_operation,
(fee)(name)(description)(extensions) )
FC_REFLECT( graphene::chain::betting_market_rules_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_rules_update_operation,
(fee)(new_name)(new_description)(extensions)(betting_market_rules_id) )
FC_REFLECT_ENUM( graphene::chain::betting_market_status,
(unresolved)
(frozen)
(graded)
(canceled)
(settled)
(BETTING_MARKET_STATUS_COUNT) )
FC_REFLECT_ENUM( graphene::chain::betting_market_group_status,
(upcoming)
(in_play)
(closed)
(graded)
(re_grading)
(settled)
(frozen)
(canceled)
(BETTING_MARKET_GROUP_STATUS_COUNT) )
FC_REFLECT( graphene::chain::betting_market_group_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_create_operation,
(fee)(description)(event_id)(rules_id)(asset_id)
(never_in_play)(delay_before_settling)
(extensions) )
FC_REFLECT( graphene::chain::betting_market_group_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_update_operation,
(fee)(betting_market_group_id)(new_description)(new_rules_id)(status)(extensions) )
FC_REFLECT( graphene::chain::betting_market_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_create_operation,
(fee)(group_id)(description)(payout_condition)(extensions) )
FC_REFLECT( graphene::chain::betting_market_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_update_operation,
(fee)(betting_market_id)(new_group_id)(new_description)(new_payout_condition)(extensions) )
FC_REFLECT_ENUM( graphene::chain::betting_market_resolution_type, (win)(not_win)(cancel)(BETTING_MARKET_RESOLUTION_COUNT) )
FC_REFLECT( graphene::chain::betting_market_group_resolve_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_resolve_operation,
(fee)(betting_market_group_id)(resolutions)(extensions) )
FC_REFLECT( graphene::chain::betting_market_group_resolved_operation::fee_parameters_type, )
FC_REFLECT( graphene::chain::betting_market_group_resolved_operation,
(bettor_id)(betting_market_group_id)(resolutions)(winnings)(fees_paid)(fee) )
FC_REFLECT( graphene::chain::betting_market_group_cancel_unmatched_bets_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_cancel_unmatched_bets_operation,
(fee)(betting_market_group_id)(extensions) )
FC_REFLECT_ENUM( graphene::chain::bet_type, (back)(lay) )
FC_REFLECT( graphene::chain::bet_place_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bet_place_operation,
(fee)(bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(extensions) )
FC_REFLECT( graphene::chain::bet_matched_operation::fee_parameters_type, )
FC_REFLECT( graphene::chain::bet_matched_operation, (bettor_id)(bet_id)(amount_bet)(backer_multiplier)(guaranteed_winnings_returned) )
FC_REFLECT( graphene::chain::bet_cancel_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bet_cancel_operation, (fee) (bettor_id) (bet_to_cancel) (extensions) )
FC_REFLECT( graphene::chain::bet_canceled_operation::fee_parameters_type, )
FC_REFLECT( graphene::chain::bet_canceled_operation, (bettor_id)(bet_id)(stake_returned) )
FC_REFLECT( graphene::chain::bet_adjusted_operation::fee_parameters_type, )
FC_REFLECT( graphene::chain::bet_adjusted_operation, (bettor_id)(bet_id)(stake_returned) )

View file

@ -69,7 +69,12 @@ namespace graphene { namespace chain {
uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings
uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling
uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH;
//uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM; ///< 0 shuffled, 1 scheduled
uint16_t betting_rake_fee_percentage = GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE; ///< part of prize paid into the dividend account for the core token holders
bet_multiplier_type min_bet_multiplier = GRAPHENE_DEFAULT_MIN_BET_MULTIPLIER;
bet_multiplier_type max_bet_multiplier = GRAPHENE_DEFAULT_MAX_BET_MULTIPLIER;
flat_map<bet_multiplier_type, bet_multiplier_type> permitted_betting_odds_increments = GRAPHENE_DEFAULT_PERMITTED_BETTING_ODDS_INCREMENTS;
uint16_t live_betting_delay_time = GRAPHENE_DEFAULT_LIVE_BETTING_DELAY_TIME; ///< delayed bets
//uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM; ///< 0 shuffled, 1 scheduled
uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM; ///< 0 shuffled, 1 scheduled
/* rps tournament parameters constraints */
uint32_t min_round_delay = TOURNAMENT_MIN_ROUND_DELAY; ///< miniaml delay between games
@ -122,7 +127,12 @@ FC_REFLECT( graphene::chain::chain_parameters,
(accounts_per_fee_scale)
(account_fee_scale_bitshifts)
(max_authority_depth)
(min_bet_multiplier)
(max_bet_multiplier)
(betting_rake_fee_percentage)
(permitted_betting_odds_increments)
(witness_schedule_algorithm)
(live_betting_delay_time)
(min_round_delay)
(max_round_delay)
(min_time_per_commit_move)

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
struct event_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* The name of the event
*/
internationalized_string_type name;
internationalized_string_type season;
optional<time_point_sec> start_time;
/**
* This can be a event_group_id_type, or a
* relative object id that resolves to a event_group_id_type
*/
object_id_type event_group_id;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
/**
* The status of an event. This is used to display in the UI, and setting
* the event's status to certain values will propagate down to the
* betting market groups in the event:
* - when set to `in_progress`, all betting market groups are set to `in_play`
* - when set to `completed`, all betting market groups are set to `closed`
* - when set to `frozen`, all betting market groups are set to `frozen`
* - when set to `canceled`, all betting market groups are set to `canceled`
*/
enum class event_status
{
upcoming, /// Event has not started yet, betting is allowed
in_progress, /// Event is in progress, if "in-play" betting is enabled, bets will be delayed
frozen, /// Betting is temporarily disabled
finished, /// Event has finished, no more betting allowed
canceled, /// Event has been canceled, all betting markets have been canceled
settled, /// All betting markets have been paid out
STATUS_COUNT
};
struct event_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
event_id_type event_id;
optional<object_id_type> new_event_group_id;
optional<internationalized_string_type> new_name;
optional<internationalized_string_type> new_season;
optional<time_point_sec> new_start_time;
optional<event_status> new_status;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
/**
* The current (or final) score of an event.
* This is only used for display to the user, witnesses must resolve each
* betting market explicitly.
* These are free-form strings that we assume will make sense to the user.
* For a game like football, this may be a score like "3". For races,
* it could be a time like "1:53.4".
*/
struct event_update_status_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/// the id of the event to update
event_id_type event_id;
/**
* the new status of the event (if the status hasn't changed, the creator
* of this operation must still set `status` to the event's current status)
*/
event_status status;
/*
* scores for each competitor stored in same order as competitors in event_object
*/
vector<string> scores;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
} }
FC_REFLECT( graphene::chain::event_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_create_operation,
(fee)(name)(season)(start_time)(event_group_id)(extensions) )
FC_REFLECT( graphene::chain::event_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_update_operation,
(fee)(event_id)(new_event_group_id)(new_name)(new_season)(new_start_time)(new_status)(extensions) )
FC_REFLECT_ENUM( graphene::chain::event_status, (upcoming)(in_progress)(frozen)(finished)(canceled)(settled)(STATUS_COUNT) )
FC_REFLECT( graphene::chain::event_update_status_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_update_status_operation,
(fee)(event_id)(status)(scores)(extensions) )

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
struct event_group_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* The name of the event_group
*/
internationalized_string_type name;
/**
* This can be a sport_id_type, or a
* relative object id that resolves to a sport_id_type
*/
object_id_type sport_id;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct event_group_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
event_group_id_type event_group_id;
/**
* This can be a sport_id_type, or a
* relative object id that resolves to a sport_id_type
*/
optional<object_id_type> new_sport_id;
optional<internationalized_string_type> new_name;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
} }
FC_REFLECT( graphene::chain::event_group_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_group_create_operation,
(fee)(name)(sport_id)(extensions) )
FC_REFLECT( graphene::chain::event_group_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_group_update_operation,
(fee)(new_sport_id)(new_name)(event_group_id)(extensions) )

View file

@ -38,6 +38,10 @@
#include <graphene/chain/protocol/withdraw_permission.hpp>
#include <graphene/chain/protocol/witness.hpp>
#include <graphene/chain/protocol/worker.hpp>
#include <graphene/chain/protocol/sport.hpp>
#include <graphene/chain/protocol/event_group.hpp>
#include <graphene/chain/protocol/event.hpp>
#include <graphene/chain/protocol/betting_market.hpp>
#include <graphene/chain/protocol/tournament.hpp>
namespace graphene { namespace chain {
@ -92,14 +96,35 @@ namespace graphene { namespace chain {
transfer_from_blind_operation,
asset_settle_cancel_operation, // VIRTUAL
asset_claim_fees_operation,
fba_distribute_operation, // VIRTUAL
fba_distribute_operation, // VIRTUAL
tournament_create_operation,
tournament_join_operation,
game_move_operation,
asset_update_dividend_operation,
asset_dividend_distribution_operation, // VIRTUAL
tournament_payout_operation, // VIRTUAL
tournament_leave_operation
tournament_leave_operation,
sport_create_operation,
sport_update_operation,
event_group_create_operation,
event_group_update_operation,
event_create_operation,
event_update_operation,
betting_market_rules_create_operation,
betting_market_rules_update_operation,
betting_market_group_create_operation,
betting_market_create_operation,
bet_place_operation,
betting_market_group_resolve_operation,
betting_market_group_resolved_operation, // VIRTUAL
bet_adjusted_operation, // VIRTUAL
betting_market_group_cancel_unmatched_bets_operation,
bet_matched_operation, // VIRTUAL
bet_cancel_operation,
bet_canceled_operation, // VIRTUAL
betting_market_group_update_operation,
betting_market_update_operation,
event_update_status_operation
> operation;
/// @} // operations group

View file

@ -94,7 +94,7 @@ namespace graphene { namespace chain {
FC_REFLECT( graphene::chain::rock_paper_scissors_game_options, (insurance_enabled)(time_per_commit_move)(time_per_reveal_move)(number_of_gestures) )
FC_REFLECT_TYPENAME( graphene::chain::rock_paper_scissors_gesture)
// FC_REFLECT_TYPENAME( graphene::chain::rock_paper_scissors_gesture)
FC_REFLECT_ENUM( graphene::chain::rock_paper_scissors_gesture,
(rock)
(paper)

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
struct sport_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
/**
* The name of the sport
*/
internationalized_string_type name;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
struct sport_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
asset fee;
sport_id_type sport_id;
optional<internationalized_string_type> new_name;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
void validate()const;
};
} }
FC_REFLECT( graphene::chain::sport_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::sport_create_operation,
(fee)(name)(extensions) )
FC_REFLECT( graphene::chain::sport_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::sport_update_operation,
(fee)(sport_id)(new_name)(extensions) )

View file

@ -134,6 +134,13 @@ namespace graphene { namespace chain {
vesting_balance_object_type,
worker_object_type,
balance_object_type,
sport_object_type,
event_group_object_type,
event_object_type,
betting_market_rules_object_type,
betting_market_group_object_type,
betting_market_object_type,
bet_object_type,
tournament_object_type,
tournament_details_object_type,
match_object_type,
@ -160,6 +167,8 @@ namespace graphene { namespace chain {
impl_special_authority_object_type,
impl_buyback_object_type,
impl_fba_accumulator_object_type,
impl_betting_market_position_object_type,
impl_global_betting_statistics_object_type,
impl_asset_dividend_data_type,
impl_pending_dividend_payout_balance_for_holder_object_type,
impl_distributed_dividend_balance_data_type
@ -182,6 +191,13 @@ namespace graphene { namespace chain {
class worker_object;
class balance_object;
class blinded_balance_object;
class sport_object;
class event_group_object;
class event_object;
class betting_market_rules_object;
class betting_market_group_object;
class betting_market_object;
class bet_object;
class tournament_object;
class tournament_details_object;
class match_object;
@ -201,6 +217,13 @@ namespace graphene { namespace chain {
typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type;
typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type;
typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type;
typedef object_id< protocol_ids, sport_object_type, sport_object> sport_id_type;
typedef object_id< protocol_ids, event_group_object_type, event_group_object> event_group_id_type;
typedef object_id< protocol_ids, event_object_type, event_object> event_id_type;
typedef object_id< protocol_ids, betting_market_rules_object_type, betting_market_rules_object> betting_market_rules_id_type;
typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type;
typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type;
typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type;
typedef object_id< protocol_ids, tournament_object_type, tournament_object> tournament_id_type;
typedef object_id< protocol_ids, tournament_details_object_type, tournament_details_object> tournament_details_id_type;
typedef object_id< protocol_ids, match_object_type, match_object> match_id_type;
@ -222,6 +245,8 @@ namespace graphene { namespace chain {
class special_authority_object;
class buyback_object;
class fba_accumulator_object;
class betting_market_position_object;
class global_betting_statistics_object;
class tournament_details_object;
class asset_dividend_data_object;
class pending_dividend_payout_balance_for_holder_object;
@ -247,6 +272,8 @@ namespace graphene { namespace chain {
typedef object_id< implementation_ids, impl_special_authority_object_type, special_authority_object > special_authority_id_type;
typedef object_id< implementation_ids, impl_buyback_object_type, buyback_object > buyback_id_type;
typedef object_id< implementation_ids, impl_fba_accumulator_object_type, fba_accumulator_object > fba_accumulator_id_type;
typedef object_id< implementation_ids, impl_betting_market_position_object_type, betting_market_position_object > betting_market_position_id_type;
typedef object_id< implementation_ids, impl_global_betting_statistics_object_type, global_betting_statistics_object > global_betting_statistics_id_type;
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
typedef fc::ripemd160 block_id_type;
@ -325,6 +352,10 @@ namespace graphene { namespace chain {
friend bool operator == ( const extended_private_key_type& p1, const extended_private_key_type& p2);
friend bool operator != ( const extended_private_key_type& p1, const extended_private_key_type& p2);
};
typedef flat_map<std::string, std::string> internationalized_string_type;
typedef uint32_t bet_multiplier_type;
} } // graphene::chain
namespace fc
@ -361,6 +392,13 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(vesting_balance_object_type)
(worker_object_type)
(balance_object_type)
(sport_object_type)
(event_group_object_type)
(event_object_type)
(betting_market_rules_object_type)
(betting_market_group_object_type)
(betting_market_object_type)
(bet_object_type)
(tournament_object_type)
(tournament_details_object_type)
(match_object_type)
@ -385,6 +423,8 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
(impl_special_authority_object_type)
(impl_buyback_object_type)
(impl_fba_accumulator_object_type)
(impl_betting_market_position_object_type)
(impl_global_betting_statistics_object_type)
(impl_asset_dividend_data_type)
(impl_pending_dividend_payout_balance_for_holder_object_type)
(impl_distributed_dividend_balance_data_type)
@ -406,6 +446,13 @@ FC_REFLECT_TYPENAME( graphene::chain::withdraw_permission_id_type )
FC_REFLECT_TYPENAME( graphene::chain::vesting_balance_id_type )
FC_REFLECT_TYPENAME( graphene::chain::worker_id_type )
FC_REFLECT_TYPENAME( graphene::chain::balance_id_type )
FC_REFLECT_TYPENAME( graphene::chain::sport_id_type )
FC_REFLECT_TYPENAME( graphene::chain::event_group_id_type )
FC_REFLECT_TYPENAME( graphene::chain::event_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_rules_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_group_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_id_type )
FC_REFLECT_TYPENAME( graphene::chain::bet_id_type )
FC_REFLECT_TYPENAME( graphene::chain::tournament_id_type )
FC_REFLECT_TYPENAME( graphene::chain::global_property_id_type )
FC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_id_type )
@ -420,6 +467,8 @@ FC_REFLECT_TYPENAME( graphene::chain::budget_record_id_type )
FC_REFLECT_TYPENAME( graphene::chain::special_authority_id_type )
FC_REFLECT_TYPENAME( graphene::chain::buyback_id_type )
FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_position_id_type )
FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type )
FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type )
FC_REFLECT( graphene::chain::void_t, )

View file

@ -146,7 +146,6 @@ void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo );
} // fc
FC_REFLECT_TYPENAME( graphene::chain::vote_id_type::vote_type )
FC_REFLECT_TYPENAME( fc::flat_set<graphene::chain::vote_id_type> )
FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) )

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
class sport_create_evaluator : public evaluator<sport_create_evaluator>
{
public:
typedef sport_create_operation operation_type;
void_result do_evaluate( const sport_create_operation& o );
object_id_type do_apply( const sport_create_operation& o );
};
class sport_update_evaluator : public evaluator<sport_update_evaluator>
{
public:
typedef sport_update_operation operation_type;
void_result do_evaluate( const sport_update_operation& o );
void_result do_apply( const sport_update_operation& o );
};
} } // graphene::chain

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
class database;
class sport_object : public graphene::db::abstract_object< sport_object >
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = sport_object_type;
internationalized_string_type name;
};
typedef multi_index_container<
sport_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > > > sport_object_multi_index_type;
typedef generic_index<sport_object, sport_object_multi_index_type> sport_object_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::sport_object, (graphene::db::object), (name) )

View file

@ -35,6 +35,7 @@
#include <graphene/chain/protocol/market.hpp>
#include <fc/uint128.hpp>
#include <fc/smart_ref_impl.hpp>
namespace graphene { namespace chain {
void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op)

View file

@ -85,6 +85,7 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati
const proposal_object& proposal = d.create<proposal_object>([&](proposal_object& proposal) {
_proposed_trx.expiration = o.expiration_time;
proposal.proposed_transaction = _proposed_trx;
proposal.proposer = o.fee_paying_account;
proposal.expiration_time = o.expiration_time;
if( o.review_period_seconds )
proposal.review_period_time = o.expiration_time - *o.review_period_seconds;

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/protocol/betting_market.hpp>
namespace graphene { namespace chain {
void betting_market_rules_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_rules_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_group_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_group_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_group_resolve_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void betting_market_group_cancel_unmatched_bets_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void bet_place_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void bet_cancel_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
} } // graphene::chain

View file

@ -21,10 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/p2p/peer_connection.hpp>
#include <graphene/chain/protocol/competitor.hpp>
namespace graphene { namespace p2p {
namespace graphene { namespace chain {
} } //graphene::p2p
void competitor_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
} } // graphene::chain

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/protocol/event.hpp>
namespace graphene { namespace chain {
void event_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void event_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void event_update_status_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
} } // graphene::chain

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/protocol/event_group.hpp>
namespace graphene { namespace chain {
void event_group_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void event_group_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
} } // graphene::chain

View file

@ -189,6 +189,11 @@ namespace graphene { namespace chain {
FC_ASSERT( maximum_proposal_lifetime - committee_proposal_review_period > block_interval,
"Committee proposal review period must be less than the maximum proposal lifetime" );
FC_ASSERT( min_bet_multiplier >= GRAPHENE_BETTING_MIN_MULTIPLIER &&
min_bet_multiplier <= GRAPHENE_BETTING_MAX_MULTIPLIER );
FC_ASSERT( max_bet_multiplier >= GRAPHENE_BETTING_MIN_MULTIPLIER &&
max_bet_multiplier <= GRAPHENE_BETTING_MAX_MULTIPLIER );
FC_ASSERT( min_bet_multiplier < max_bet_multiplier );
FC_ASSERT( rake_fee_percentage >= TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE,
"Rake fee percentage must not be less than ${min}", ("min",TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE));
FC_ASSERT( rake_fee_percentage <= TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE,

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/protocol/sport.hpp>
namespace graphene { namespace chain {
void sport_create_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
void sport_update_operation::validate() const
{
FC_ASSERT( fee.amount >= 0 );
}
} } // graphene::chain

View file

@ -193,7 +193,7 @@ struct sign_state
if( approved_by.find(a.first) == approved_by.end() )
{
if( depth == max_recursion )
return false;
continue;
if( check_authority( get_active( a.first ), depth+1 ) )
{
approved_by.insert( a.first );

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/sport_evaluator.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
namespace graphene { namespace chain {
void_result sport_create_evaluator::do_evaluate(const sport_create_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type sport_create_evaluator::do_apply(const sport_create_operation& op)
{ try {
const sport_object& new_sport =
db().create<sport_object>( [&]( sport_object& sport_obj ) {
sport_obj.name = op.name;
});
return new_sport.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result sport_update_evaluator::do_evaluate(const sport_update_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME);
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_name.valid());
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result sport_update_evaluator::do_apply(const sport_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.sport_id),
[&]( sport_object& spo )
{
if( op.new_name.valid() )
spo.name = *op.new_name;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} } // graphene::chain

View file

@ -304,53 +304,51 @@ namespace graphene { namespace chain {
const tournament_details_object& details = tournament_obj.tournament_details_id(event.db);
share_type total_prize = 0;
for (const auto& payer_pair : details.payers)
{
total_prize += payer_pair.second;
}
total_prize += payer_pair.second;
assert(total_prize == tournament_obj.prize_pool);
#endif
assert(event.match.match_winners.size() == 1);
const account_id_type& winner = *event.match.match_winners.begin();
uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage;
// check whether the core asset pays dividends. If so, we transfer the rake fee
// to the core asset's dividend account
const asset_object& core_asset_obj = asset_id_type()(event.db);
optional<asset_dividend_data_id_type> dividend_id = core_asset_obj.dividend_data_id;
share_type rake_amount = 0;
const asset_object & asset_obj = tournament_obj.options.buy_in.asset_id(event.db);
optional<asset_dividend_data_id_type> dividend_id = asset_obj.dividend_data_id;
if (dividend_id.valid())
{
rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
}
if (dividend_id)
rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
asset won_prize(tournament_obj.prize_pool - rake_amount, tournament_obj.options.buy_in.asset_id);
tournament_payout_operation op;
if (won_prize.amount.value)
{
// Adjusting balance of winner
event.db.adjust_balance(winner, won_prize);
// Adjusting balance of winner
event.db.adjust_balance(winner, won_prize);
// Generating a virtual operation that shows the payment
op.tournament_id = tournament_obj.id;
op.payout_amount = won_prize;
op.payout_account_id = winner;
op.type = payout_type::prize_award;
event.db.push_applied_operation(op);
// Generating a virtual operation that shows the payment
op.tournament_id = tournament_obj.id;
op.payout_amount = won_prize;
op.payout_account_id = winner;
op.type = payout_type::prize_award;
event.db.push_applied_operation(op);
}
if (dividend_id.valid() && rake_amount.value)
if (dividend_id && rake_amount.value)
{
// Adjusting balance of dividend_distribution_account
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db);
const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account;
asset rake(rake_amount, tournament_obj.options.buy_in.asset_id);
event.db.adjust_balance(rake_account_id, rake);
// Adjusting balance of dividend_distribution_account
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db);
const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account;
asset rake(rake_amount, tournament_obj.options.buy_in.asset_id);
event.db.adjust_balance(rake_account_id, rake);
// Generating a virtual operation that shows the payment
op.payout_amount = rake;
op.payout_account_id = rake_account_id;
op.type = payout_type::rake_fee;
event.db.push_applied_operation(op);
// Generating a virtual operation that shows the payment
op.payout_amount = rake;
op.payout_account_id = rake_account_id;
op.type = payout_type::rake_fee;
event.db.push_applied_operation(op);
}
}
};

View file

@ -64,7 +64,7 @@ asset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context&
}
}
return asset( allowed_withdraw, ctx.amount.asset_id );
return asset( allowed_withdraw, ctx.balance.asset_id );
}
void linear_vesting_policy::on_deposit(const vesting_policy_context& ctx)

View file

@ -10,3 +10,4 @@ install( TARGETS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install( FILES ${HEADERS} DESTINATION "include/graphene/db" )

View file

@ -80,6 +80,8 @@ namespace graphene { namespace chain {
virtual const object* find( object_id_type id )const override
{
static_assert(std::is_same<typename MultiIndexType::key_type, object_id_type>::value,
"First index of MultiIndexType MUST be object_id_type!");
auto itr = _indices.find( id );
if( itr == _indices.end() ) return nullptr;
return &*itr;

View file

@ -165,9 +165,10 @@ namespace graphene { namespace db {
void on_modify( const object& obj );
template<typename T>
void add_secondary_index()
T* add_secondary_index()
{
_sindex.emplace_back( new T() );
return static_cast<T*>(_sindex.back().get());
}
template<typename T>

View file

@ -25,3 +25,4 @@ install( TARGETS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install( FILES ${headers} DESTINATION "include/graphene/deterministic_openssl_rand" )

View file

@ -32,3 +32,4 @@ install( TARGETS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install( FILES ${HEADERS} DESTINATION "include/graphene/net" )

View file

@ -49,6 +49,9 @@
#define GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT 20
/* uncomment next line to use testnet seed ip and port */
//#define GRAPHENE_TEST_NETWORK 1
#define GRAPHENE_NET_TEST_SEED_IP "104.236.44.210" // autogenerated
#define GRAPHENE_NET_TEST_P2P_PORT 1700
#define GRAPHENE_NET_DEFAULT_P2P_PORT 2776

View file

@ -39,8 +39,8 @@ namespace graphene { namespace net {
*/
struct message_header
{
uint32_t size; // number of bytes in message, capped at MAX_MESSAGE_SIZE
uint32_t msg_type; // every channel gets a 16 bit message type specifier
uint32_t size = 0; // number of bytes in message, capped at MAX_MESSAGE_SIZE
uint32_t msg_type = 0; // every channel gets a 16 bit message type specifier
};
typedef fc::uint160_t message_hash_type;

View file

@ -158,9 +158,6 @@ namespace graphene { namespace net {
*/
virtual fc::time_point_sec get_block_time(const item_hash_t& block_id) = 0;
/** returns graphene::blockchain::now() */
virtual fc::time_point_sec get_blockchain_now() = 0;
virtual item_hash_t get_head_block_id() const = 0;
virtual uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const = 0;

View file

@ -166,25 +166,25 @@ namespace graphene { namespace net
};
size_t _total_queued_messages_size;
size_t _total_queued_messages_size = 0;
std::queue<std::unique_ptr<queued_message>, std::list<std::unique_ptr<queued_message> > > _queued_messages;
fc::future<void> _send_queued_messages_done;
public:
fc::time_point connection_initiation_time;
fc::time_point connection_closed_time;
fc::time_point connection_terminated_time;
peer_connection_direction direction;
peer_connection_direction direction = peer_connection_direction::unknown;
//connection_state state;
firewalled_state is_firewalled;
firewalled_state is_firewalled = firewalled_state::unknown;
fc::microseconds clock_offset;
fc::microseconds round_trip_delay;
our_connection_state our_state;
bool they_have_requested_close;
their_connection_state their_state;
bool we_have_requested_close;
our_connection_state our_state = our_connection_state::disconnected;
bool they_have_requested_close = false;
their_connection_state their_state = their_connection_state::disconnected;
bool we_have_requested_close = false;
connection_negotiation_status negotiation_status;
connection_negotiation_status negotiation_status = connection_negotiation_status::disconnected;
fc::oexception connection_closed_error;
fc::time_point get_connection_time()const { return _message_connection.get_connection_time(); }
@ -199,7 +199,7 @@ namespace graphene { namespace net
* from the user_data field of the hello, or if none is present it will be filled with a
* copy of node_public_key */
node_id_t node_id;
uint32_t core_protocol_version;
uint32_t core_protocol_version = 0;
std::string user_agent;
fc::optional<std::string> graphene_git_revision_sha;
fc::optional<fc::time_point_sec> graphene_git_revision_unix_timestamp;
@ -212,8 +212,8 @@ namespace graphene { namespace net
// its hello message. For outbound, they record what we sent the peer
// in our hello message
fc::ip::address inbound_address;
uint16_t inbound_port;
uint16_t outbound_port;
uint16_t inbound_port = 0;
uint16_t outbound_port = 0;
/// @}
typedef std::unordered_map<item_id, fc::time_point> item_to_time_map_type;
@ -222,14 +222,14 @@ namespace graphene { namespace net
/// @{
boost::container::deque<item_hash_t> ids_of_items_to_get; /// id of items in the blockchain that this peer has told us about
std::set<item_hash_t> ids_of_items_being_processed; /// list of all items this peer has offered use that we've already handed to the client but the client hasn't finished processing
uint32_t number_of_unfetched_item_ids; /// number of items in the blockchain that follow ids_of_items_to_get but the peer hasn't yet told us their ids
bool peer_needs_sync_items_from_us;
bool we_need_sync_items_from_peer;
uint32_t number_of_unfetched_item_ids = 0; /// number of items in the blockchain that follow ids_of_items_to_get but the peer hasn't yet told us their ids
bool peer_needs_sync_items_from_us = false;
bool we_need_sync_items_from_peer = false;
fc::optional<boost::tuple<std::vector<item_hash_t>, fc::time_point> > item_ids_requested_from_peer; /// we check this to detect a timed-out request and in busy()
item_to_time_map_type sync_items_requested_from_peer; /// ids of blocks we've requested from this peer during sync. fetch from another peer if this peer disconnects
item_hash_t last_block_delegate_has_seen; /// the hash of the last block this peer has told us about that the peer knows
fc::time_point_sec last_block_time_delegate_has_seen;
bool inhibit_fetching_sync_blocks;
bool inhibit_fetching_sync_blocks = false;
/// @}
/// non-synchronization state data
@ -259,16 +259,17 @@ namespace graphene { namespace net
// blockchain catch up
fc::time_point transaction_fetching_inhibited_until;
uint32_t last_known_fork_block_number;
uint32_t last_known_fork_block_number = 0;
fc::future<void> accept_or_connect_task_done;
firewall_check_state_data *firewall_check_state;
firewall_check_state_data *firewall_check_state = nullptr;
#ifndef NDEBUG
private:
fc::thread* _thread;
unsigned _send_message_queue_tasks_running; // temporary debugging
fc::thread* _thread = nullptr;
unsigned _send_message_queue_tasks_running = 0; // temporary debugging
#endif
bool _currently_handling_message = false; // true while we're in the middle of handling a message from the remote system
private:
peer_connection(peer_connection_delegate* delegate);
void destroy();
@ -299,8 +300,9 @@ namespace graphene { namespace net
fc::ip::endpoint get_local_endpoint();
void set_remote_endpoint(fc::optional<fc::ip::endpoint> new_remote_endpoint);
bool busy();
bool idle();
bool busy() const;
bool idle() const;
bool is_currently_handling_message() const;
bool is_transaction_fetching_inhibited() const;
fc::sha512 get_shared_secret() const;

View file

@ -32,6 +32,8 @@
#include <graphene/net/stcp_socket.hpp>
#include <graphene/net/config.hpp>
#include <atomic>
#ifdef DEFAULT_LOGGER
# undef DEFAULT_LOGGER
#endif
@ -61,7 +63,6 @@ namespace graphene { namespace net {
fc::time_point _last_message_sent_time;
bool _send_message_in_progress;
#ifndef NDEBUG
fc::thread* _thread;
#endif
@ -137,7 +138,6 @@ namespace graphene { namespace net {
_sock.bind(local_endpoint);
}
void message_oriented_connection_impl::read_loop()
{
VERIFY_CORRECT_THREAD();
@ -261,8 +261,13 @@ namespace graphene { namespace net {
//pad the message we send to a multiple of 16 bytes
size_t size_with_padding = 16 * ((size_of_message_and_header + 15) / 16);
std::unique_ptr<char[]> padded_message(new char[size_with_padding]);
memcpy(padded_message.get(), (char*)&message_to_send, sizeof(message_header));
memcpy(padded_message.get() + sizeof(message_header), message_to_send.data.data(), message_to_send.size );
char* paddingSpace = padded_message.get() + sizeof(message_header) + message_to_send.size;
size_t toClean = size_with_padding - size_of_message_and_header;
memset(paddingSpace, 0, toClean);
_sock.write(padded_message.get(), size_with_padding);
_sock.flush();
_bytes_sent += size_with_padding;

View file

@ -396,7 +396,6 @@ namespace graphene { namespace net { namespace detail {
void connection_count_changed( uint32_t c ) override;
uint32_t get_block_number(const item_hash_t& block_id) override;
fc::time_point_sec get_block_time(const item_hash_t& block_id) override;
fc::time_point_sec get_blockchain_now() override;
item_hash_t get_head_block_id() const override;
uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override;
void error_encountered(const std::string& message, const fc::oexception& error) override;
@ -975,10 +974,7 @@ namespace graphene { namespace net { namespace detail {
{
throw;
}
catch (const fc::exception& e)
{
elog("${e}", ("e", e));
}
FC_CAPTURE_AND_LOG( (0) )
}// while(!canceled)
}
@ -1425,6 +1421,19 @@ namespace graphene { namespace net { namespace detail {
wlog( "Sending a keepalive message to peer ${peer} who hasn't sent us any messages in the last ${timeout} seconds",
( "peer", active_peer->get_remote_endpoint() )("timeout", active_send_keepalive_timeout ) );
peers_to_send_keep_alive.push_back(active_peer);
}
else if (active_peer->we_need_sync_items_from_peer &&
!active_peer->is_currently_handling_message() &&
!active_peer->item_ids_requested_from_peer &&
active_peer->ids_of_items_to_get.empty())
{
// This is a state we should never get into in the first place, but if we do, we should disconnect the peer
// to re-establish the connection.
fc_wlog(fc::logger::get("sync"), "Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.",
("peer", active_peer->get_remote_endpoint()));
wlog("Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.",
("peer", active_peer->get_remote_endpoint()));
peers_to_disconnect_forcibly.push_back(active_peer);
}
}
}
@ -2456,24 +2465,24 @@ namespace graphene { namespace net { namespace detail {
uint32_t expected_num = first_block_number_in_reponse + i;
if (actual_num != expected_num)
{
wlog("Invalid response from peer ${peer_endpoint}. The list of blocks they provided is not sequential, "
"the ${position}th block in their reply was block number ${actual_num}, "
"but it should have been number ${expected_num}",
("peer_endpoint", originating_peer->get_remote_endpoint())
("position", i)
("actual_num", actual_num)
("expected_num", expected_num));
fc::exception error_for_peer(FC_LOG_MESSAGE(error,
"You gave an invalid response to my request for sync blocks. The list of blocks you provided is not sequential, "
"the ${position}th block in their reply was block number ${actual_num}, "
"but it should have been number ${expected_num}",
("position", i)
("actual_num", actual_num)
("expected_num", expected_num)));
disconnect_from_peer(originating_peer,
"You gave an invalid response to my request for sync blocks",
true, error_for_peer);
return;
wlog("Invalid response from peer ${peer_endpoint}. The list of blocks they provided is not sequential, "
"the ${position}th block in their reply was block number ${actual_num}, "
"but it should have been number ${expected_num}",
("peer_endpoint", originating_peer->get_remote_endpoint())
("position", i)
("actual_num", actual_num)
("expected_num", expected_num));
fc::exception error_for_peer(FC_LOG_MESSAGE(error,
"You gave an invalid response to my request for sync blocks. The list of blocks you provided is not sequential, "
"the ${position}th block in their reply was block number ${actual_num}, "
"but it should have been number ${expected_num}",
("position", i)
("actual_num", actual_num)
("expected_num", expected_num)));
disconnect_from_peer(originating_peer,
"You gave an invalid response to my request for sync blocks",
true, error_for_peer);
return;
}
}
@ -2520,180 +2529,205 @@ namespace graphene { namespace net { namespace detail {
}
originating_peer->item_ids_requested_from_peer.reset();
dlog( "sync: received a list of ${count} available items from ${peer_endpoint}",
( "count", blockchain_item_ids_inventory_message_received.item_hashes_available.size() )
( "peer_endpoint", originating_peer->get_remote_endpoint() ) );
//for( const item_hash_t& item_hash : blockchain_item_ids_inventory_message_received.item_hashes_available )
//{
// dlog( "sync: ${hash}", ("hash", item_hash ) );
//}
// if the peer doesn't have any items after the one we asked for
if( blockchain_item_ids_inventory_message_received.total_remaining_item_count == 0 &&
( blockchain_item_ids_inventory_message_received.item_hashes_available.empty() || // there are no items in the peer's blockchain. this should only happen if our blockchain was empty when we requested, might want to verify that.
( blockchain_item_ids_inventory_message_received.item_hashes_available.size() == 1 &&
_delegate->has_item( item_id(blockchain_item_ids_inventory_message_received.item_type,
blockchain_item_ids_inventory_message_received.item_hashes_available.front() ) ) ) ) && // we've already seen the last item in the peer's blockchain
originating_peer->ids_of_items_to_get.empty() &&
originating_peer->number_of_unfetched_item_ids == 0 ) // <-- is the last check necessary?
// if exceptions are throw after clearing the item_ids_requested_from_peer (above),
// it could leave our sync in a stalled state. Wrap a try/catch around the rest
// of the function so we can log if this ever happens.
try
{
dlog( "sync: peer said we're up-to-date, entering normal operation with this peer" );
originating_peer->we_need_sync_items_from_peer = false;
dlog( "sync: received a list of ${count} available items from ${peer_endpoint}",
( "count", blockchain_item_ids_inventory_message_received.item_hashes_available.size() )
( "peer_endpoint", originating_peer->get_remote_endpoint() ) );
//for( const item_hash_t& item_hash : blockchain_item_ids_inventory_message_received.item_hashes_available )
//{
// dlog( "sync: ${hash}", ("hash", item_hash ) );
//}
uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();
_total_number_of_unfetched_items = new_number_of_unfetched_items;
if( new_number_of_unfetched_items == 0 )
_delegate->sync_status( blockchain_item_ids_inventory_message_received.item_type, 0 );
return;
}
std::deque<item_hash_t> item_hashes_received( blockchain_item_ids_inventory_message_received.item_hashes_available.begin(),
blockchain_item_ids_inventory_message_received.item_hashes_available.end() );
originating_peer->number_of_unfetched_item_ids = blockchain_item_ids_inventory_message_received.total_remaining_item_count;
// flush any items this peer sent us that we've already received and processed from another peer
if (!item_hashes_received.empty() &&
originating_peer->ids_of_items_to_get.empty())
{
bool is_first_item_for_other_peer = false;
for (const peer_connection_ptr& peer : _active_connections)
if (peer != originating_peer->shared_from_this() &&
!peer->ids_of_items_to_get.empty() &&
peer->ids_of_items_to_get.front() == blockchain_item_ids_inventory_message_received.item_hashes_available.front())
{
dlog("The item ${newitem} is the first item for peer ${peer}",
("newitem", blockchain_item_ids_inventory_message_received.item_hashes_available.front())
("peer", peer->get_remote_endpoint()));
is_first_item_for_other_peer = true;
break;
}
dlog("is_first_item_for_other_peer: ${is_first}. item_hashes_received.size() = ${size}",
("is_first", is_first_item_for_other_peer)("size", item_hashes_received.size()));
if (!is_first_item_for_other_peer)
// if the peer doesn't have any items after the one we asked for
if( blockchain_item_ids_inventory_message_received.total_remaining_item_count == 0 &&
( blockchain_item_ids_inventory_message_received.item_hashes_available.empty() || // there are no items in the peer's blockchain. this should only happen if our blockchain was empty when we requested, might want to verify that.
( blockchain_item_ids_inventory_message_received.item_hashes_available.size() == 1 &&
_delegate->has_item( item_id(blockchain_item_ids_inventory_message_received.item_type,
blockchain_item_ids_inventory_message_received.item_hashes_available.front() ) ) ) ) && // we've already seen the last item in the peer's blockchain
originating_peer->ids_of_items_to_get.empty() &&
originating_peer->number_of_unfetched_item_ids == 0 ) // <-- is the last check necessary?
{
while (!item_hashes_received.empty() &&
_delegate->has_item(item_id(blockchain_item_ids_inventory_message_received.item_type,
item_hashes_received.front())))
dlog( "sync: peer said we're up-to-date, entering normal operation with this peer" );
originating_peer->we_need_sync_items_from_peer = false;
uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();
_total_number_of_unfetched_items = new_number_of_unfetched_items;
if( new_number_of_unfetched_items == 0 )
_delegate->sync_status( blockchain_item_ids_inventory_message_received.item_type, 0 );
return;
}
std::deque<item_hash_t> item_hashes_received( blockchain_item_ids_inventory_message_received.item_hashes_available.begin(),
blockchain_item_ids_inventory_message_received.item_hashes_available.end() );
originating_peer->number_of_unfetched_item_ids = blockchain_item_ids_inventory_message_received.total_remaining_item_count;
// flush any items this peer sent us that we've already received and processed from another peer
if (!item_hashes_received.empty() &&
originating_peer->ids_of_items_to_get.empty())
{
bool is_first_item_for_other_peer = false;
for (const peer_connection_ptr& peer : _active_connections)
if (peer != originating_peer->shared_from_this() &&
!peer->ids_of_items_to_get.empty() &&
peer->ids_of_items_to_get.front() == blockchain_item_ids_inventory_message_received.item_hashes_available.front())
{
dlog("The item ${newitem} is the first item for peer ${peer}",
("newitem", blockchain_item_ids_inventory_message_received.item_hashes_available.front())
("peer", peer->get_remote_endpoint()));
is_first_item_for_other_peer = true;
break;
}
dlog("is_first_item_for_other_peer: ${is_first}. item_hashes_received.size() = ${size}",
("is_first", is_first_item_for_other_peer)("size", item_hashes_received.size()));
if (!is_first_item_for_other_peer)
{
assert(item_hashes_received.front() != item_hash_t());
while (!item_hashes_received.empty() &&
_delegate->has_item(item_id(blockchain_item_ids_inventory_message_received.item_type,
item_hashes_received.front())))
{
assert(item_hashes_received.front() != item_hash_t());
originating_peer->last_block_delegate_has_seen = item_hashes_received.front();
originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hashes_received.front());
dlog("popping item because delegate has already seen it. peer ${peer}'s last block the delegate has seen is now ${block_id} (actual block #${actual_block_num})",
("peer", originating_peer->get_remote_endpoint())
("block_id", originating_peer->last_block_delegate_has_seen)
("actual_block_num", _delegate->get_block_number(item_hashes_received.front())));
item_hashes_received.pop_front();
}
dlog("after removing all items we have already seen, item_hashes_received.size() = ${size}", ("size", item_hashes_received.size()));
}
}
else if (!item_hashes_received.empty())
{
// we received a list of items and we already have a list of items to fetch from this peer.
// In the normal case, this list will immediately follow the existing list, meaning the
// last hash of our existing list will match the first hash of the new list.
// In the much less likely case, we've received a partial list of items from the peer, then
// the peer switched forks before sending us the remaining list. In this case, the first
// hash in the new list may not be the last hash in the existing list (it may be earlier, or
// it may not exist at all.
// In either case, pop items off the back of our existing list until we find our first
// item, then append our list.
while (!originating_peer->ids_of_items_to_get.empty())
{
if (item_hashes_received.front() != originating_peer->ids_of_items_to_get.back())
originating_peer->ids_of_items_to_get.pop_back();
else
break;
}
if (originating_peer->ids_of_items_to_get.empty())
{
// this happens when the peer has switched forks between the last inventory message and
// this one, and there weren't any unfetched items in common
// We don't know where in the blockchain the new front() actually falls, all we can
// expect is that it is a block that we knew about because it should be one of the
// blocks we sent in the initial synopsis.
assert(_delegate->has_item(item_id(_sync_item_type, item_hashes_received.front())));
originating_peer->last_block_delegate_has_seen = item_hashes_received.front();
originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hashes_received.front());
dlog("popping item because delegate has already seen it. peer ${peer}'s last block the delegate has seen is now ${block_id} (actual block #${actual_block_num})",
("peer", originating_peer->get_remote_endpoint())
("block_id", originating_peer->last_block_delegate_has_seen)
("actual_block_num", _delegate->get_block_number(item_hashes_received.front())));
item_hashes_received.pop_front();
}
dlog("after removing all items we have already seen, item_hashes_received.size() = ${size}", ("size", item_hashes_received.size()));
}
}
else if (!item_hashes_received.empty())
{
// we received a list of items and we already have a list of items to fetch from this peer.
// In the normal case, this list will immediately follow the existing list, meaning the
// last hash of our existing list will match the first hash of the new list.
// In the much less likely case, we've received a partial list of items from the peer, then
// the peer switched forks before sending us the remaining list. In this case, the first
// hash in the new list may not be the last hash in the existing list (it may be earlier, or
// it may not exist at all.
// In either case, pop items off the back of our existing list until we find our first
// item, then append our list.
while (!originating_peer->ids_of_items_to_get.empty())
{
if (item_hashes_received.front() != originating_peer->ids_of_items_to_get.back())
originating_peer->ids_of_items_to_get.pop_back();
else
break;
{
// the common simple case: the new list extends the old. pop off the duplicate element
originating_peer->ids_of_items_to_get.pop_back();
}
}
if (originating_peer->ids_of_items_to_get.empty())
if (!item_hashes_received.empty() && !originating_peer->ids_of_items_to_get.empty())
assert(item_hashes_received.front() != originating_peer->ids_of_items_to_get.back());
// append the remaining items to the peer's list
boost::push_back(originating_peer->ids_of_items_to_get, item_hashes_received);
originating_peer->number_of_unfetched_item_ids = blockchain_item_ids_inventory_message_received.total_remaining_item_count;
// at any given time, there's a maximum number of blocks that can possibly be out there
// [(now - genesis time) / block interval]. If they offer us more blocks than that,
// they must be an attacker or have a buggy client.
fc::time_point_sec minimum_time_of_last_offered_block =
originating_peer->last_block_time_delegate_has_seen + // timestamp of the block immediately before the first unfetched block
originating_peer->number_of_unfetched_item_ids * GRAPHENE_MIN_BLOCK_INTERVAL;
fc::time_point_sec now = fc::time_point::now();
if (minimum_time_of_last_offered_block > now + GRAPHENE_NET_FUTURE_SYNC_BLOCKS_GRACE_PERIOD_SEC)
{
// this happens when the peer has switched forks between the last inventory message and
// this one, and there weren't any unfetched items in common
// We don't know where in the blockchain the new front() actually falls, all we can
// expect is that it is a block that we knew about because it should be one of the
// blocks we sent in the initial synopsis.
assert(_delegate->has_item(item_id(_sync_item_type, item_hashes_received.front())));
originating_peer->last_block_delegate_has_seen = item_hashes_received.front();
originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hashes_received.front());
item_hashes_received.pop_front();
wlog("Disconnecting from peer ${peer} who offered us an implausible number of blocks, their last block would be in the future (${timestamp})",
("peer", originating_peer->get_remote_endpoint())
("timestamp", minimum_time_of_last_offered_block));
fc::exception error_for_peer(FC_LOG_MESSAGE(error, "You offered me a list of more sync blocks than could possibly exist. Total blocks offered: ${blocks}, Minimum time of the last block you offered: ${minimum_time_of_last_offered_block}, Now: ${now}",
("blocks", originating_peer->number_of_unfetched_item_ids)
("minimum_time_of_last_offered_block", minimum_time_of_last_offered_block)
("now", now)));
disconnect_from_peer(originating_peer,
"You offered me a list of more sync blocks than could possibly exist",
true, error_for_peer);
return;
}
uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();
if (new_number_of_unfetched_items != _total_number_of_unfetched_items)
_delegate->sync_status(blockchain_item_ids_inventory_message_received.item_type,
new_number_of_unfetched_items);
_total_number_of_unfetched_items = new_number_of_unfetched_items;
if (blockchain_item_ids_inventory_message_received.total_remaining_item_count != 0)
{
// the peer hasn't sent us all the items it knows about.
if (originating_peer->ids_of_items_to_get.size() > GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)
{
// we have a good number of item ids from this peer, start fetching blocks from it;
// we'll switch back later to finish the job.
trigger_fetch_sync_items_loop();
}
else
{
// keep fetching the peer's list of sync items until we get enough to switch into block-
// fetchimg mode
fetch_next_batch_of_item_ids_from_peer(originating_peer);
}
}
else
{
// the common simple case: the new list extends the old. pop off the duplicate element
originating_peer->ids_of_items_to_get.pop_back();
// the peer has told us about all of the items it knows
if (!originating_peer->ids_of_items_to_get.empty())
{
// we now know about all of the items the peer knows about, and there are some items on the list
// that we should try to fetch. Kick off the fetch loop.
trigger_fetch_sync_items_loop();
}
else
{
// If we get here, the peer has sent us a non-empty list of items, but we have already
// received all of the items from other peers. Send a new request to the peer to
// see if we're really in sync
fetch_next_batch_of_item_ids_from_peer(originating_peer);
}
}
}
if (!item_hashes_received.empty() && !originating_peer->ids_of_items_to_get.empty())
assert(item_hashes_received.front() != originating_peer->ids_of_items_to_get.back());
// append the remaining items to the peer's list
boost::push_back(originating_peer->ids_of_items_to_get, item_hashes_received);
originating_peer->number_of_unfetched_item_ids = blockchain_item_ids_inventory_message_received.total_remaining_item_count;
// at any given time, there's a maximum number of blocks that can possibly be out there
// [(now - genesis time) / block interval]. If they offer us more blocks than that,
// they must be an attacker or have a buggy client.
fc::time_point_sec minimum_time_of_last_offered_block =
originating_peer->last_block_time_delegate_has_seen + // timestamp of the block immediately before the first unfetched block
originating_peer->number_of_unfetched_item_ids * GRAPHENE_MIN_BLOCK_INTERVAL;
if (minimum_time_of_last_offered_block > _delegate->get_blockchain_now() + GRAPHENE_NET_FUTURE_SYNC_BLOCKS_GRACE_PERIOD_SEC)
catch (const fc::canceled_exception&)
{
wlog("Disconnecting from peer ${peer} who offered us an implausible number of blocks, their last block would be in the future (${timestamp})",
("peer", originating_peer->get_remote_endpoint())
("timestamp", minimum_time_of_last_offered_block));
fc::exception error_for_peer(FC_LOG_MESSAGE(error, "You offered me a list of more sync blocks than could possibly exist. Total blocks offered: ${blocks}, Minimum time of the last block you offered: ${minimum_time_of_last_offered_block}, Now: ${now}",
("blocks", originating_peer->number_of_unfetched_item_ids)
("minimum_time_of_last_offered_block", minimum_time_of_last_offered_block)
("now", _delegate->get_blockchain_now())));
disconnect_from_peer(originating_peer,
"You offered me a list of more sync blocks than could possibly exist",
true, error_for_peer);
return;
throw;
}
uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();
if (new_number_of_unfetched_items != _total_number_of_unfetched_items)
_delegate->sync_status(blockchain_item_ids_inventory_message_received.item_type,
new_number_of_unfetched_items);
_total_number_of_unfetched_items = new_number_of_unfetched_items;
if (blockchain_item_ids_inventory_message_received.total_remaining_item_count != 0)
catch (const fc::exception& e)
{
// the peer hasn't sent us all the items it knows about.
if (originating_peer->ids_of_items_to_get.size() > GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)
{
// we have a good number of item ids from this peer, start fetching blocks from it;
// we'll switch back later to finish the job.
trigger_fetch_sync_items_loop();
}
else
{
// keep fetching the peer's list of sync items until we get enough to switch into block-
// fetchimg mode
fetch_next_batch_of_item_ids_from_peer(originating_peer);
}
elog("Caught unexpected exception: ${e}", ("e", e));
assert(false && "exceptions not expected here");
}
else
catch (const std::exception& e)
{
// the peer has told us about all of the items it knows
if (!originating_peer->ids_of_items_to_get.empty())
{
// we now know about all of the items the peer knows about, and there are some items on the list
// that we should try to fetch. Kick off the fetch loop.
trigger_fetch_sync_items_loop();
}
else
{
// If we get here, the peer has sent us a non-empty list of items, but we have already
// received all of the items from other peers. Send a new request to the peer to
// see if we're really in sync
fetch_next_batch_of_item_ids_from_peer(originating_peer);
}
elog("Caught unexpected exception: ${e}", ("e", e.what()));
assert(false && "exceptions not expected here");
}
catch (...)
{
elog("Caught unexpected exception, could break sync operation");
}
}
else
@ -3270,7 +3304,33 @@ namespace graphene { namespace net { namespace detail {
block_processed_this_iteration = true;
}
else
{
dlog("Already received and accepted this block (presumably through normal inventory mechanism), treating it as accepted");
std::vector< peer_connection_ptr > peers_needing_next_batch;
for (const peer_connection_ptr& peer : _active_connections)
{
auto items_being_processed_iter = peer->ids_of_items_being_processed.find(received_block_iter->block_id);
if (items_being_processed_iter != peer->ids_of_items_being_processed.end())
{
peer->ids_of_items_being_processed.erase(items_being_processed_iter);
dlog("Removed item from ${endpoint}'s list of items being processed, still processing ${len} blocks",
("endpoint", peer->get_remote_endpoint())("len", peer->ids_of_items_being_processed.size()));
// if we just processed the last item in our list from this peer, we will want to
// send another request to find out if we are now in sync (this is normally handled in
// send_sync_block_to_node_delegate)
if (peer->ids_of_items_to_get.empty() &&
peer->number_of_unfetched_item_ids == 0 &&
peer->ids_of_items_being_processed.empty())
{
dlog("We received last item in our list for peer ${endpoint}, setup to do a sync check", ("endpoint", peer->get_remote_endpoint()));
peers_needing_next_batch.push_back( peer );
}
}
}
for( const peer_connection_ptr& peer : peers_needing_next_batch )
fetch_next_batch_of_item_ids_from_peer(peer.get());
}
break; // start iterating _received_sync_items from the beginning
} // end if potential_first_block
@ -3486,19 +3546,43 @@ namespace graphene { namespace net { namespace detail {
if (sync_item_iter != originating_peer->sync_items_requested_from_peer.end())
{
originating_peer->sync_items_requested_from_peer.erase(sync_item_iter);
_active_sync_requests.erase(block_message_to_process.block_id);
process_block_during_sync(originating_peer, block_message_to_process, message_hash);
if (originating_peer->idle())
// if exceptions are throw here after removing the sync item from the list (above),
// it could leave our sync in a stalled state. Wrap a try/catch around the rest
// of the function so we can log if this ever happens.
try
{
// we have finished fetching a batch of items, so we either need to grab another batch of items
// or we need to get another list of item ids.
if (originating_peer->number_of_unfetched_item_ids > 0 &&
originating_peer->ids_of_items_to_get.size() < GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)
fetch_next_batch_of_item_ids_from_peer(originating_peer);
else
trigger_fetch_sync_items_loop();
_active_sync_requests.erase(block_message_to_process.block_id);
process_block_during_sync(originating_peer, block_message_to_process, message_hash);
if (originating_peer->idle())
{
// we have finished fetching a batch of items, so we either need to grab another batch of items
// or we need to get another list of item ids.
if (originating_peer->number_of_unfetched_item_ids > 0 &&
originating_peer->ids_of_items_to_get.size() < GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)
fetch_next_batch_of_item_ids_from_peer(originating_peer);
else
trigger_fetch_sync_items_loop();
}
return;
}
catch (const fc::canceled_exception& e)
{
throw;
}
catch (const fc::exception& e)
{
elog("Caught unexpected exception: ${e}", ("e", e));
assert(false && "exceptions not expected here");
}
catch (const std::exception& e)
{
elog("Caught unexpected exception: ${e}", ("e", e.what()));
assert(false && "exceptions not expected here");
}
catch (...)
{
elog("Caught unexpected exception, could break sync operation");
}
return;
}
}
@ -4192,7 +4276,7 @@ namespace graphene { namespace net { namespace detail {
// limit the rate at which we accept connections to mitigate DOS attacks
fc::usleep( fc::milliseconds(10) );
} FC_CAPTURE_AND_RETHROW()
} FC_CAPTURE_AND_LOG( (0) )
}
} // accept_loop()
@ -4393,7 +4477,7 @@ namespace graphene { namespace net { namespace detail {
_node_configuration = detail::node_configuration();
#ifdef GRAPHENE_TEST_NETWORK
uint32_t port = GRAPHENE_NET_TEST_P2P_PORT + GRAPHENE_TEST_NETWORK_VERSION;
uint32_t port = GRAPHENE_NET_TEST_P2P_PORT;
#else
uint32_t port = GRAPHENE_NET_DEFAULT_P2P_PORT;
#endif
@ -4489,7 +4573,8 @@ namespace graphene { namespace net { namespace detail {
error_message_stream << "\nStill waiting for port " << listen_endpoint.port() << " to become available\n";
}
std::string error_message = error_message_stream.str();
ulog(error_message);
wlog(error_message);
std::cout << "\033[31m" << error_message;
_delegate->error_encountered( error_message, fc::oexception() );
fc::usleep( fc::seconds(5 ) );
}
@ -5335,17 +5420,21 @@ namespace graphene { namespace net { namespace detail {
# define INVOKE_AND_COLLECT_STATISTICS(method_name, ...) \
try \
{ \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
if (_thread->is_current()) \
{ \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \
return _node_delegate->method_name(__VA_ARGS__); \
} \
else \
return _thread->async([&](){ \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \
return _node_delegate->method_name(__VA_ARGS__); \
}, "invoke " BOOST_STRINGIZE(method_name)).wait(); \
@ -5367,17 +5456,21 @@ namespace graphene { namespace net { namespace detail {
}
#else
# define INVOKE_AND_COLLECT_STATISTICS(method_name, ...) \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
if (_thread->is_current()) \
{ \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \
return _node_delegate->method_name(__VA_ARGS__); \
} \
else \
return _thread->async([&](){ \
call_statistics_collector statistics_collector(#method_name, \
&_ ## method_name ## _execution_accumulator, \
&_ ## method_name ## _delay_before_accumulator, \
&_ ## method_name ## _delay_after_accumulator); \
call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \
return _node_delegate->method_name(__VA_ARGS__); \
}, "invoke " BOOST_STRINGIZE(method_name)).wait()
@ -5447,14 +5540,6 @@ namespace graphene { namespace net { namespace detail {
INVOKE_AND_COLLECT_STATISTICS(get_block_time, block_id);
}
/** returns graphene::blockchain::now() */
fc::time_point_sec statistics_gathering_node_delegate_wrapper::get_blockchain_now()
{
// this function doesn't need to block,
ASSERT_TASK_NOT_PREEMPTED();
return _node_delegate->get_blockchain_now();
}
item_hash_t statistics_gathering_node_delegate_wrapper::get_head_block_id() const
{
INVOKE_AND_COLLECT_STATISTICS(get_head_block_id);

View file

@ -29,6 +29,8 @@
#include <fc/thread/thread.hpp>
#include <boost/scope_exit.hpp>
#ifdef DEFAULT_LOGGER
# undef DEFAULT_LOGGER
#endif
@ -86,11 +88,12 @@ namespace graphene { namespace net
inhibit_fetching_sync_blocks(false),
transaction_fetching_inhibited_until(fc::time_point::min()),
last_known_fork_block_number(0),
firewall_check_state(nullptr)
firewall_check_state(nullptr),
#ifndef NDEBUG
,_thread(&fc::thread::current()),
_send_message_queue_tasks_running(0)
_thread(&fc::thread::current()),
_send_message_queue_tasks_running(0),
#endif
_currently_handling_message(false)
{
}
@ -265,6 +268,10 @@ namespace graphene { namespace net
void peer_connection::on_message( message_oriented_connection* originating_connection, const message& received_message )
{
VERIFY_CORRECT_THREAD();
_currently_handling_message = true;
BOOST_SCOPE_EXIT(this_) {
this_->_currently_handling_message = false;
} BOOST_SCOPE_EXIT_END
_node->on_message( this, received_message );
}
@ -438,18 +445,24 @@ namespace graphene { namespace net
_remote_endpoint = new_remote_endpoint;
}
bool peer_connection::busy()
bool peer_connection::busy() const
{
VERIFY_CORRECT_THREAD();
return !items_requested_from_peer.empty() || !sync_items_requested_from_peer.empty() || item_ids_requested_from_peer;
}
bool peer_connection::idle()
bool peer_connection::idle() const
{
VERIFY_CORRECT_THREAD();
return !busy();
}
bool peer_connection::is_currently_handling_message() const
{
VERIFY_CORRECT_THREAD();
return _currently_handling_message;
}
bool peer_connection::is_transaction_fetching_inhibited() const
{
VERIFY_CORRECT_THREAD();

View file

@ -1,32 +0,0 @@
file(GLOB HEADERS "include/graphene/p2p/*.hpp")
set(SOURCES node.cpp
stcp_socket.cpp
peer_connection.cpp
message_oriented_connection.cpp)
add_library( graphene_p2p ${SOURCES} ${HEADERS} )
target_link_libraries( graphene_p2p
PUBLIC fc graphene_db )
target_include_directories( graphene_p2p
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
PRIVATE "${CMAKE_SOURCE_DIR}/libraries/chain/include"
)
#if(MSVC)
# set_source_files_properties( node.cpp PROPERTIES COMPILE_FLAGS "/bigobj" )
#endif(MSVC)
#if (USE_PCH)
# set_target_properties(graphene_p2p PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
# cotire(graphene_p2p )
#endif(USE_PCH)
install( TARGETS
graphene_p2p
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View file

@ -1,96 +0,0 @@
# Network Protocol 2
Building a low-latency network requires P2P nodes that have low-latency
connections and a protocol designed to minimize latency. for the purpose
of this document we will assume that two nodes are located on opposite
sides of the globe with a ping time of 250ms.
## Announce, Request, Send Protocol
Under the prior network archtiecture, transactions and blocks were broadcast
in a manner similar to the Bitcoin protocol: inventory messages notify peers of
transactions and blocks, then peers fetch the transaction or block from one
peer. After validating the item a node will broadcast an inventory message to
its peers.
Under this model it will take 0.75 seconds for a peer to communicate a transaction
or block to another peer even if their size was 0 and there was no processing overhead.
This level of performance is unacceptable for a network attempting to produce one block
every second.
This prior protocol also sent every transaction twice: initial broadcast, and again as
part of a block.
## Push Protocol
To minimize latency each node needs to immediately broadcast the data it receives
to its peers after validating it. Given the average transaction size is less than
100 bytes, it is almost as effecient to send the transaction as it is to send
the notice (assuming a 20 byte transaction id)
Each node implements the following protocol:
onReceiveTransaction( from_peer, transaction )
if( isKnown( transaction.id() ) )
return
markKnown( transaction.id() )
if( !validate( transaction ) )
return
for( peer : peers )
if( peer != from_peer )
send( peer, transaction )
onReceiveBlock( from_peer, block_summary )
if( isKnown( block_summary )
return
full_block = reconstructFullBlcok( from_peer, block_summary )
if( !full_block ) disconnect from_peer
markKnown( block_summary )
if( !pushBlock( full_block ) ) disconnect from_peer
for( peer : peers )
if( peer != from_peer )
send( peer, block_summary )
onHello( new_peer, new_peer_head_block_num )
replyHello( new_peer ) // ack the hello message with our timestamp to measure latency
if( peers.size() >= max_peers )
send( new_peer, peers )
disconnect( new_peer )
return
while( new_peer_head_block_num < our_head_block_num )
sendFullBlock( new_peer, ++new_peer_head_block_num )
new_peer.synced = true
for( peer : peers )
send( peer, new_peer )
onHelloReply( from_peer, hello_reply )
update_latency_measure, disconnect if too slow
onReceivePeers( from_peer, peers )
addToPotentialPeers( peers )
onUpdateConnectionsTimer
if( peers.size() < desired_peers )
connect( random_potential_peer )
onFullBlock( from_peer, full_block )
if( !pushBlock( full_block ) ) disconnect from_peer
onStartup
init_potential_peers from config
start onUpdateConnectionsTimer

View file

@ -1,183 +0,0 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <fc/array.hpp>
#include <fc/io/varint.hpp>
#include <fc/network/ip.hpp>
#include <fc/io/raw.hpp>
#include <fc/crypto/ripemd160.hpp>
#include <fc/reflect/variant.hpp>
namespace graphene { namespace p2p {
using namespace graphene::chain;
struct message_header
{
uint32_t size; // number of bytes in message, capped at MAX_MESSAGE_SIZE
uint32_t msg_type;
};
typedef fc::uint160_t message_hash_type;
/**
* Abstracts the process of packing/unpacking a message for a
* particular channel.
*/
struct message : public message_header
{
std::vector<char> data;
message(){}
message( message&& m )
:message_header(m),data( std::move(m.data) ){}
message( const message& m )
:message_header(m),data( m.data ){}
/**
* Assumes that T::type specifies the message type
*/
template<typename T>
message( const T& m )
{
msg_type = T::type;
data = fc::raw::pack(m);
size = (uint32_t)data.size();
}
fc::uint160_t id()const
{
return fc::ripemd160::hash( data.data(), (uint32_t)data.size() );
}
/**
* Automatically checks the type and deserializes T in the
* opposite process from the constructor.
*/
template<typename T>
T as()const
{
try {
FC_ASSERT( msg_type == T::type );
T tmp;
if( data.size() )
{
fc::datastream<const char*> ds( data.data(), data.size() );
fc::raw::unpack( ds, tmp );
}
else
{
// just to make sure that tmp shouldn't have any data
fc::datastream<const char*> ds( nullptr, 0 );
fc::raw::unpack( ds, tmp );
}
return tmp;
} FC_RETHROW_EXCEPTIONS( warn,
"error unpacking network message as a '${type}' ${x} !=? ${msg_type}",
("type", fc::get_typename<T>::name() )
("x", T::type)
("msg_type", msg_type)
);
}
};
enum core_message_type_enum {
hello_message_type = 1000,
transaction_message_type = 1001,
block_message_type = 1002,
peer_message_type = 1003,
error_message_type = 1004
};
struct hello_message
{
static const core_message_type_enum type;
std::string user_agent;
uint16_t version;
fc::time_point timestamp;
fc::ip::address inbound_address;
uint16_t inbound_port;
uint16_t outbound_port;
public_key_type node_public_key;
fc::sha256 chain_id;
fc::variant_object user_data;
block_id_type head_block;
};
struct hello_reply_message
{
static const core_message_type_enum type;
fc::time_point hello_timestamp;
fc::time_point reply_timestamp;
};
struct transaction_message
{
static const core_message_type_enum type;
signed_transaction trx;
};
struct block_summary_message
{
static const core_message_type_enum type;
signed_block_header header;
vector<transaction_id_type> transaction_ids;
};
struct full_block_message
{
static const core_message_type_enum type;
signed_block block;
};
struct peers_message
{
static const core_message_type_enum type;
vector<fc::ip::endpoint> peers;
};
struct error_message
{
static const core_message_type_enum type;
string message;
};
} } // graphene::p2p
FC_REFLECT( graphene::p2p::message_header, (size)(msg_type) )
FC_REFLECT_DERIVED( graphene::p2p::message, (graphene::p2p::message_header), (data) )
FC_REFLECT_ENUM( graphene::p2p::core_message_type_enum,
(hello_message_type)
(transaction_message_type)
(block_message_type)
(peer_message_type)
(error_message_type)
)

Some files were not shown because too many files have changed in this diff Show more