diff --git a/.buildkite/coverage.yml b/.buildkite/coverage.yml new file mode 100644 index 00000000000..122fd0662a4 --- /dev/null +++ b/.buildkite/coverage.yml @@ -0,0 +1,29 @@ +steps: + - command: | + echo "--- :hammer: Building" && \ + /usr/bin/cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-4.0 -DCMAKE_C_COMPILER=clang-4.0 -DWASM_ROOT=/root/opt/wasm -DOPENSSL_ROOT_DIR=/usr/include/openssl -DBUILD_MONGO_DB_PLUGIN=true -DENABLE_COVERAGE_TESTING=true -DBUILD_DOXYGEN=false && \ + /usr/bin/ninja + echo "--- :spiral_note_pad: Generating Code Coverage Report" && \ + /usr/bin/ninja EOS_ut_coverage && \ + echo "--- :arrow_up: Publishing Code Coverage Report" && \ + buildkite-agent artifact upload "EOS_ut_coverage/**/*" s3://eosio-coverage/$BUILDKITE_JOB_ID && \ + cp /config/.coveralls.yml . && \ + /usr/local/bin/coveralls-lcov EOS_ut_coverage_filtered.info && \ + echo "+++ View Report" && \ + printf "\033]1339;url=https://eosio-coverage.s3-us-west-2.amazonaws.com/$BUILDKITE_JOB_ID/EOS_ut_coverage/index.html;content=View Full Coverage Report\a\n" + label: ":spiral_note_pad: Generate Report" + agents: + - "role=linux-coverage" + plugins: + docker#v1.1.1: + image: "eosio/ci:ubuntu18" + workdir: /data/job + mounts: + - /etc/buildkite-agent/config:/config + environment: + - BOOST_ROOT=/root/opt/boost_1_66_0 + - OPENSSL_ROOT_DIR=/usr/include/openssl + - WASM_ROOT=/root/opt/wasm + - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/opt/wasm/bin + - CI=true + timeout: 30 diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 148f982a469..5926c7766b2 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -25,6 +25,22 @@ steps: docker#v1.1.1: image: "eosio/ci:ubuntu" workdir: /data/job + timeout: 30 + + - command: | + echo "+++ :hammer: Building" && \ + echo 1 | ./eosio_build.sh && \ + echo "--- :compression: Compressing build directory" && \ + tar -pczf build.tar.gz build/ + label: ":ubuntu: 18.04 Build" + agents: + - "role=linux-builder" + artifact_paths: "build.tar.gz" + plugins: + docker#v1.1.1: + image: "eosio/ci:ubuntu18" + workdir: /data/job + timeout: 30 - command: | echo "+++ :hammer: Building" && \ @@ -39,6 +55,7 @@ steps: docker#v1.1.1: image: "eosio/ci:fedora" workdir: /data/job + timeout: 30 - command: | echo "+++ :hammer: Building" && \ @@ -53,6 +70,7 @@ steps: docker#v1.1.1: image: "eosio/ci:centos" workdir: /data/job + timeout: 30 - command: | echo "+++ :hammer: Building" && \ @@ -67,6 +85,7 @@ steps: docker#v1.1.1: image: "eosio/ci:amazonlinux" workdir: /data/job + timeout: 30 - wait @@ -112,6 +131,31 @@ steps: docker#v1.1.1: image: "eosio/ci:ubuntu" workdir: /data/job + timeout: 30 + + - command: | + echo "--- :arrow_down: Downloading build directory" && \ + buildkite-agent artifact download "build.tar.gz" . --step ":ubuntu: 18.04 Build" && + tar -zxf build.tar.gz && \ + echo "--- :m: Starting MongoDB" && \ + $(which mongod) --fork --logpath "$(pwd)"/mongod.log && \ + echo "+++ :microscope: Running tests" && \ + cd /data/job/build && ctest --output-on-failure + retry: + automatic: + limit: 1 + label: ":ubuntu: 18.04 Tests" + agents: + - "role=linux-tester" + artifact_paths: + - "mongod.log" + - "build/genesis.json" + - "build/config.ini" + plugins: + docker#v1.1.1: + image: "eosio/ci:ubuntu18" + workdir: /data/job + timeout: 30 - command: | echo "--- :arrow_down: Downloading build directory" && \ @@ -135,6 +179,7 @@ steps: docker#v1.1.1: image: "eosio/ci:fedora" workdir: /data/job + timeout: 30 - command: | echo "--- :arrow_down: Downloading build directory" && \ @@ -158,6 +203,7 @@ steps: docker#v1.1.1: image: "eosio/ci:centos" workdir: /data/job + timeout: 30 - command: | echo "--- :arrow_down: Downloading build directory" && \ @@ -181,3 +227,4 @@ steps: docker#v1.1.1: image: "eosio/ci:amazonlinux" workdir: /data/job + timeout: 30 diff --git a/.gitignore b/.gitignore index 1210a3f22a8..315a8bb2feb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.dot *.abi.hpp *.cmake +*.ninja \#* \.#* CMakeCache.txt @@ -29,6 +30,9 @@ libraries/egenesis/egenesis_full.cpp libraries/egenesis/embed_genesis libraries/types/type_generator libraries/types/types_test +libraries/fc/test/crypto/test_cypher_suites +libraries/testing/chain_tester + libraries/wallet/Doxyfile libraries/wallet/api_documentation.cpp @@ -39,24 +43,24 @@ libraries/wasm-jit/Source/Programs/Disassemble libraries/wasm-jit/Source/Programs/Test libraries/wasm-jit/Source/Programs/wavm -programs/cli_wallet/cli_wallet +programs/cleos/cleos programs/js_operation_serializer/js_operation_serializer -programs/witness_node/witness_node programs/data-dir -programs/eos-walletd/eos-walletd -programs/eosiod/eosiod -programs/eosioc/eosioc -programs/launcher/launcher programs/eosio-abigen/eosio-abigen +programs/cleos/config.hpp +programs/eosio-applesedemo/eosio-applesedemo +programs/eosio-launcher/config.hpp +programs/eosio-launcher/eosio-launcher +programs/keosd/keosd +programs/nodeos/config.hpp +programs/nodeos/nodeos scripts/tn_init.sh -tests/app_test -tests/chain_bench -tests/chain_test -tests/intense_test -tests/performance_test -tests/tests/config.hpp +tests/plugin_test +tests/config.hpp +unittests/config.hpp +unittests/unit_test doxygen @@ -65,10 +69,6 @@ witness_node_data_dir *.wallet -programs/witness_node/object_database/* - -object_database/* - *.pyc *.pyo diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c87a23f3bd..052363906f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set( CMAKE_CXX_STANDARD 14 ) set( CMAKE_CXX_EXTENSIONS ON ) set( CXX_STANDARD_REQUIRED ON) -set(VERSION_MAJOR 3) +set(VERSION_MAJOR 4) set(VERSION_MINOR 0) set(VERSION_PATCH 0) @@ -187,6 +187,7 @@ add_subdirectory( contracts ) add_subdirectory( plugins ) add_subdirectory( programs ) add_subdirectory( scripts ) +add_subdirectory( unittests ) add_subdirectory( tests ) add_subdirectory( tools ) add_subdirectory( debian ) diff --git a/CMakeModules/wasm.cmake b/CMakeModules/wasm.cmake index 8269c975089..c33e6a69592 100644 --- a/CMakeModules/wasm.cmake +++ b/CMakeModules/wasm.cmake @@ -151,12 +151,22 @@ macro(add_wast_executable) add_custom_command(OUTPUT ${DESTINATION_FOLDER}/${target}.wast DEPENDS ${target}.s - COMMAND $ -o ${DESTINATION_FOLDER}/${target}.wast -s 8192 ${MAX_MEMORY_PARAM} ${target}.s + COMMAND $ -o ${DESTINATION_FOLDER}/${target}.wast -s 10240 ${MAX_MEMORY_PARAM} ${target}.s COMMENT "Generating WAST ${target}.wast" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} VERBATIM ) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${target}.wast) + + add_custom_command(OUTPUT ${DESTINATION_FOLDER}/${target}.wasm + DEPENDS ${target}.wast + COMMAND $ ${DESTINATION_FOLDER}/${target}.wast ${DESTINATION_FOLDER}/${target}.wasm -n + COMMENT "Generating WASM ${target}.wasm" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + VERBATIM + ) + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${target}.wasm) + STRING (REPLACE "." "_" TARGET_VARIABLE "${target}") add_custom_command(OUTPUT ${DESTINATION_FOLDER}/${target}.wast.hpp @@ -182,7 +192,7 @@ macro(add_wast_executable) else() endif() - add_custom_target(${target} ALL DEPENDS ${DESTINATION_FOLDER}/${target}.wast.hpp ${extra_target_dependency}) + add_custom_target(${target} ALL DEPENDS ${DESTINATION_FOLDER}/${target}.wast.hpp ${extra_target_dependency} ${DESTINATION_FOLDER}/${target}.wasm) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${DESTINATION_FOLDER}/${target}.wast.hpp) diff --git a/Docker/README.md b/Docker/README.md index 2ea5a2cffb5..3b9b8842e77 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -61,7 +61,7 @@ After `docker-compose up -d`, two services named `nodeosd` and `keosd` will be s You can run the `cleos` commands via a bash alias. ```bash -alias cleos='docker-compose exec keosd /opt/eosio/bin/cleos -u http://nodeosd:8888' # For DAWN3.0, use '-H nodeosd' instead of '-u http://nodeosd:8888' +alias cleos='docker-compose exec keosd /opt/eosio/bin/cleos -u http://nodeosd:8888' cleos get info cleos get account inita ``` @@ -171,13 +171,13 @@ volumes: *NOTE:* the default version is the latest, you can change it to what you want -run `docker pull eosio/eos:latest` +run `docker pull eosio/eos:latest` run `docker-compose up` -### Dawn3.0 Testnet +### Dawn 4.0 Testnet -We can easliy set up a dawn3.0 local testnet using docker images. Just run the following commands: +We can easily set up a Dawn 4.0 local testnet using docker images. Just run the following commands: Note: if you want to use the mongo db plugin, you have to enable it in your `data-dir/config.ini` first. @@ -190,13 +190,13 @@ docker volume create --name=nodeos-data-volume docker volume create --name=keosd-data-volume docker volume create --name=mongo-data-volume # start containers -docker-compose -f docker-compose-dawn3.0.yaml up -d +docker-compose -f docker-compose-dawn4.0.yaml up -d # get chain info curl http://127.0.0.1:8888/v1/chain/get_info # get logs docker-compose logs nodeosd # stop containers -docker-compose -f docker-compose-dawn3.0.yaml down +docker-compose -f docker-compose-dawn4.0.yaml down ``` The `blocks` data are stored under `--data-dir` by default, and the wallet files are stored under `--wallet-dir` by default, of course you can change these as you want. diff --git a/Docker/docker-compose-dawn3.0.yaml b/Docker/docker-compose-dawn4.0.yaml similarity index 100% rename from Docker/docker-compose-dawn3.0.yaml rename to Docker/docker-compose-dawn4.0.yaml diff --git a/EXCHANGE_README.md b/EXCHANGE_README.md new file mode 100644 index 00000000000..47fcd344615 --- /dev/null +++ b/EXCHANGE_README.md @@ -0,0 +1,364 @@ +Exchange Deposit & Withdraw Documentation +----------------------------------------- + +This document is targeted toward exchanges who wish to automate deposit +and withdraw of standard-conforming EOSIO token contracts. The blockchain's +native token conforms to the standard. + + +Configuring Nodeos +------------------ +This tutorial uses the `cleos` commandline tool to query a local `nodeos` server +which should be connected to an eosio blockchain. `nodeos` will need to be configured +with the following plugins: + + 1. eosio::wallet_api_plugin + 2. eosio::history_api_plugin + 3. eosio::chain_api_plugin + +By default the history plugin will log the history of all accounts, but this is not +the recomended configuration as it will consume tens of gigabytes of RAM in the +medium term. For a more optimized memory footprint you should configure the history +plugin to only log activity relevant to your account(s). This can be achieved with +the following config param placed in your config.ini or passed on the commandline. + +``` + $ nodeos --filter_on_accounts youraccount +``` + +Replaying the Blockchain +------------------------ + +If you have already synced the blockchain without the history plugin, then you may need to +replay the blockchain to pickup any historical activity. + +``` + $ nodeos --replay --filter_on_accounts youraccount +``` + +You only need to replay once, subsequent runs of nodeos should not use the replay flag or +your startup times will be unnecessiarlly long. + + +Accepting Deposits +----------- +When designing this tutorial we assume that an exchange will poll `nodeos` for incoming +transactions and will want to know when a transfer is considered irreversible or final. + +With eosio based chains, finality of a transaction occurs once 2/3+1 of block produers have +either directly or indirectly confirmed the block. This could take from less than a second to +a couple of minutes, but either way nodeos will keep you posted on the status. + +## Initial Condition +``` +./cleos get currency balance eosio.token scott EOS +900.0000 EOS +``` + +We will now deposit some funds to exchange: + +``` +./cleos transfer scott exchange "1.0000 EOS" +executed transaction: 5ec797175dd24612acd8fc5a8685fa44caa8646cec0a87b12568db22a3df02fb 256 bytes 8k cycles +# eosio.token <= eosio.token::transfer {"from":"scott","to":"exchange","quantity":"1.0000 EOS","memo":""} +>> transfer +# scott <= eosio.token::transfer {"from":"scott","to":"exchange","quantity":"1.0000 EOS","memo":""} +# exchange <= eosio.token::transfer {"from":"scott","to":"exchange","quantity":"1.0000 EOS","memo":""} +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +This output indicates that the action "eosio.token::transfer" was delivered to 3 accounts/contracts, (eosio.token, scott, and exchange). +The eosio token standard requires that both the sender and receiver account/contract be notified of all transfer actions so those +accounts can run custom logic. At this time neither `scott` nor `exchange` has any contact set, but the transaction log +still notes that they were notified. + + +## Polling Account History +The account history consists of all actions which were either authorized by the account or received by the account. Since the +exchange received the `eosio.token::transfer` action it is listed in the history. If you are using the console confirmed and +irreversible transactions are printed in "green" while unconfirmed transactions are printed in "yellow". Without color you +can tell whether a transaction is confirmed or not by the first character, '#' for irreversible and '?' for potentially reversable. + +``` +./cleos get actions exchange +# seq when contract::action => receiver trx id... args +================================================================================================================ +# 0 2018-04-29T01:09:45.000 eosio.token::transfer => exchange 5ec79717... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +``` + +Do a few more transfers: + +``` +./cleos get actions exchange +# seq when contract::action => receiver trx id... args +================================================================================================================ +# 0 2018-04-29T01:09:45.000 eosio.token::transfer => exchange 5ec79717... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +# 1 2018-04-29T01:16:25.000 eosio.token::transfer => exchange 2269828c... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +? 2 2018-04-29T01:19:54.000 eosio.token::transfer => exchange 213f3797... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +``` + +The last transfer is still pending, waiting on irreversibility. + + +The "seq" column represents the index of actions for your specific account, it will always increment as new relevant actions are added. + +The `cleos get actions` command allows you some control over which actions are fetched, you can view the help for this command with `-h` + +``` +./cleos get actions -h +Usage: ./cleos get actions [OPTIONS] account_name [pos] [offset] + +Positionals: + account_name TEXT name of account to query on + pos INT sequence number of action for this account, -1 for last + offset INT get actions [pos,pos+offset] for positive offset or [pos-offset,pos) for negative offset + + Options: + -j,--json print full json + --full don't truncate action json + --pretty pretty print full action json + --console print console output generated by action +``` + +To get only the last action you would do the following... + +``` +./cleos get actions exchange -1 -1 +# seq when contract::action => receiver trx id... args +================================================================================================================ +# 2 2018-04-29T01:19:54.000 eosio.token::transfer => exchange 213f3797... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +``` + +This says go to the last sequence number (indicated by pos = -1) and then fetch "1" item prior to it (offset = -1). This should +return sequence in the range [3-1,3) or [2,3) which is only row 2. In this case "-1" position means "one past the last sequence" and +operates like and end iterator from c++ containers. + +### Fetching only "New" Actions + +Since we presume your exchange is running a polling micro-service, it will want to fetch the "next unprocessed deposit". In this case the +microservice will need to track the seq number of the "last processed seq". For the sake of this example, we will assume that +"seq 0" has been processed and that we want to fetch "seq 1" if any. + +We pass pos=1 and offset=0 to get the range [1,1+0] or [1,1]. +``` +./cleos get actions exchange 1 0 +# seq when contract::action => receiver trx id... args +================================================================================================================ +# 1 2018-04-29T01:16:25.000 eosio.token::transfer => exchange 2269828c... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +``` + +We can call this in a loop procesing each confirmed action (those starting with #) until we either run out of items or +we find an unconfirmed action (starting with ?). + +``` +./cleos get actions exchange 3 0 +# seq when contract::action => receiver trx id... args +================================================================================================================ +``` + +### Machine Readable Account History (JSON) + +So far this tutorial has focused on using `cleos` to fetch and display the history, but cleos is merely a light-weight +wrapper around a json-rpc interface. `cleos` can dump the raw json returned from the json-rpc request or you can make +your own json-rpc request. + +Here is the JSON returned when querying sequence 2. +``` +./cleos get actions exchange 2 0 -j +{ + "actions": [{ + "global_action_seq": 32856, + "account_action_seq": 2, + "block_num": 32759, + "block_time": "2018-04-29T01:19:54.000", + "action_trace": { + "receipt": { + "receiver": "exchange", + "act_digest": "00686ff415fe97951a942889dbaed2b880043e3ae6ac2d5579318bbb2d30060f", + "global_sequence": 32856, + "recv_sequence": 3, + "auth_sequence": [[ + "scott", + 43 + ] + ] + }, + "act": { + "account": "eosio.token", + "name": "transfer", + "authorization": [{ + "actor": "scott", + "permission": "active" + } + ], + "data": { + "from": "scott", + "to": "exchange", + "quantity": "1.0000 EOS", + "memo": "" + }, + "hex_data": "00000000809c29c20000008a4dd35057102700000000000004454f530000000000" + }, + "elapsed": 52, + "cpu_usage": 1000, + "console": "", + "total_inline_cpu_usage": 1000, + "trx_id": "213f37972498cbae5abf6bcb5aec82e09967df7f04cf90f67b7d63a6bb871d58", + "inline_traces": [] + } + } + ], + "last_irreversible_block": 35062 +} +``` + +Given this JSON, an action is irreversible (final) if `"block_num" < "last_irreversible_block"`. + +You can identify irreversible deposits by the following: + +``` + actions[0].action_trace.act.account == "eosio.token" && + actions[0].action_trace.act.name == "transfer" && + actions[0].action_trace.act.data.quantity == "X.0000 EOS" && + actions[0].action_trace.to == "exchange" && + actions[0].action_trace.memo == "KEY TO IDENTIFY INTERNAL ACCOUNT" && + actions[0].action_trace.receipt.receiver == "exchange" && + actions[0].block_num < last_irreversible_block +``` + +In practice you should give your customers a "memo" that identifies which of your internal accounts you should +credit with the deposit. + +## WARNING + +It is critical that you validate all of the conditions above, including the token symbol name. Users can create +other contracts with "transfer" actions that "notify" your account. If you do not validate all of the above properties +then you may process "false deposits". + +``` + actions[0].action_trace.act.account == "eosio.token" && + actions[0].action_trace.receipt.receiver == "exchange" +``` + +### Validating Balance + +Now that we have received 3 deposits we should see that the exchange has a balance of 3.0000 EOS. + +``` +./cleos get currency balance eosio.token exchange EOS +3.0000 EOS +``` + +# Processing Withdraws + +(note, while generating this tutorial scott deposited another 1.0000 EOS (seq 3) for total exchange balance of 4.0000 EOS.) + +When a user requests a withdraw from your exchange they will need to provide you with their eosio account name and +the amount to be withdrawn. You can then run the cleos command which will interact with the "unlocked" wallet +running on `nodeos` which should only enable localhost connections. More advanced usage would have a separate +key-server (`keos`), but that will be covered later. + +Lets assume scott wants to withdraw `1.0000 EOS`: +``` +./cleos transfer exchange scott "1.0000 EOS" +executed transaction: 93e785202e7502bb1383ad10e786cc20f7dd738d3fd3da38712b3fb38fb9af26 256 bytes 8k cycles +# eosio.token <= eosio.token::transfer {"from":"exchange","to":"scott","quantity":"1.0000 EOS","memo":""} +>> transfer +# exchange <= eosio.token::transfer {"from":"exchange","to":"scott","quantity":"1.0000 EOS","memo":""} +# scott <= eosio.token::transfer {"from":"exchange","to":"scott","quantity":"1.0000 EOS","memo":""} +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +At this stage your local `nodeos` client accepted the transaction and likely broadcast it to the broader network. + +Now we can get the history and see that there are "3" new actions listed all with trx id `93e78520...` which is what +our transfer command returned to us. Because `exchange` authorized the transaction it is informed of all accounts which +processed and accepted the 'transfer'. In this case the 'eosio.token' contract processed it and updated balances, the +sender ('exchange') processed it and so did the receiver ('scott') and all 3 contracts/accounts approved it and/or performed +state transitions based upon the action. + +``` +./cleos get actions exchange -1 -8 +# seq when contract::action => receiver trx id... args +================================================================================================================ +# 0 2018-04-29T01:09:45.000 eosio.token::transfer => exchange 5ec79717... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +# 1 2018-04-29T01:16:25.000 eosio.token::transfer => exchange 2269828c... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +# 2 2018-04-29T01:19:54.000 eosio.token::transfer => exchange 213f3797... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +# 3 2018-04-29T01:53:57.000 eosio.token::transfer => exchange 8b7766ac... {"from":"scott","to":"exchange","quantity":"1.0000 EOS","mem... +# 4 2018-04-29T01:54:17.500 eosio.token::transfer => eosio.token 93e78520... {"from":"exchange","to":"scott","quantity":"1.0000 EOS","mem... +# 5 2018-04-29T01:54:17.500 eosio.token::transfer => exchange 93e78520... {"from":"exchange","to":"scott","quantity":"1.0000 EOS","mem... +# 6 2018-04-29T01:54:17.500 eosio.token::transfer => scott 93e78520... {"from":"exchange","to":"scott","quantity":"1.0000 EOS","mem... +``` + +By processing the history we can also be informed when our transaction was confirmed. In practice it may be useful to embed an exchange-specify memo +on the withdraw request which you can use to map to your private database state which tracks the withdraw process *or* you could simply use the +transaction ID. When your account history microservice comes across seq 5 and sees it is irreversible it can then mark your withdaw as complete. + +### Handling Errors + +Sometimes network issues may cause a transaction to fail and never be included in a block. Your internal database will need to know when this has happend +so that it can inform the user and/or try again. If you do not get an immediate error when you submit your local transfer, then you must wait for +the transaction to expire. Every transaction has an "expiration" after which the transaction can never be applied. Once the last irreversible block has +moved past the expiration time you can safely mark your attempted withdaw as failed and not worry about it "floating around the ether" to be applied +when you least expect. + +By default cleos sets an expiration window of just 2 minutes. This is long enough to allow all 21 producers an opportunity to include the transaction. + +``` + ./cleos transfer exchange scott "1.0000 EOS" -j -d +{ + "expiration": "2018-04-29T01:58:12", + "ref_block_num": 37282, + "ref_block_prefix": 351570603, + "max_net_usage_words": 0, + "max_kcpu_usage": 0, + "delay_sec": 0, + "context_free_actions": [], + ... + +``` + +Your microservice can query the last irreversible block number and the head block time using cleos. +``` +./cleos get info +{ + "server_version": "0812f84d", + "head_block_num": 39313, + "last_irreversible_block_num": 39298, + "last_irreversible_block_id": "000099823bfc4f0b936d8e48c70fc3f1619eb8d21989d160a9fe23655f1f5c79", + "head_block_id": "000099912473a7a3699ad682f731d1874ebddcf4b60eff79f8e6e4216077278d", + "head_block_time": "2018-04-29T02:14:31", + "head_block_producer": "producer2" +} +``` + +### Exchange Security + +This tutorial shows the minimal viable deposit/withdraw handlers and assumes a single wallet which contains all keys necessary to +authorize deposits and withdaws. A security-focused exchange would take the following additional steps: + +1. keep vast majority of funds in a time-delayed, multi-sig controlled account +2. use multi-sig on the hot wallet with several independent processes/servers double-checking all withdraws +3. deploy a custom contract that only allows withdraws to KYC'd accounts and require multi-sig to white-list accounts +4. deploy a custom contract that only accepts deposits of known tokens from KYC'd accounts +5. deploy a custom contract that enforces a mandatory 24 hour waiting period for all withdraws +6. utilize hardware wallets for all signing, even automated withdraw + +Customer's want immediate withdraws, but they also want the exchange to be protected. The blockchain-enforced 24 hour period +lets the customer know the money is "on the way" while also informing potential-hackers that the exchange has 24 hours to +respond to unauthorized access. Furthermore, if the exchange emails/text messages users upon start of withdraw, users have +24 hours to contact the exchange and fix any unauthorized access to their individual account. + +Information on how to utilize these more advanced techniques will be available in a future document. + + + + + + + + + + + + diff --git a/contracts/eosio.bios/eosio.bios.abi b/contracts/eosio.bios/eosio.bios.abi index 416fa4338fe..7d0d17b6e16 100644 --- a/contracts/eosio.bios/eosio.bios.abi +++ b/contracts/eosio.bios/eosio.bios.abi @@ -33,8 +33,7 @@ "name": "set_producers", "base": "", "fields": [ - {"name":"version", "type":"uint32"}, - {"name":"producers", "type":"producer_key[]"} + {"name":"schedule", "type":"producer_key[]"} ] },{ "name": "require_auth", @@ -42,12 +41,6 @@ "fields": [ {"name":"from", "type":"account_name"} ] - },{ - "name": "nonce", - "base": "", - "fields": [ - {"name":"value", "type":"string"} - ] }], "actions": [{ "name": "setalimits", @@ -69,10 +62,6 @@ "name": "reqauth", "type": "require_auth", "ricardian_contract": "" - },{ - "name": "nonce", - "type": "nonce", - "ricardian_contract": "" } ], "tables": [], diff --git a/contracts/eosio.bios/eosio.bios.hpp b/contracts/eosio.bios/eosio.bios.hpp index 0af50f77ba7..bb67f8fb49a 100644 --- a/contracts/eosio.bios/eosio.bios.hpp +++ b/contracts/eosio.bios/eosio.bios.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include -#include +#include namespace eosio { @@ -20,13 +19,15 @@ namespace eosio { } void setglimits( uint64_t ram, uint64_t net, uint64_t cpu ) { + (void)ram; (void)net; (void)cpu; require_auth( _self ); } - void setprods( producer_schedule sch ) { + void setprods( std::vector schedule ) { + (void)schedule; // schedule argument just forces the deserialization of the action data into vector (necessary check) require_auth( _self ); char buffer[action_data_size()]; - read_action_data( buffer, sizeof(buffer) ); + read_action_data( buffer, sizeof(buffer) ); // should be the same data as eosio::pack(schedule) set_active_producers(buffer, sizeof(buffer)); } diff --git a/contracts/eosio.msig/eosio.msig.abi b/contracts/eosio.msig/eosio.msig.abi index 9aa20238691..7a500522b9b 100644 --- a/contracts/eosio.msig/eosio.msig.abi +++ b/contracts/eosio.msig/eosio.msig.abi @@ -28,19 +28,26 @@ "base": "", "fields": [ {"name": "expiration", "type": "time_point_sec"}, - {"name": "region", "type": "uint16"}, - {"name": "ref_block_num", "type": "uint32"}, - {"name": "ref_block_prefix", "type": "uint16"}, + {"name": "ref_block_num", "type": "uint16"}, + {"name": "ref_block_prefix", "type": "uint32"}, {"name": "max_net_usage_words", "type": "varuint32"}, - {"name": "max_kcpu_usage", "type": "varuint32"}, + {"name": "max_cpu_usage_ms", "type": "uint8"}, {"name": "delay_sec", "type": "varuint32"} ] + },{ + "name": "extension", + "base": "", + "fields": [ + {"name": "type", "type" : "uint16" }, + {"name": "data", "type": "bytes"} + ] },{ "name": "transaction", "base": "transaction_header", "fields": [ {"name": "context_free_actions", "type": "action[]"}, - {"name": "actions", "type": "action[]"} + {"name": "actions", "type": "action[]"}, + {"name": "transaction_extensions", "type": "extension[]"} ] },{ "name": "propose", diff --git a/contracts/eosio.msig/eosio.msig.cpp b/contracts/eosio.msig/eosio.msig.cpp index 78ba41d5cbc..72b452f1beb 100644 --- a/contracts/eosio.msig/eosio.msig.cpp +++ b/contracts/eosio.msig/eosio.msig.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace eosio { @@ -9,7 +10,7 @@ because parsing data in the dispatcher uses too much CPU in case if proposed tra If we use dispatcher the function signature should be: -void multisig::propose( account_name proposer, +void multisig::propose( account_name proposer, name proposal_name, vector requested, transaction trx) @@ -33,13 +34,18 @@ void multisig::propose() { ds >> trx_header; require_auth( proposer ); - eosio_assert( trx_header.expiration > now(), "transaction expired" ); + eosio_assert( trx_header.expiration >= now(), "transaction expired" ); //eosio_assert( trx_header.actions.size() > 0, "transaction must have at least one action" ); proposals proptable( _self, proposer ); eosio_assert( proptable.find( proposal_name ) == proptable.end(), "proposal with the same name exists" ); - check_auth( buffer+trx_pos, size-trx_pos, requested ); + bytes packed_requested = pack(requested); + auto res = ::check_transaction_authorization( buffer+trx_pos, size-trx_pos, + (const char*)0, 0, + packed_requested.data(), packed_requested.size() + ); + eosio_assert( res > 0, "transaction authorization failed" ); proptable.emplace( proposer, [&]( auto& prop ) { prop.proposal_name = proposal_name; @@ -57,7 +63,7 @@ void multisig::approve( account_name proposer, name proposal_name, permission_le auto itr = std::find( prop_it->requested_approvals.begin(), prop_it->requested_approvals.end(), level ); eosio_assert( itr != prop_it->requested_approvals.end(), "approval is not on the list of requested approvals" ); - + proptable.modify( prop_it, proposer, [&]( auto& mprop ) { mprop.provided_approvals.push_back( level ); mprop.requested_approvals.erase( itr ); @@ -103,11 +109,22 @@ void multisig::exec( account_name proposer, name proposal_name, account_name exe transaction_header trx_header; datastream ds( prop_it->packed_transaction.data(), prop_it->packed_transaction.size() ); ds >> trx_header; + eosio_assert( trx_header.expiration >= now(), "transaction expired" ); + + // Updating expiration is not necessary because the expiration field of a deferred transaction is modified to be valid anyway + /* trx_header.expiration = now() + 60; ds.seekp(0); ds << trx_header; + */ + + bytes packed_provided_approvals = pack(prop_it->provided_approvals); + auto res = ::check_transaction_authorization( prop_it->packed_transaction.data(), prop_it->packed_transaction.size(), + (const char*)0, 0, + packed_provided_approvals.data(), packed_provided_approvals.size() + ); + eosio_assert( res > 0, "transaction authorization failed" ); - check_auth( prop_it->packed_transaction, prop_it->provided_approvals ); send_deferred( (uint128_t(proposer) << 64) | proposal_name, executer, prop_it->packed_transaction.data(), prop_it->packed_transaction.size() ); proptable.erase(prop_it); diff --git a/contracts/eosio.system/common.hpp b/contracts/eosio.system/common.hpp deleted file mode 100644 index 42be96e8d88..00000000000 --- a/contracts/eosio.system/common.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace eosiosystem { - - template - class common { - public: - static constexpr account_name system_account = SystemAccount; - - - static constexpr uint32_t blocks_per_producer = 12; - static constexpr uint32_t seconds_per_day = 24 * 3600; - static constexpr uint32_t days_per_4years = 1461; - - static eosio_global_state& get_default_parameters() { - static eosio_global_state dp; - get_blockchain_parameters(dp); - return dp; - } - }; - -} diff --git a/contracts/eosio.system/delegate_bandwidth.cpp b/contracts/eosio.system/delegate_bandwidth.cpp index 07077f4f217..43482a0c0a3 100644 --- a/contracts/eosio.system/delegate_bandwidth.cpp +++ b/contracts/eosio.system/delegate_bandwidth.cpp @@ -14,6 +14,8 @@ #include + +#include #include namespace eosiosystem { @@ -29,17 +31,16 @@ namespace eosiosystem { static constexpr time refund_delay = 3*24*3600; static constexpr time refund_expiration_time = 3600; - struct total_resources { + struct user_resources { account_name owner; asset net_weight; asset cpu_weight; - asset storage_stake; - uint64_t storage_bytes = 0; + int64_t ram_bytes = 0; uint64_t primary_key()const { return owner; } // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( total_resources, (owner)(net_weight)(cpu_weight)(storage_stake)(storage_bytes) ) + EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_bytes) ) }; @@ -51,13 +52,11 @@ namespace eosiosystem { account_name to; asset net_weight; asset cpu_weight; - asset storage_stake; - uint64_t storage_bytes = 0; uint64_t primary_key()const { return to; } // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight)(storage_stake)(storage_bytes) ) + EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight) ) }; @@ -72,46 +71,168 @@ namespace eosiosystem { EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(amount) ) }; - typedef eosio::multi_index< N(totalband), total_resources> total_resources_table; + /** + * These tables are designed to be constructed in the scope of the relevant user, this + * facilitates simpler API for per-user queries + */ + typedef eosio::multi_index< N(userres), user_resources> user_resources_table; typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table; typedef eosio::multi_index< N(refunds), refund_request> refunds_table; - void system_contract::delegatebw( const account_name from, const account_name receiver, - const asset stake_net_quantity, const asset stake_cpu_quantity, - const asset stake_storage_quantity ) + + /** + * Called after a new account is created. This code enforces resource-limits rules + * for new accounts as well as new account naming conventions. + * + * 1. accounts cannot contain '.' symbols which forces all acccounts to be 12 + * characters long without '.' until a future account auction process is implemented + * which prevents name squatting. + * + * 2. new accounts must stake a minimal number of tokens (as set in system parameters) + * therefore, this method will execute an inline buyram from receiver for newacnt in + * an amount equal to the current new account creation fee. + */ + void native::newaccount( account_name creator, + account_name newact + /* no need to parse authorites + const authority& owner, + const authority& active, + const authority& recovery*/ ) { + eosio::print( eosio::name{creator}, " created ", eosio::name{newact}, "\n"); + + user_resources_table userres( _self, newact); + + userres.emplace( newact, [&]( auto& res ) { + res.owner = newact; + }); + + set_resource_limits( newact, + 0,// r->ram_bytes, + 0, 0 ); + // r->net_weight.amount, + // r->cpu_weight.amount ); + } + + /** + * This action will buy an exact amount of ram and bill the payer the current market price. + */ + void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { + auto itr = _rammarket.find(S(4,RAMEOS)); + auto tmp = *itr; + auto eosout = tmp.convert( asset(bytes,S(0,RAM)), S(4,EOS) ); + print( "eosout: ", eosout, " ", tmp.base.balance, " ", tmp.quote.balance, "\n" ); + + buyram( payer, receiver, eosout ); + } + + + /** + * When buying ram the payer irreversiblly transfers quant to system contract and only + * the receiver may reclaim the tokens via the sellram action. The receiver pays for the + * storage of all database records associated with this action. + * + * RAM is a scarce resource whose supply is defined by global properties max_ram_size. RAM is + * priced using the bancor algorithm such that price-per-byte with a constant reserve ratio of 100:1. + */ + void system_contract::buyram( account_name payer, account_name receiver, asset quant ) { - eosio_assert( stake_cpu_quantity.amount >= 0, "must stake a positive amount" ); - eosio_assert( stake_net_quantity.amount >= 0, "must stake a positive amount" ); - eosio_assert( stake_storage_quantity.amount >= 0, "must stake a positive amount" ); + print( "\n payer: ", eosio::name{payer}, " buys ram for ", eosio::name{receiver}, " with ", quant, "\n" ); + require_auth( payer ); + eosio_assert( quant.amount > 0, "must purchase a positive amount" ); + + if( payer != N(eosio) ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + { payer, N(eosio), quant, std::string("buy ram") } ); + } - asset total_stake = stake_cpu_quantity + stake_net_quantity + stake_storage_quantity; - eosio_assert( total_stake.amount > 0, "must stake a positive amount" ); + print( "free ram: ", _gstate.free_ram(), "\n"); - require_auth( from ); + int64_t bytes_out; + + auto itr = _rammarket.find(S(4,RAMEOS)); + _rammarket.modify( itr, 0, [&]( auto& es ) { + bytes_out = es.convert( quant, S(0,RAM) ).amount; + }); + + print( "ram bytes out: ", bytes_out, "\n" ); + + eosio_assert( bytes_out > 0, "must reserve a positive amount" ); + + _gstate.total_ram_bytes_reserved += uint64_t(bytes_out); + _gstate.total_ram_stake.amount += quant.amount; - //eosio_assert( is_account( receiver ), "can only delegate resources to an existing account" ); - int64_t storage_bytes = 0; - if ( 0 < stake_storage_quantity.amount ) { - global_state_singleton gs( _self, _self ); - auto parameters = gs.exists() ? gs.get() : get_default_parameters(); - const eosio::asset token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()); - //make sure that there is no posibility of overflow here - int64_t storage_bytes_estimated = int64_t( parameters.max_storage_size - parameters.total_storage_bytes_reserved ) - * int64_t(parameters.storage_reserve_ratio) * stake_storage_quantity - / ( token_supply - parameters.total_storage_stake ) / 1000 /* reserve ratio coefficient */; - - storage_bytes = ( int64_t(parameters.max_storage_size) - int64_t(parameters.total_storage_bytes_reserved) - storage_bytes_estimated ) - * int64_t(parameters.storage_reserve_ratio) * stake_storage_quantity - / ( token_supply - stake_storage_quantity - parameters.total_storage_stake ) / 1000 /* reserve ratio coefficient */; - - eosio_assert( 0 < storage_bytes, "stake is too small to increase storage even by 1 byte" ); - - parameters.total_storage_bytes_reserved += uint64_t(storage_bytes); - parameters.total_storage_stake += stake_storage_quantity; - gs.set( parameters, _self ); + user_resources_table userres( _self, receiver ); + auto res_itr = userres.find( receiver ); + if( res_itr == userres.end() ) { + res_itr = userres.emplace( receiver, [&]( auto& res ) { + res.owner = receiver; + res.ram_bytes = bytes_out; + }); + } else { + userres.modify( res_itr, receiver, [&]( auto& res ) { + res.ram_bytes += bytes_out; + }); } + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + } - del_bandwidth_table del_tbl( _self, from ); + + /** + * While buying ram uses the current market price according to the bancor-algorithm, selling ram only + * refunds the purchase price to the account. In this way there is no profit to be made through buying + * and selling ram. + */ + void system_contract::sellram( account_name account, uint64_t bytes ) { + require_auth( account ); + + user_resources_table userres( _self, account ); + auto res_itr = userres.find( account ); + eosio_assert( res_itr != userres.end(), "no resource row" ); + eosio_assert( res_itr->ram_bytes >= bytes, "insufficient quota" ); + + asset tokens_out; + auto itr = _rammarket.find(S(4,RAMEOS)); + _rammarket.modify( itr, 0, [&]( auto& es ) { + tokens_out = es.convert( asset(bytes,S(0,RAM)), S(4,EOS) ); + print( "out: ", tokens_out, "\n" ); + }); + + _gstate.total_ram_bytes_reserved -= bytes; + _gstate.total_ram_stake.amount -= tokens_out.amount; + + //// this shouldn't happen, but just in case it does we should prevent it + eosio_assert( _gstate.total_ram_stake.amount >= 0, "error, attempt to unstake more tokens than previously staked" ); + + userres.modify( res_itr, account, [&]( auto& res ) { + res.ram_bytes -= bytes; + }); + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + + if( N(eosio) != account ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), account, asset(tokens_out), std::string("sell ram") } ); + } + } + + void system_contract::delegatebw( account_name from, account_name receiver, + asset stake_net_quantity, + asset stake_cpu_quantity, bool transfer ) + + { + require_auth( from ); + print( "from: ", eosio::name{from}, " to: ", eosio::name{receiver}, " net: ", stake_net_quantity, " cpu: ", stake_cpu_quantity ); + + eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" ); + eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" ); + + auto total_stake = stake_cpu_quantity.amount + stake_net_quantity.amount; + eosio_assert( total_stake > 0, "must stake a positive amount" ); + + account_name source_stake_from = from; + + if( transfer ) from = receiver; + + del_bandwidth_table del_tbl( _self, from); auto itr = del_tbl.find( receiver ); if( itr == del_tbl.end() ) { del_tbl.emplace( from, [&]( auto& dbo ){ @@ -119,128 +240,139 @@ namespace eosiosystem { dbo.to = receiver; dbo.net_weight = stake_net_quantity; dbo.cpu_weight = stake_cpu_quantity; - dbo.storage_stake = stake_storage_quantity; - dbo.storage_bytes = uint64_t(storage_bytes); }); } else { - del_tbl.modify( itr, from, [&]( auto& dbo ){ + del_tbl.modify( itr, 0, [&]( auto& dbo ){ dbo.net_weight += stake_net_quantity; dbo.cpu_weight += stake_cpu_quantity; - dbo.storage_stake += stake_storage_quantity; - dbo.storage_bytes += uint64_t(storage_bytes); }); } - total_resources_table totals_tbl( _self, receiver ); + print( "totals" ); + user_resources_table totals_tbl( _self, receiver ); auto tot_itr = totals_tbl.find( receiver ); if( tot_itr == totals_tbl.end() ) { tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { tot.owner = receiver; tot.net_weight = stake_net_quantity; tot.cpu_weight = stake_cpu_quantity; - tot.storage_stake = stake_storage_quantity; - tot.storage_bytes = uint64_t(storage_bytes); }); } else { totals_tbl.modify( tot_itr, from == receiver ? from : 0, [&]( auto& tot ) { tot.net_weight += stake_net_quantity; tot.cpu_weight += stake_cpu_quantity; - tot.storage_stake += stake_storage_quantity; - tot.storage_bytes += uint64_t(storage_bytes); }); } - //set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, tot_itr->net_weight.quantity, tot_itr->cpu_weight.quantity ); + set_resource_limits( tot_itr->owner, tot_itr->ram_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)}, - { from, N(eosio), total_stake, std::string("stake bandwidth") } ); + if( N(eosio) != source_stake_from ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)}, + { source_stake_from, N(eosio), asset(total_stake), std::string("stake bandwidth") } ); + } - if ( asset(0) < stake_net_quantity + stake_cpu_quantity ) { - increase_voting_power( from, stake_net_quantity + stake_cpu_quantity ); + print( "voters \n" ); + auto from_voter = _voters.find(from); + if( from_voter == _voters.end() ) { + print( " create voter \n" ); + from_voter = _voters.emplace( from, [&]( auto& v ) { + v.owner = from; + v.staked = total_stake; + print( " vote weight: ", v.last_vote_weight, "\n" ); + }); + } else { + _voters.modify( from_voter, 0, [&]( auto& v ) { + v.staked += total_stake; + print( " vote weight: ", v.last_vote_weight, "\n" ); + }); } + print( "voteproducer\n" ); + if( from_voter->producers.size() || from_voter->proxy ) { + voteproducer( from, from_voter->proxy, from_voter->producers ); + } } // delegatebw - void system_contract::undelegatebw( const account_name from, const account_name receiver, - const asset unstake_net_quantity, const asset unstake_cpu_quantity, - const uint64_t unstake_storage_bytes ) + void validate_b1_vesting( int64_t stake ) { + const int64_t seconds_per_year = 60*60*24*365; + const int64_t base_time = 1527811200; /// 2018-06-01 + const int64_t max_claimable = 100'000'000'0000ll; + const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (10*seconds_per_year) ); + + eosio_assert( max_claimable - claimable <= stake, "b1 can only claim their tokens over 10 years" ); + } + + void system_contract::undelegatebw( account_name from, account_name receiver, + asset unstake_net_quantity, asset unstake_cpu_quantity ) { - eosio_assert( unstake_cpu_quantity.amount >= 0, "must unstake a positive amount" ); - eosio_assert( unstake_net_quantity.amount >= 0, "must unstake a positive amount" ); + eosio_assert( unstake_cpu_quantity >= asset(), "must unstake a positive amount" ); + eosio_assert( unstake_net_quantity >= asset(), "must unstake a positive amount" ); require_auth( from ); - //eosio_assert( is_account( receiver ), "can only delegate resources to an existing account" ); - del_bandwidth_table del_tbl( _self, from ); const auto& dbw = del_tbl.get( receiver ); - eosio_assert( dbw.net_weight >= unstake_net_quantity, "insufficient staked net bandwidth" ); - eosio_assert( dbw.cpu_weight >= unstake_cpu_quantity, "insufficient staked cpu bandwidth" ); - eosio_assert( dbw.storage_bytes >= unstake_storage_bytes, "insufficient staked storage" ); - - eosio::asset storage_stake_decrease(0, system_token_symbol); - if ( 0 < unstake_storage_bytes ) { - storage_stake_decrease = 0 < dbw.storage_bytes ? - dbw.storage_stake * int64_t(unstake_storage_bytes) / int64_t(dbw.storage_bytes) - : eosio::asset(0, system_token_symbol); - global_state_singleton gs( _self, _self ); - auto parameters = gs.get(); //it should exist if user staked for bandwith - parameters.total_storage_bytes_reserved -= unstake_storage_bytes; - parameters.total_storage_stake -= storage_stake_decrease; - gs.set( parameters, _self ); - } + eosio_assert( dbw.net_weight.amount >= unstake_net_quantity.amount, "insufficient staked net bandwidth" ); + eosio_assert( dbw.cpu_weight.amount >= unstake_cpu_quantity.amount, "insufficient staked cpu bandwidth" ); + + auto total_refund = unstake_cpu_quantity.amount + unstake_net_quantity.amount; + + _voters.modify( _voters.get(from), 0, [&]( auto& v ) { + v.staked -= total_refund; + if( from == N(b1) ) { + validate_b1_vesting( v.staked ); + } + }); - eosio::asset total_refund = unstake_cpu_quantity + unstake_net_quantity + storage_stake_decrease; - eosio_assert( total_refund.amount > 0, "must unstake a positive amount" ); + eosio_assert( total_refund > 0, "must unstake a positive amount" ); del_tbl.modify( dbw, from, [&]( auto& dbo ){ dbo.net_weight -= unstake_net_quantity; dbo.cpu_weight -= unstake_cpu_quantity; - dbo.storage_stake -= storage_stake_decrease; - dbo.storage_bytes -= unstake_storage_bytes; - }); + }); + + user_resources_table totals_tbl( _self, receiver ); - total_resources_table totals_tbl( _self, receiver ); const auto& totals = totals_tbl.get( receiver ); totals_tbl.modify( totals, 0, [&]( auto& tot ) { tot.net_weight -= unstake_net_quantity; tot.cpu_weight -= unstake_cpu_quantity; - tot.storage_stake -= storage_stake_decrease; - tot.storage_bytes -= unstake_storage_bytes; - }); + }); - //set_resource_limits( totals.owner, totals.storage_bytes, totals.net_weight.quantity, totals.cpu_weight.quantity ); + set_resource_limits( receiver, totals.ram_bytes, totals.net_weight.amount, totals.cpu_weight.amount ); refunds_table refunds_tbl( _self, from ); //create refund request auto req = refunds_tbl.find( from ); if ( req != refunds_tbl.end() ) { refunds_tbl.modify( req, 0, [&]( refund_request& r ) { - r.amount += unstake_net_quantity + unstake_cpu_quantity + storage_stake_decrease; + r.amount += unstake_net_quantity + unstake_cpu_quantity; r.request_time = now(); }); } else { refunds_tbl.emplace( from, [&]( refund_request& r ) { r.owner = from; - r.amount = unstake_net_quantity + unstake_cpu_quantity + storage_stake_decrease; + r.amount = unstake_net_quantity + unstake_cpu_quantity; r.request_time = now(); }); } + //create or replace deferred transaction //refund act; //act.owner = from; - eosio::transaction out( now() + refund_delay + refund_expiration_time ); + eosio::transaction out; out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from ); out.delay_sec = refund_delay; out.send( from, receiver ); - if ( asset(0) < unstake_net_quantity + unstake_cpu_quantity ) { - decrease_voting_power( from, unstake_net_quantity + unstake_cpu_quantity ); - } + const auto& fromv = _voters.get( from ); + if( fromv.producers.size() || fromv.proxy ) { + voteproducer( from, fromv.proxy, fromv.producers ); + } } // undelegatebw @@ -261,4 +393,5 @@ namespace eosiosystem { refunds_tbl.erase( req ); } + } //namespace eosiosystem diff --git a/contracts/eosio.system/eosio.system.abi b/contracts/eosio.system/eosio.system.abi index 759eaddef45..234a9776f65 100644 --- a/contracts/eosio.system/eosio.system.abi +++ b/contracts/eosio.system/eosio.system.abi @@ -1,40 +1,27 @@ { "types": [], "structs": [{ - "name": "nonce", - "base": "", - "fields": [ - {"name":"value", "type":"string"} - ] - },{ - "name": "transfer", + "name": "buyrambytes", "base": "", "fields": [ - {"name":"from", "type":"account_name"}, - {"name":"to", "type":"account_name"}, - {"name":"quantity", "type":"asset"}, - {"name":"memo", "type":"string"} + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"bytes", "type":"uint32"} ] },{ - "name": "issue", - "base": "", - "fields": [ - {"name":"to", "type":"account_name"}, - {"name":"quantity", "type":"asset"} - ] - },{ - "name": "account", + "name": "sellram", "base": "", "fields": [ - {"name":"currency", "type":"uint64"}, - {"name":"balance", "type":"uint64"} + {"name":"account", "type":"account_name"}, + {"name":"bytes", "type":"uint64"} ] },{ - "name": "currency_stats", + "name": "buyram", "base": "", "fields": [ - {"name":"currency", "type":"uint64"}, - {"name":"supply", "type":"uint64"} + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"quant", "type":"asset"} ] },{ "name": "delegatebw", @@ -44,7 +31,7 @@ {"name":"receiver", "type":"account_name"}, {"name":"stake_net_quantity", "type":"asset"}, {"name":"stake_cpu_quantity", "type":"asset"}, - {"name":"stake_storage_quantity", "type":"asset"} + {"name":"transfer", "type":"bool"} ] },{ "name": "undelegatebw", @@ -53,8 +40,7 @@ {"name":"from", "type":"account_name"}, {"name":"receiver", "type":"account_name"}, {"name":"unstake_net_quantity", "type":"asset"}, - {"name":"unstake_cpu_quantity", "type":"asset"}, - {"name":"unstake_storage_bytes", "type":"uint64"} + {"name":"unstake_cpu_quantity", "type":"asset"} ] },{ "name": "refund", @@ -70,8 +56,16 @@ {"name":"to", "type":"account_name"}, {"name":"net_weight", "type":"uint64"}, {"name":"cpu_weight", "type":"uint64"}, - {"name":"storage_stake", "type":"uint64"}, - {"name":"storage_bytes", "type":"uint64"} + {"name":"ram_bytes", "type":"uint64"} + ] + },{ + "name": "user_resources", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"}, + {"name":"ram_bytes", "type":"uint64"} ] },{ "name": "total_resources", @@ -80,8 +74,7 @@ {"name":"owner", "type":"account_name"}, {"name":"net_weight", "type":"asset"}, {"name":"cpu_weight", "type":"asset"}, - {"name":"storage_stake", "type":"asset"}, - {"name":"storage_bytes", "type":"uint64"} + {"name":"ram_bytes", "type":"uint64"} ] },{ "name": "refund_request", @@ -95,62 +88,71 @@ "name": "blockchain_parameters", "base": "", "fields": [ + {"name":"max_block_net_usage", "type": "uint32"}, + {"name":"target_block_net_usage_pct", "type": "uint32"}, + {"name":"max_transaction_net_usage", "type":"uint32"}, {"name":"base_per_transaction_net_usage", "type":"uint32"}, + {"name":"net_usage_leeway", "type":"uint32"}, + {"name":"context_free_discount_net_usage_num", "type":"uint32"}, + {"name":"context_free_discount_net_usage_den", "type":"uint32"}, + {"name":"max_block_cpu_usage", "type": "uint64"}, + {"name":"target_block_cpu_usage_pct", "type": "uint32"}, + {"name":"max_transaction_cpu_usage", "type":"uint32"}, {"name":"base_per_transaction_cpu_usage", "type":"uint32"}, {"name":"base_per_action_cpu_usage", "type":"uint32"}, {"name":"base_setcode_cpu_usage", "type":"uint32"}, {"name":"per_signature_cpu_usage", "type":"uint32"}, - {"name":"per_lock_net_usage", "type":"uint32"}, - {"name":"context_free_discount_cpu_usage_num", "type":"uint64"}, - {"name":"context_free_discount_cpu_usage_den", "type":"uint64"}, - {"name":"max_transaction_cpu_usage", "type":"uint32"}, - {"name":"max_transaction_net_usage", "type":"uint32"}, - {"name":"max_block_cpu_usage", "type": "uint64"}, - {"name":"target_block_cpu_usage_pct", "type": "uint32"}, - {"name":"max_block_net_usage", "type": "uint64"}, - {"name":"target_block_net_usage_pct", "type": "uint32"}, + {"name":"cpu_usage_leeway", "type":"uint32"}, + {"name":"context_free_discount_cpu_usage_num", "type":"uint32"}, + {"name":"context_free_discount_cpu_usage_den", "type":"uint32"}, {"name":"max_transaction_lifetime", "type":"uint32"}, - {"name":"max_transaction_exec_time", "type":"uint32"}, - {"name":"max_authority_depth", "type":"uint16"}, - {"name":"max_inline_depth", "type":"uint16"}, + {"name":"deferred_trx_expiration_window", "type":"uint32"}, + {"name":"max_transaction_delay", "type":"uint32"}, {"name":"max_inline_action_size", "type":"uint32"}, - {"name":"max_generated_transaction_count", "type":"uint32"}, - {"name":"max_transaction_delay", "type":"uint32"} + {"name":"max_inline_action_depth", "type":"uint16"}, + {"name":"max_authority_depth", "type":"uint16"}, + {"name":"max_generated_transaction_count", "type":"uint32"} ] },{ "name": "eosio_parameters", "base": "blockchain_parameters", "fields": [ - {"name":"max_storage_size", "type":"uint64"}, - {"name":"percent_of_max_inflation_rate", "type":"uint32"}, - {"name":"storage_reserve_ratio", "type":"uint32"} + {"name":"max_ram_size", "type":"uint64"} ] },{ "name": "eosio_global_state", "base": "eosio_parameters", "fields": [ - {"name":"total_storage_bytes_reserved", "type":"uint64"}, - {"name":"total_storage_stake", "type":"uint64"}, - {"name":"payment_per_block", "type":"uint64"} + {"name":"total_ram_bytes_reserved", "type":"uint64"}, + {"name":"total_ram_stake", "type":"asset"}, + {"name":"last_producer_schedule_update", "type":"time"}, + {"name":"last_pervote_bucket_fill", "type":"uint64"}, + {"name":"pervote_bucket", "type":"asset"}, + {"name":"savings", "type":"asset"}, + {"name":"last_producer_schedule_id", "type":"checksum160"}, + {"name":"total_activatied_stake", "type":"int64"} ] },{ "name": "producer_info", "base": "", "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"total_votes", "type":"uint128"}, - {"name":"prefs", "type":"eosio_parameters"}, - {"name":"packed_key", "type":"uint8[]"}, - {"name":"per_block_payments", "type":"uint64"}, - {"name":"last_claim_time", "type":"uint32"} + {"name":"owner", "type":"account_name"}, + {"name":"total_votes", "type":"float64"}, + {"name":"producer_key", "type":"public_key"}, + {"name":"url", "type":"string"}, + {"name":"produced_blocks", "type":"uint32"}, + {"name":"last_claim_time", "type":"uint64"}, + {"name":"location", "type":"uint16"}, + {"name":"time_became_active", "type":"uint32"}, + {"name":"last_produced_block_time", "type":"uint32"} ] },{ "name": "regproducer", "base": "", "fields": [ {"name":"producer", "type":"account_name"}, - {"name":"producer_key", "type":"bytes"}, - {"name":"prefs", "type":"eosio_parameters"} + {"name":"producer_key", "type":"public_key"}, + {"name":"url", "type":"string"} ] },{ "name": "unregprod", @@ -159,16 +161,17 @@ {"name":"producer", "type":"account_name"} ] },{ - "name": "regproxy", + "name": "setram", "base": "", "fields": [ - {"name":"proxy", "type":"account_name"} + {"name":"max_ram_size", "type":"uint64"} ] },{ - "name": "unregproxy", + "name": "regproxy", "base": "", "fields": [ - {"name":"proxy", "type":"account_name"} + {"name":"proxy", "type":"account_name"}, + {"name":"isproxy", "type":"bool"} ] },{ "name": "voteproducer", @@ -182,17 +185,16 @@ "name": "voter_info", "base": "", "fields": [ - {"name":"owner", "type":"account_name"}, - {"name":"proxy", "type":"account_name"}, - {"name":"last_update", "type":"uint32"}, - {"name":"is_proxy", "type":"uint32"}, - {"name":"staked", "type":"asset"}, - {"name":"unstaking", "type":"asset"}, - {"name":"unstake_per_week", "type":"asset"}, - {"name":"proxied_votes", "type":"uint128"}, - {"name":"producers", "type":"account_name[]"}, - {"name":"deferred_trx_id", "type":"uint32"}, - {"name":"last_unstake", "type":"uint32"} + {"name":"owner", "type":"account_name"}, + {"name":"proxy", "type":"account_name"}, + {"name":"producers", "type":"account_name[]"}, + {"name":"staked", "type":"int64"}, + {"name":"last_vote_weight", "type":"float64"}, + {"name":"proxied_vote_weight", "type":"float64"}, + {"name":"is_proxy", "type":"bool"}, + {"name":"deferred_trx_id", "type":"uint32"}, + {"name":"last_unstake_time", "type":"time"}, + {"name":"unstaking", "type":"asset"} ] },{ "name": "claimrewards", @@ -202,13 +204,18 @@ ] } ], - "actions": [{ - "name": "transfer", - "type": "transfer", + "actions": [ + { + "name": "buyrambytes", + "type": "buyrambytes", "ricardian_contract": "" },{ - "name": "issue", - "type": "issue", + "name": "buyram", + "type": "buyram", + "ricardian_contract": "" + },{ + "name": "sellram", + "type": "sellram", "ricardian_contract": "" },{ "name": "delegatebw", @@ -226,6 +233,10 @@ "name": "regproducer", "type": "regproducer", "ricardian_contract": "" + },{ + "name": "setram", + "type": "setram", + "ricardian_contract": "" },{ "name": "unregprod", "type": "unregprod", @@ -234,10 +245,6 @@ "name": "regproxy", "type": "regproxy", "ricardian_contract": "" - },{ - "name": "unregproxy", - "type": "unregproxy", - "ricardian_contract": "" },{ "name": "voteproducer", "type": "voteproducer", @@ -246,18 +253,32 @@ "name": "claimrewards", "type": "claimrewards", "ricardian_contract": "" - },{ - "name": "nonce", - "type": "nonce", - "ricardian_contract": "" } ], "tables": [{ - "name": "producerinfo", + "name": "producers", "type": "producer_info", "index_type": "i64", "key_names" : ["owner"], "key_types" : ["uint64"] + },{ + "name": "global", + "type": "eosio_global_state", + "index_type": "i64", + "key_names" : [], + "key_types" : [] + },{ + "name": "voters", + "type": "voter_info", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["account_name"] + },{ + "name": "userres", + "type": "user_resources", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] },{ "name": "totalband", "type": "total_resources", diff --git a/contracts/eosio.system/eosio.system.cpp b/contracts/eosio.system/eosio.system.cpp index 6dfcd8699a9..efaaccc28bb 100644 --- a/contracts/eosio.system/eosio.system.cpp +++ b/contracts/eosio.system/eosio.system.cpp @@ -4,18 +4,88 @@ #include "delegate_bandwidth.cpp" #include "producer_pay.cpp" #include "voting.cpp" +#include "exchange_state.cpp" + + +namespace eosiosystem { + + system_contract::system_contract( account_name s ) + :native(s), + _voters(_self,_self), + _producers(_self,_self), + _global(_self,_self), + _rammarket(_self,_self) + { + //print( "construct system\n" ); + _gstate = _global.exists() ? _global.get() : get_default_parameters(); + + auto itr = _rammarket.find(S(4,RAMEOS)); + + if( itr == _rammarket.end() ) { + auto system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; + if( system_token_supply > 0 ) { + itr = _rammarket.emplace( _self, [&]( auto& m ) { + m.supply.amount = 100000000000000ll; + m.supply.symbol = S(4,RAMEOS); + m.base.balance.amount = int64_t(_gstate.free_ram()); + m.base.balance.symbol = S(0,RAM); + m.quote.balance.amount = system_token_supply / 1000; + m.quote.balance.symbol = S(4,EOS); + }); + } + } else { + //print( "ram market already created" ); + } + } + + eosio_global_state system_contract::get_default_parameters() { + eosio_global_state dp; + get_blockchain_parameters(dp); + return dp; + } + + + system_contract::~system_contract() { + //print( "destruct system\n" ); + _global.set( _gstate, _self ); + //eosio_exit(0); + } + + void system_contract::setram( uint64_t max_ram_size ) { + require_auth( _self ); + + eosio_assert( max_ram_size < 1024ll*1024*1024*1024*1024, "ram size is unrealistic" ); + eosio_assert( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" ); + + auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size); + auto itr = _rammarket.find(S(4,RAMEOS)); + + /** + * Increase or decrease the amount of ram for sale based upon the change in max + * ram size. + */ + _rammarket.modify( itr, 0, [&]( auto& m ) { + m.base.balance.amount += delta; + }); + + _gstate.max_ram_size = max_ram_size; + _global.set( _gstate, _self ); + } + +} /// eosio.system + EOSIO_ABI( eosiosystem::system_contract, - // delegate_bandwith.cpp - (delegatebw)(undelegatebw)(refund) - (regproxy) - // voting.cpp - (unregproxy)(regproducer)(unregprod)(voteproducer)(onblock) - // producer_pay.cpp - (claimrewards) - // native.hpp - //XXX - (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(postrecovery)(passrecovery)(vetorecovery)(onerror)(canceldelay) - // defined in eosio.system.hpp - (nonce) + (setram) + // delegate_bandwith.cpp + (delegatebw)(undelegatebw)(refund) + (buyram)(buyrambytes)(sellram) + // voting.cpp + (regproxy)(regproducer)(unregprod)(voteproducer) + // producer_pay.cpp + (claimrewards) + // native.hpp + //XXX + (onblock) + (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(postrecovery)(passrecovery)(vetorecovery)(onerror)(canceldelay) ) diff --git a/contracts/eosio.system/eosio.system.hpp b/contracts/eosio.system/eosio.system.hpp index d62f15d4d3f..41a10d792e7 100644 --- a/contracts/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/eosio.system.hpp @@ -4,13 +4,11 @@ */ #pragma once -#include "native.hpp" - +#include #include -#include -#include #include #include +#include #include @@ -20,132 +18,198 @@ namespace eosiosystem { using eosio::indexed_by; using eosio::const_mem_fun; - struct block_header { - checksum256 previous; - time timestamp; - checksum256 transaction_mroot; - checksum256 action_mroot; - checksum256 block_mroot; - account_name producer; - uint32_t schedule_version; - eosio::optional new_producers; - - // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE(block_header, (previous)(timestamp)(transaction_mroot)(action_mroot)(block_mroot) - (producer)(schedule_version)(new_producers)) - }; - struct eosio_parameters : eosio::blockchain_parameters { - uint64_t max_storage_size = 10 * 1024 * 1024; - uint32_t percent_of_max_inflation_rate = 0; - uint32_t storage_reserve_ratio = 1000; // ratio * 1000 + uint64_t max_ram_size = 64ll*1024 * 1024 * 1024; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE_DERIVED( eosio_parameters, eosio::blockchain_parameters, (max_storage_size)(percent_of_max_inflation_rate)(storage_reserve_ratio) ) + EOSLIB_SERIALIZE_DERIVED( eosio_parameters, eosio::blockchain_parameters, (max_ram_size) ) }; struct eosio_global_state : eosio_parameters { - uint64_t total_storage_bytes_reserved = 0; - eosio::asset total_storage_stake; - eosio::asset payment_per_block; - eosio::asset payment_to_eos_bucket; - time first_block_time_in_cycle = 0; - uint32_t blocks_per_cycle = 0; - time last_bucket_fill_time = 0; - eosio::asset eos_bucket; + uint64_t free_ram()const { return max_ram_size - total_ram_bytes_reserved; } + + uint64_t total_ram_bytes_reserved = 0; + eosio::asset total_ram_stake; + + block_timestamp last_producer_schedule_update = 0; + uint64_t last_pervote_bucket_fill = 0; + eosio::asset pervote_bucket; + eosio::asset savings; + checksum160 last_producer_schedule_id; + + int64_t total_activated_stake = 0; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE_DERIVED( eosio_global_state, eosio_parameters, (total_storage_bytes_reserved)(total_storage_stake) - (payment_per_block)(payment_to_eos_bucket)(first_block_time_in_cycle)(blocks_per_cycle) - (last_bucket_fill_time)(eos_bucket) ) + EOSLIB_SERIALIZE_DERIVED( eosio_global_state, eosio_parameters, (total_ram_bytes_reserved)(total_ram_stake) + (last_producer_schedule_update) + (last_pervote_bucket_fill) + (pervote_bucket)(savings)(last_producer_schedule_id)(total_activated_stake) ) }; struct producer_info { - account_name owner; - uint128_t total_votes = 0; - eosio_parameters prefs; - eosio::bytes packed_key; /// a packed public key object - eosio::asset per_block_payments; - time last_rewards_claim = 0; - time time_became_active = 0; - time last_produced_block_time = 0; - - uint64_t primary_key()const { return owner; } - uint128_t by_votes()const { return total_votes; } - bool active() const { return 0 < packed_key.size(); } + account_name owner; + double total_votes = 0; + eosio::public_key producer_key; /// a packed public key object + std::string url; + uint32_t produced_blocks; + uint64_t last_claim_time = 0; + uint16_t location = 0; + block_timestamp time_became_active = 0; + block_timestamp last_produced_block_time = 0; + + uint64_t primary_key()const { return owner; } + double by_votes()const { return -total_votes; } + bool active() const { return producer_key != public_key(); } // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( producer_info, (owner)(total_votes)(prefs)(packed_key) - (per_block_payments)(last_rewards_claim) + EOSLIB_SERIALIZE( producer_info, (owner)(total_votes)(producer_key)(url) + (produced_blocks)(last_claim_time)(location) (time_became_active)(last_produced_block_time) ) }; - typedef eosio::multi_index< N(producerinfo), producer_info, - indexed_by > + struct voter_info { + account_name owner = 0; /// the voter + account_name proxy = 0; /// the proxy set by the voter, if any + std::vector producers; /// the producers approved by this voter if no proxy set + int64_t staked = 0; + + /** + * Every time a vote is cast we must first "undo" the last vote weight, before casting the + * new vote weight. Vote weight is calculated as: + * + * stated.amount * 2 ^ ( weeks_since_launch/weeks_per_year) + */ + double last_vote_weight = 0; /// the vote weight cast the last time the vote was updated + + /** + * Total vote weight delegated to this voter. + */ + double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy + bool is_proxy = 0; /// whether the voter is a proxy for others + + + uint32_t deferred_trx_id = 0; /// the ID of the 3-day delay deferred transaction + time last_unstake_time = 0; /// the time when the deferred_trx_id was sent + eosio::asset unstaking; /// the total unstaking (pending 3 day delay) + + uint64_t primary_key()const { return owner; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(deferred_trx_id)(last_unstake_time)(unstaking) ) + }; + + typedef eosio::multi_index< N(voters), voter_info> voters_table; + + + typedef eosio::multi_index< N(producers), producer_info, + indexed_by > > producers_table; typedef eosio::singleton global_state_singleton; - static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation + // static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation static constexpr uint32_t seconds_per_day = 24 * 3600; static constexpr uint64_t system_token_symbol = S(4,EOS); - class system_contract : public native, private eosio::contract { - public: + class system_contract : public native { + private: + voters_table _voters; + producers_table _producers; + global_state_singleton _global; - using eosio::contract::contract; + eosio_global_state _gstate; + rammarket _rammarket; + + public: + system_contract( account_name s ); + ~system_contract(); // Actions: + void onblock( uint32_t timestamp_slot, account_name producer ); + // const block_header& header ); /// only parse first 3 fields of block header // functions defined in delegate_bandwidth.cpp - void delegatebw( const account_name from, const account_name receiver, - const asset stake_net_quantity, const asset stake_cpu_quantity, - const asset stake_storage_quantity ); - void undelegatebw( const account_name from, const account_name receiver, - const asset unstake_net_quantity, const asset unstake_cpu_quantity, - const uint64_t unstake_storage_bytes ); - - void refund( const account_name owner ); + /** + * Stakes SYS from the balance of 'from' for the benfit of 'receiver'. + * If transfer == true, then 'receiver' can unstake to their account + * Else 'from' can unstake at any time. + */ + void delegatebw( account_name from, account_name receiver, + asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); + + + /** + * Decreases the total tokens delegated by from to receiver and/or + * frees the memory associated with the delegation if there is nothing + * left to delegate. + * + * This will cause an immediate reduction in net/cpu bandwidth of the + * receiver. + * + * A transaction is scheduled to send the tokens back to 'from' after + * the staking period has passed. If existing transaction is scheduled, it + * will be canceled and a new transaction issued that has the combined + * undelegated amount. + * + * The 'from' account loses voting power as a result of this call and + * all producer tallies are updated. + */ + void undelegatebw( account_name from, account_name receiver, + asset unstake_net_quantity, asset unstake_cpu_quantity ); + + + /** + * Increases receiver's ram quota based upon current price and quantity of + * tokens provided. An inline transfer from receiver to system contract of + * tokens will be executed. + */ + void buyram( account_name buyer, account_name receiver, asset tokens ); + void buyrambytes( account_name buyer, account_name receiver, uint32_t bytes ); + + /** + * Reduces quota my bytes and then performs an inline transfer of tokens + * to receiver based upon the average purchase price of the original quota. + */ + void sellram( account_name receiver, uint64_t bytes ); + + /** + * This action is called after the delegation-period to claim all pending + * unstaked tokens belonging to owner + */ + void refund( account_name owner ); // functions defined in voting.cpp - void regproducer( const account_name producer, const bytes& producer_key, const eosio_parameters& prefs ); + void regproducer( const account_name producer, const public_key& producer_key, const std::string& url ); void unregprod( const account_name producer ); - eosio::asset payment_per_block(uint32_t percent_of_max_inflation_rate); - - void update_elected_producers(time cycle_time); + void setram( uint64_t max_ram_size ); void voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ); - void regproxy( const account_name proxy ); - - void unregproxy( const account_name proxy ); - - void nonce( const std::string& /*value*/ ) {} + void regproxy( const account_name proxy, bool isproxy ); // functions defined in producer_pay.cpp - - void onblock( const block_header& header ); - void claimrewards( const account_name& owner ); private: + eosio::asset payment_per_block( double rate, const eosio::asset& token_supply, uint32_t num_blocks ); + + eosio::asset payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& pervote_bucket ); + + eosio::asset supply_growth( double rate, const eosio::asset& token_supply, time seconds ); + + void update_elected_producers( block_timestamp timestamp ); + // Implementation details: //defined in voting.hpp static eosio_global_state get_default_parameters(); // defined in voting.cpp - void increase_voting_power( account_name acnt, const eosio::asset& amount ); - - void decrease_voting_power( account_name acnt, const eosio::asset& amount ); - - // defined in producer_pay.cpp - bool update_cycle( time block_time ); - + void propagate_weight_change( const voter_info& voter ); }; } /// eosiosystem diff --git a/contracts/eosio.system/exchange_state.cpp b/contracts/eosio.system/exchange_state.cpp new file mode 100644 index 00000000000..b621bdef902 --- /dev/null +++ b/contracts/eosio.system/exchange_state.cpp @@ -0,0 +1,85 @@ +#include + +namespace eosiosystem { + asset exchange_state::convert_to_exchange( connector& c, asset in ) { + + real_type R(supply.amount); + real_type C(c.balance.amount+in.amount); + real_type F(c.weight/1000.0); + real_type T(in.amount); + real_type ONE(1.0); + + real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); + //print( "E: ", E, "\n"); + int64_t issued = int64_t(E); + + supply.amount += issued; + c.balance.amount += in.amount; + + return asset( issued, supply.symbol ); + } + + asset exchange_state::convert_from_exchange( connector& c, asset in ) { + eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" ); + + real_type R(supply.amount - in.amount); + real_type C(c.balance.amount); + real_type F(1000.0/c.weight); + real_type E(in.amount); + real_type ONE(1.0); + + + // potentially more accurate: + // The functions std::expm1 and std::log1p are useful for financial calculations, for example, + // when calculating small daily interest rates: (1+x)n + // -1 can be expressed as std::expm1(n * std::log1p(x)). + // real_type T = C * std::expm1( F * std::log1p(E/R) ); + + real_type T = C * (std::pow( ONE + E/R, F) - ONE); + //print( "T: ", T, "\n"); + int64_t out = int64_t(T); + + supply.amount -= in.amount; + c.balance.amount -= out; + + return asset( out, c.balance.symbol ); + } + + asset exchange_state::convert( asset from, symbol_type to ) { + auto sell_symbol = from.symbol; + auto ex_symbol = supply.symbol; + auto base_symbol = base.balance.symbol; + auto quote_symbol = quote.balance.symbol; + + //print( "From: ", from, " TO ", asset( 0,to), "\n" ); + //print( "base: ", base_symbol, "\n" ); + //print( "quote: ", quote_symbol, "\n" ); + //print( "ex: ", supply.symbol, "\n" ); + + if( sell_symbol != ex_symbol ) { + if( sell_symbol == base_symbol ) { + from = convert_to_exchange( base, from ); + } else if( sell_symbol == quote_symbol ) { + from = convert_to_exchange( quote, from ); + } else { + eosio_assert( false, "invalid sell" ); + } + } else { + if( to == base_symbol ) { + from = convert_from_exchange( base, from ); + } else if( to == quote_symbol ) { + from = convert_from_exchange( quote, from ); + } else { + eosio_assert( false, "invalid conversion" ); + } + } + + if( to != from.symbol ) + return convert( from, to ); + + return from; + } + + + +} /// namespace eosiosystem diff --git a/contracts/eosio.system/exchange_state.hpp b/contracts/eosio.system/exchange_state.hpp new file mode 100644 index 00000000000..3705a9b8b98 --- /dev/null +++ b/contracts/eosio.system/exchange_state.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace eosiosystem { + using eosio::asset; + using eosio::symbol_type; + + typedef double real_type; + + /** + * Uses Bancor math to create a 50/50 relay between two asset types. The state of the + * bancor exchange is entirely contained within this struct. There are no external + * side effects associated with using this API. + */ + struct exchange_state { + asset supply; + + struct connector { + asset balance; + double weight = .5; + + EOSLIB_SERIALIZE( connector, (balance)(weight) ) + }; + + connector base; + connector quote; + + uint64_t primary_key()const { return supply.symbol; } + + asset convert_to_exchange( connector& c, asset in ); + asset convert_from_exchange( connector& c, asset in ); + asset convert( asset from, symbol_type to ); + + EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) ) + }; + + typedef eosio::multi_index rammarket; + +} /// namespace eosiosystem diff --git a/contracts/eosio.system/native.hpp b/contracts/eosio.system/native.hpp index fd7bfe66d92..18c89cdc4a2 100644 --- a/contracts/eosio.system/native.hpp +++ b/contracts/eosio.system/native.hpp @@ -7,6 +7,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace eosiosystem { using eosio::permission_level; @@ -32,53 +37,86 @@ namespace eosiosystem { struct authority { uint32_t threshold; + uint32_t delay_sec; std::vector keys; std::vector accounts; // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts) ) + EOSLIB_SERIALIZE( authority, (threshold)(delay_sec)(keys)(accounts) ) }; + struct block_header { + uint32_t timestamp; + account_name producer; + uint16_t confirmed = 0; + block_id_type previous; + checksum256 transaction_mroot; + checksum256 action_mroot; + uint32_t schedule_version = 0; + eosio::optional new_producers; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE(block_header, (timestamp)(producer)(confirmed)(previous)(transaction_mroot)(action_mroot) + (schedule_version)(new_producers)) + }; + + /* - * Empty handlers for native messages. * Method parameters commented out to prevent generation of code that parses input data. */ - class native { + class native : public eosio::contract { public: - void newaccount( /*account_name creator, - account_name name, - const authority& owner, - const authority& active, - const authority& recovery*/ ) {} + using eosio::contract::contract; + + /** + * Called after a new account is created. This code enforces resource-limits rules + * for new accounts as well as new account naming conventions. + * + * 1. accounts cannot contain '.' symbols which forces all acccounts to be 12 + * characters long without '.' until a future account auction process is implemented + * which prevents name squatting. + * + * 2. new accounts must stake a minimal number of tokens (as set in system parameters) + * therefore, this method will execute an inline buyram from receiver for newacnt in + * an amount equal to the current new account creation fee. + */ + void newaccount( account_name creator, + account_name newact + /* no need to parse authorites + const authority& owner, + const authority& active, + const authority& recovery*/ ); + + + void updateauth( /*account_name account, + permission_name permission, + permission_name parent, + const authority& data*/ ) {} - void updateauth( /*account_name account, - permission_name permission, - permission_name parent, - const authority& data*/ ) {} + void deleteauth( /*account_name account, permission_name permission*/ ) {} - void deleteauth( /*account_name account, permission_name permission*/ ) {} + void linkauth( /*account_name account, + account_name code, + action_name type, + permission_name requirement*/ ) {} - void linkauth( /*account_name account, - account_name code, - action_name type, - permission_name requirement*/ ) {} + void unlinkauth( /*account_name account, + account_name code, + action_name type*/ ) {} - void unlinkauth( /*account_name account, - account_name code, - action_name type*/ ) {} + void postrecovery( /*account_name account, + const authority& data, + const std::string& memo*/ ) {} - void postrecovery( /*account_name account, - const authority& data, - const std::string& memo*/ ) {} + void passrecovery( /*account_name account*/ ) {} - void passrecovery( /*account_name account*/ ) {} + void vetorecovery( /*account_name account*/ ) {} - void vetorecovery( /*account_name account*/ ) {} + void onerror( /*const bytes&*/ ) {} - void onerror( /*const bytes&*/ ) {} + void canceldelay( /*permission_level canceling_auth, transaction_id_type trx_id*/ ) {} - void canceldelay( /*permission_level canceling_auth, transaction_id_type trx_id*/ ) {} }; } diff --git a/contracts/eosio.system/producer_pay.cpp b/contracts/eosio.system/producer_pay.cpp index 6091fc118e2..9022ca034c1 100644 --- a/contracts/eosio.system/producer_pay.cpp +++ b/contracts/eosio.system/producer_pay.cpp @@ -4,111 +4,140 @@ namespace eosiosystem { -static const uint32_t num_of_payed_producers = 121; - -bool system_contract::update_cycle(time block_time) { - global_state_singleton gs( _self, _self ); - auto parameters = gs.exists() ? gs.get() : get_default_parameters(); - if (parameters.first_block_time_in_cycle == 0) { - // This is the first time onblock is called in the blockchain. - parameters.last_bucket_fill_time = block_time; - gs.set( parameters, _self ); - update_elected_producers( block_time ); - return true; + const int64_t min_daily_tokens = 100; + + const double continuous_rate = 0.04879; // 5% annual rate + const double perblock_rate = 0.0025; // 0.25% + const double standby_rate = 0.0075; // 0.75% + const uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year + const uint32_t seconds_per_year = 52*7*24*3600; + const uint32_t blocks_per_day = 2 * 24 * 3600; + const uint32_t blocks_per_hour = 2 * 3600; + const uint64_t useconds_per_day = 24 * 3600 * uint64_t(1000000); + + eosio::asset system_contract::payment_per_block( double rate, const eosio::asset& token_supply, uint32_t num_blocks ) { + const int64_t payment = static_cast( (rate * double(token_supply.amount) * double(num_blocks)) / double(blocks_per_year) ); + return eosio::asset( payment, token_supply.symbol ); } - static const uint32_t slots_per_cycle = parameters.blocks_per_cycle; - const uint32_t time_slots = block_time - parameters.first_block_time_in_cycle; - if (time_slots >= slots_per_cycle) { - time beginning_of_cycle = block_time - (time_slots % slots_per_cycle); - update_elected_producers(beginning_of_cycle); - return true; + eosio::asset system_contract::supply_growth( double rate, const eosio::asset& token_supply, time seconds ) { + const int64_t payment = static_cast( (rate * double(token_supply.amount) * double(seconds)) / double(seconds_per_year) ); + return eosio::asset( payment, token_supply.symbol ); } - return false; -} - -void system_contract::onblock(const block_header& header) { - // update parameters if it's a new cycle - update_cycle(header.timestamp); - - producers_table producers_tbl( _self, _self ); - account_name producer = header.producer; - - global_state_singleton gs( _self, _self ); - auto parameters = gs.exists() ? gs.get() : get_default_parameters(); - // const system_token_type block_payment = parameters.payment_per_block; - const asset block_payment = parameters.payment_per_block; - auto prod = producers_tbl.find(producer); - if ( prod != producers_tbl.end() ) { - producers_tbl.modify( prod, 0, [&](auto& p) { - p.per_block_payments += block_payment; - p.last_produced_block_time = header.timestamp; + + void system_contract::onblock( block_timestamp timestamp, account_name producer ) { + using namespace eosio; + + require_auth(N(eosio)); + + /** until activated stake crosses this threshold no new rewards are paid */ + if( _gstate.total_activated_stake < 150'000'000'0000 ) + return; + + if( _gstate.last_pervote_bucket_fill == 0 ) /// start the presses + _gstate.last_pervote_bucket_fill = current_time(); + + auto prod = _producers.find(producer); + if ( prod != _producers.end() ) { + _producers.modify( prod, 0, [&](auto& p ) { + p.produced_blocks++; + p.last_produced_block_time = timestamp; }); - } + } + + /// only update block producers once every minute, block_timestamp is in half seconds + if( timestamp - _gstate.last_producer_schedule_update > 120 ) { + update_elected_producers( timestamp ); + } - const uint32_t num_of_payments = header.timestamp - parameters.last_bucket_fill_time; - // const system_token_type to_eos_bucket = num_of_payments * parameters.payment_to_eos_bucket; - const asset to_eos_bucket = num_of_payments * parameters.payment_to_eos_bucket; - parameters.last_bucket_fill_time = header.timestamp; - parameters.eos_bucket += to_eos_bucket; - gs.set( parameters, _self ); -} - -void system_contract::claimrewards(const account_name& owner) { - require_auth(owner); - eosio_assert(current_sender() == account_name(), "claimrewards can not be part of a deferred transaction"); - producers_table producers_tbl( _self, _self ); - auto prod = producers_tbl.find(owner); - eosio_assert(prod != producers_tbl.end(), "account name is not in producer list"); - eosio_assert(prod->active(), "producer is not active"); // QUESTION: Why do we want to prevent inactive producers from claiming their earned rewards? - if( prod->last_rewards_claim > 0 ) { - eosio_assert(now() >= prod->last_rewards_claim + seconds_per_day, "already claimed rewards within a day"); } - // system_token_type rewards = prod->per_block_payments; - eosio::asset rewards = prod->per_block_payments; - auto idx = producers_tbl.template get_index(); - auto itr = --idx.end(); - - bool is_among_payed_producers = false; - uint128_t total_producer_votes = 0; - uint32_t n = 0; - while( n < num_of_payed_producers ) { - if( !is_among_payed_producers ) { - if( itr->owner == owner ) - is_among_payed_producers = true; + + eosio::asset system_contract::payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& pervote_bucket ) { + eosio::asset payment(0, S(4,EOS)); + const int64_t min_daily_amount = 100 * 10000; + if ( pervote_bucket.amount < min_daily_amount ) { + return payment; } - if( itr->active() ) { + + auto idx = _producers.template get_index(); + + double total_producer_votes = 0; + double running_payment_amount = 0; + bool to_be_payed = false; + for ( auto itr = idx.cbegin(); itr != idx.cend(); ++itr ) { + if ( !(itr->total_votes > 0) ) { + break; + } + if ( !itr->active() ) { + continue; + } + + if ( itr->owner == owner ) { + to_be_payed = true; + } + total_producer_votes += itr->total_votes; - ++n; + running_payment_amount = (itr->total_votes) * double(pervote_bucket.amount) / total_producer_votes; + if ( running_payment_amount < min_daily_amount ) { + if ( itr->owner == owner ) { + to_be_payed = false; + } + total_producer_votes -= itr->total_votes; + break; + } } - if( itr == idx.begin() ) { - break; + + if ( to_be_payed ) { + payment.amount = static_cast( (double(pervote_bucket.amount) * owners_votes) / total_producer_votes ); } - --itr; + + return payment; } - if (is_among_payed_producers && total_producer_votes > 0) { - global_state_singleton gs( _self, _self ); - if( gs.exists() ) { - auto parameters = gs.get(); - // auto share_of_eos_bucket = system_token_type( static_cast( (prod->total_votes * parameters.eos_bucket.quantity) / total_producer_votes ) ); // This will be improved in the future when total_votes becomes a double type. - auto share_of_eos_bucket = eosio::asset( static_cast( (prod->total_votes * parameters.eos_bucket.amount) / total_producer_votes ) ); - rewards += share_of_eos_bucket; - parameters.eos_bucket -= share_of_eos_bucket; - gs.set( parameters, _self ); - } - } + void system_contract::claimrewards( const account_name& owner ) { + using namespace eosio; - // eosio_assert( rewards > system_token_type(), "no rewards available to claim" ); - eosio_assert( rewards > asset(0, S(4,EOS)), "no rewards available to claim" ); + require_auth(owner); - producers_tbl.modify( prod, 0, [&](auto& p) { - p.last_rewards_claim = now(); - p.per_block_payments.amount = 0; - }); + auto prod = _producers.find( owner ); + eosio_assert( prod != _producers.end(), "account name is not in producer list" ); + eosio_assert( prod->active(), "producer does not have an active key" ); + if( prod->last_claim_time > 0 ) { + eosio_assert(current_time() >= prod->last_claim_time + useconds_per_day, "already claimed rewards within a day"); + } - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, - { N(eosio), owner, rewards, std::string("producer claiming rewards") } ); -} + const asset token_supply = token( N(eosio.token)).get_supply(symbol_type(system_token_symbol).name() ); + const uint32_t secs_since_last_fill = static_cast( (current_time() - _gstate.last_pervote_bucket_fill) / 1000000 ); + + const asset to_pervote_bucket = supply_growth( standby_rate, token_supply, secs_since_last_fill ); + const asset to_savings = supply_growth( continuous_rate - (perblock_rate + standby_rate), token_supply, secs_since_last_fill ); + const asset perblock_pay = payment_per_block( perblock_rate, token_supply, prod->produced_blocks ); + const asset issue_amount = to_pervote_bucket + to_savings + perblock_pay; + + const asset pervote_pay = payment_per_vote( owner, prod->total_votes, to_pervote_bucket + _gstate.pervote_bucket ); + + if ( perblock_pay.amount + pervote_pay.amount == 0 ) { + _producers.modify( prod, 0, [&](auto& p) { + p.last_claim_time = current_time(); + }); + return; + } + + INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, + {N(eosio), issue_amount, std::string("issue tokens for producer pay and savings")} ); + + _gstate.pervote_bucket += ( to_pervote_bucket - pervote_pay ); + _gstate.last_pervote_bucket_fill = current_time(); + _gstate.savings += to_savings; + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), owner, perblock_pay + pervote_pay, std::string("producer claiming rewards") } ); + + _producers.modify( prod, 0, [&](auto& p) { + p.last_claim_time = current_time(); + p.produced_blocks = 0; + }); + + } } //namespace eosiosystem diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index b7ecdfa54ba..99f43ecb3f4 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -5,6 +5,7 @@ #include "eosio.system.hpp" #include +#include #include #include #include @@ -15,7 +16,6 @@ #include #include -#include #include namespace eosiosystem { @@ -26,31 +26,6 @@ namespace eosiosystem { using eosio::singleton; using eosio::transaction; - - static constexpr uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year - static constexpr uint32_t blocks_per_producer = 12; - - struct voter_info { - account_name owner = 0; - account_name proxy = 0; - time last_update = 0; - uint32_t is_proxy = 0; - eosio::asset staked; - eosio::asset unstaking; - eosio::asset unstake_per_week; - uint128_t proxied_votes = 0; - std::vector producers; - uint32_t deferred_trx_id = 0; - time last_unstake_time = 0; //uint32 - - uint64_t primary_key()const { return owner; } - - // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(last_update)(is_proxy)(staked)(unstaking)(unstake_per_week)(proxied_votes)(producers)(deferred_trx_id)(last_unstake_time) ) - }; - - typedef eosio::multi_index< N(voters), voter_info> voters_table; - /** * This method will create a producer_config and producer_info object for 'producer' * @@ -59,310 +34,117 @@ namespace eosiosystem { * @pre authority of producer to register * */ - void system_contract::regproducer( const account_name producer, const bytes& packed_producer_key, const eosio_parameters& prefs ) { + void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url ) { //, const eosio_parameters& prefs ) { + eosio_assert( url.size() < 512, "url too long" ); //eosio::print("produce_key: ", producer_key.size(), ", sizeof(public_key): ", sizeof(public_key), "\n"); require_auth( producer ); - producers_table producers_tbl( _self, _self ); - auto prod = producers_tbl.find( producer ); - - //check that we can unpack producer key - public_key producer_key = eosio::unpack( packed_producer_key ); + auto prod = _producers.find( producer ); - if ( prod != producers_tbl.end() ) { - producers_tbl.modify( prod, producer, [&]( producer_info& info ){ - info.prefs = prefs; - info.packed_key = eosio::pack( producer_key ); - }); + if ( prod != _producers.end() ) { + if( producer_key != prod->producer_key ) { + _producers.modify( prod, producer, [&]( producer_info& info ){ + info.producer_key = producer_key; + info.url = url; + }); + } } else { - producers_tbl.emplace( producer, [&]( producer_info& info ){ + _producers.emplace( producer, [&]( producer_info& info ){ info.owner = producer; info.total_votes = 0; - info.prefs = prefs; - info.packed_key = eosio::pack( producer_key ); - }); + info.producer_key = producer_key; + info.url = url; + }); } } void system_contract::unregprod( const account_name producer ) { require_auth( producer ); - producers_table producers_tbl( _self, _self ); - auto prod = producers_tbl.find( producer ); - eosio_assert( prod != producers_tbl.end(), "producer not found" ); + const auto& prod = _producers.get( producer, "producer not found" ); - producers_tbl.modify( prod, 0, [&]( producer_info& info ){ - info.packed_key.clear(); - }); + _producers.modify( prod, 0, [&]( producer_info& info ){ + info.producer_key = eosio::public_key(); + }); } - void system_contract::increase_voting_power( account_name acnt, const eosio::asset& amount ) { - voters_table voters_tbl( _self, _self ); - auto voter = voters_tbl.find( acnt ); + void system_contract::update_elected_producers( block_timestamp block_time ) { + _gstate.last_producer_schedule_update = block_time; - eosio_assert( 0 <= amount.amount, "negative asset" ); + auto idx = _producers.get_index(); - if( voter == voters_tbl.end() ) { - voter = voters_tbl.emplace( acnt, [&]( voter_info& a ) { - a.owner = acnt; - a.last_update = now(); - a.staked = amount; - }); - } else { - voters_tbl.modify( voter, 0, [&]( auto& av ) { - av.last_update = now(); - av.staked += amount; - }); - } + std::vector< std::pair > top_producers; + top_producers.reserve(21); - const std::vector* producers = nullptr; - if ( voter->proxy ) { - auto proxy = voters_tbl.find( voter->proxy ); - eosio_assert( proxy != voters_tbl.end(), "selected proxy not found" ); //data corruption - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes += uint64_t(amount.amount); } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - } else { - producers = &voter->producers; - } + for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes; ++it ) { + if( !it->active() ) continue; - if ( producers ) { - producers_table producers_tbl( _self, _self ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes += uint64_t(amount.amount); + if ( it->time_became_active == 0 ) { + _producers.modify( *it, 0, [&](auto& p) { + p.time_became_active = block_time; + }); + } else if ( block_time > 2 * 21 * 12 + it->time_became_active && + block_time > it->last_produced_block_time + blocks_per_day ) { + _producers.modify( *it, 0, [&](auto& p) { + p.producer_key = public_key(); + p.time_became_active = 0; }); - } - } - } - - void system_contract::decrease_voting_power( account_name acnt, const eosio::asset& amount ) { - require_auth( acnt ); - voters_table voters_tbl( _self, _self ); - auto voter = voters_tbl.find( acnt ); - eosio_assert( voter != voters_tbl.end(), "stake not found" ); - - if ( 0 < amount.amount ) { - eosio_assert( amount <= voter->staked, "cannot unstake more than total stake amount" ); - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked -= amount; - a.last_update = now(); - }); - const std::vector* producers = nullptr; - if ( voter->proxy ) { - auto proxy = voters_tbl.find( voter->proxy ); - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes -= uint64_t(amount.amount); } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - } else { - producers = &voter->producers; + continue; } - if ( producers ) { - producers_table producers_tbl( _self, _self ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes -= uint64_t(amount.amount); - }); - } - } - } else { - if (voter->deferred_trx_id) { - //XXX cancel_deferred_transaction(voter->deferred_trx_id); - } - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked += a.unstaking; - a.unstaking.amount = 0; - a.unstake_per_week.amount = 0; - a.deferred_trx_id = 0; - a.last_update = now(); - }); + top_producers.emplace_back( std::pair({{it->owner, it->producer_key}, it->location})); } - } + - eosio_global_state system_contract::get_default_parameters() { - eosio_global_state dp; - get_blockchain_parameters(dp); - return dp; - } - eosio::asset system_contract::payment_per_block(uint32_t percent_of_max_inflation_rate) { - const eosio::asset token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()); - const double annual_rate = double(max_inflation_rate * percent_of_max_inflation_rate) / double(10000); - const double continuous_rate = std::log1p(annual_rate); - int64_t payment = static_cast((continuous_rate * double(token_supply.amount)) / double(blocks_per_year)); - return eosio::asset(payment, system_token_symbol); - } + /// sort by producer name + std::sort( top_producers.begin(), top_producers.end() ); - void system_contract::update_elected_producers(time cycle_time) { - producers_table producers_tbl( _self, _self ); - auto idx = producers_tbl.template get_index(); - - std::array base_per_transaction_net_usage; - std::array base_per_transaction_cpu_usage; - std::array base_per_action_cpu_usage; - std::array base_setcode_cpu_usage; - std::array per_signature_cpu_usage; - std::array per_lock_net_usage; - std::array context_free_discount_cpu_usage_num; - std::array context_free_discount_cpu_usage_den; - std::array max_transaction_cpu_usage; - std::array max_transaction_net_usage; - std::array max_block_cpu_usage; - std::array target_block_cpu_usage_pct; - std::array max_block_net_usage; - std::array target_block_net_usage_pct; - std::array max_transaction_lifetime; - std::array max_authority_depth; - std::array max_transaction_exec_time; - std::array max_inline_depth; - std::array max_inline_action_size; - std::array max_generated_transaction_count; - std::array max_transaction_delay; - std::array percent_of_max_inflation_rate; - std::array storage_reserve_ratio; - - eosio::producer_schedule schedule; - schedule.producers.reserve(21); - size_t n = 0; - for ( auto it = idx.crbegin(); it != idx.crend() && n < 21 && 0 < it->total_votes; ++it ) { - if ( it->active() ) { - schedule.producers.emplace_back(); - schedule.producers.back().producer_name = it->owner; - //eosio_assert( sizeof(schedule.producers.back().block_signing_key) == it->packed_key.size(), "size mismatch" ); - schedule.producers.back().block_signing_key = eosio::unpack( it->packed_key ); - //std::copy( it->packed_key.begin(), it->packed_key.end(), schedule.producers.back().block_signing_key.data.data() ); - - base_per_transaction_net_usage[n] = it->prefs.base_per_transaction_net_usage; - base_per_transaction_cpu_usage[n] = it->prefs.base_per_transaction_cpu_usage; - base_per_action_cpu_usage[n] = it->prefs.base_per_action_cpu_usage; - base_setcode_cpu_usage[n] = it->prefs.base_setcode_cpu_usage; - per_signature_cpu_usage[n] = it->prefs.per_signature_cpu_usage; - per_lock_net_usage[n] = it->prefs.per_lock_net_usage; - context_free_discount_cpu_usage_num[n] = it->prefs.context_free_discount_cpu_usage_num; - context_free_discount_cpu_usage_den[n] = it->prefs.context_free_discount_cpu_usage_den; - max_transaction_cpu_usage[n] = it->prefs.max_transaction_cpu_usage; - max_transaction_net_usage[n] = it->prefs.max_transaction_net_usage; - max_block_cpu_usage[n] = it->prefs.max_block_cpu_usage; - target_block_cpu_usage_pct[n] = it->prefs.target_block_cpu_usage_pct; - max_block_net_usage[n] = it->prefs.max_block_net_usage; - target_block_net_usage_pct[n] = it->prefs.target_block_net_usage_pct; - max_transaction_lifetime[n] = it->prefs.max_transaction_lifetime; - max_authority_depth[n] = it->prefs.max_authority_depth; - max_transaction_exec_time[n] = it->prefs.max_transaction_exec_time; - max_inline_depth[n] = it->prefs.max_inline_depth; - max_inline_action_size[n] = it->prefs.max_inline_action_size; - max_generated_transaction_count[n] = it->prefs.max_generated_transaction_count; - max_transaction_delay[n] = it->prefs.max_transaction_delay; - - storage_reserve_ratio[n] = it->prefs.storage_reserve_ratio; - percent_of_max_inflation_rate[n] = it->prefs.percent_of_max_inflation_rate; - ++n; - } - } - if ( n == 0 ) { //no active producers with votes > 0 - return; - } - if ( 1 < n ) { - std::sort( base_per_transaction_net_usage.begin(), base_per_transaction_net_usage.begin()+n ); - std::sort( base_per_transaction_cpu_usage.begin(), base_per_transaction_cpu_usage.begin()+n ); - std::sort( base_per_action_cpu_usage.begin(), base_per_action_cpu_usage.begin()+n ); - std::sort( base_setcode_cpu_usage.begin(), base_setcode_cpu_usage.begin()+n ); - std::sort( per_signature_cpu_usage.begin(), per_signature_cpu_usage.begin()+n ); - std::sort( per_lock_net_usage.begin(), per_lock_net_usage.begin()+n ); - std::sort( context_free_discount_cpu_usage_num.begin(), context_free_discount_cpu_usage_num.begin()+n ); - std::sort( context_free_discount_cpu_usage_den.begin(), context_free_discount_cpu_usage_den.begin()+n ); - std::sort( max_transaction_cpu_usage.begin(), max_transaction_cpu_usage.begin()+n ); - std::sort( max_transaction_net_usage.begin(), max_transaction_net_usage.begin()+n ); - std::sort( max_block_cpu_usage.begin(), max_block_cpu_usage.begin()+n ); - std::sort( target_block_cpu_usage_pct.begin(), target_block_cpu_usage_pct.begin()+n ); - std::sort( max_block_net_usage.begin(), max_block_net_usage.begin()+n ); - std::sort( target_block_net_usage_pct.begin(), target_block_net_usage_pct.begin()+n ); - std::sort( max_transaction_lifetime.begin(), max_transaction_lifetime.begin()+n ); - std::sort( max_transaction_exec_time.begin(), max_transaction_exec_time.begin()+n ); - std::sort( max_authority_depth.begin(), max_authority_depth.begin()+n ); - std::sort( max_inline_depth.begin(), max_inline_depth.begin()+n ); - std::sort( max_inline_action_size.begin(), max_inline_action_size.begin()+n ); - std::sort( max_generated_transaction_count.begin(), max_generated_transaction_count.begin()+n ); - std::sort( max_transaction_delay.begin(), max_transaction_delay.begin()+n ); - std::sort( storage_reserve_ratio.begin(), storage_reserve_ratio.begin()+n ); - std::sort( percent_of_max_inflation_rate.begin(), percent_of_max_inflation_rate.begin()+n ); - } + std::vector producers; - // should use producer_schedule_type from libraries/chain/include/eosio/chain/producer_schedule.hpp - bytes packed_schedule = pack(schedule); - set_active_producers( packed_schedule.data(), packed_schedule.size() ); - size_t median = n/2; - - global_state_singleton gs( _self, _self ); - auto parameters = gs.exists() ? gs.get() : get_default_parameters(); - - parameters.base_per_transaction_net_usage = base_per_transaction_net_usage[median]; - parameters.base_per_transaction_cpu_usage = base_per_transaction_cpu_usage[median]; - parameters.base_per_action_cpu_usage = base_per_action_cpu_usage[median]; - parameters.base_setcode_cpu_usage = base_setcode_cpu_usage[median]; - parameters.per_signature_cpu_usage = per_signature_cpu_usage[median]; - parameters.per_lock_net_usage = per_lock_net_usage[median]; - parameters.context_free_discount_cpu_usage_num = context_free_discount_cpu_usage_num[median]; - parameters.context_free_discount_cpu_usage_den = context_free_discount_cpu_usage_den[median]; - parameters.max_transaction_cpu_usage = max_transaction_cpu_usage[median]; - parameters.max_transaction_net_usage = max_transaction_net_usage[median]; - parameters.max_block_cpu_usage = max_block_cpu_usage[median]; - parameters.target_block_cpu_usage_pct = target_block_cpu_usage_pct[median]; - parameters.max_block_net_usage = max_block_net_usage[median]; - parameters.target_block_net_usage_pct = target_block_net_usage_pct[median]; - parameters.max_transaction_lifetime = max_transaction_lifetime[median]; - parameters.max_transaction_exec_time = max_transaction_exec_time[median]; - parameters.max_authority_depth = max_authority_depth[median]; - parameters.max_inline_depth = max_inline_depth[median]; - parameters.max_inline_action_size = max_inline_action_size[median]; - parameters.max_generated_transaction_count = max_generated_transaction_count[median]; - parameters.max_transaction_delay = max_transaction_delay[median]; - parameters.storage_reserve_ratio = storage_reserve_ratio[median]; - parameters.percent_of_max_inflation_rate = percent_of_max_inflation_rate[median]; - - // not voted on - parameters.first_block_time_in_cycle = cycle_time; - - // derived parameters - auto half_of_percentage = parameters.percent_of_max_inflation_rate / 2; - auto other_half_of_percentage = parameters.percent_of_max_inflation_rate - half_of_percentage; - parameters.payment_per_block = payment_per_block(half_of_percentage); - parameters.payment_to_eos_bucket = payment_per_block(other_half_of_percentage); - parameters.blocks_per_cycle = blocks_per_producer * schedule.producers.size(); - - if ( parameters.max_storage_size < parameters.total_storage_bytes_reserved ) { - parameters.max_storage_size = parameters.total_storage_bytes_reserved; - } + producers.reserve(top_producers.size()); + for( const auto& item : top_producers ) + producers.push_back(item.first); - auto issue_quantity = parameters.blocks_per_cycle * (parameters.payment_per_block + parameters.payment_to_eos_bucket); - INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, - {N(eosio), issue_quantity, std::string("producer pay")} ); + bytes packed_schedule = pack(producers); + checksum160 new_id; + sha1( packed_schedule.data(), packed_schedule.size(), &new_id ); - set_blockchain_parameters( parameters ); - gs.set( parameters, _self ); + if( new_id != _gstate.last_producer_schedule_id ) { + _gstate.last_producer_schedule_id = new_id; + set_active_producers( packed_schedule.data(), packed_schedule.size() ); + } + _gstate.last_producer_schedule_update = block_time; } + double stake2vote( int64_t staked ) { + double weight = int64_t(now() / (seconds_per_day * 7)) / double( 52 ); + return double(staked) * std::pow( 2, weight ); + } /** - * @pre producers must be sorted from lowest to highest + * @pre producers must be sorted from lowest to highest and must be registered and active * @pre if proxy is set then no producers can be voted for + * @pre if proxy is set then proxy account must exist and be registered as a proxy * @pre every listed producer or proxy must have been previously registered * @pre voter must authorize this action * @pre voter must have previously staked some EOS for voting + * @pre voter->staked must be up to date + * + * @post every producer previously voted for will have vote reduced by previous vote weight + * @post every producer newly voted for will have vote increased by new vote amount + * @post prior proxy will proxied_vote_weight decremented by previous vote weight + * @post new proxy will proxied_vote_weight incremented by new vote weight + * + * If voting for a proxy, the producer votes will not change until the proxy updates their own vote. */ - void system_contract::voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ) { - require_auth( voter ); + void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { + require_auth( voter_name ); //validate input if ( proxy ) { eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" ); + eosio_assert( voter_name != proxy, "cannot proxy to self" ); require_recipient( proxy ); } else { eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); @@ -371,139 +153,144 @@ namespace eosiosystem { } } - voters_table voters_tbl( _self, _self ); - auto voter_it = voters_tbl.find( voter ); + auto voter = _voters.find(voter_name); + eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object + eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + + /** + * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until + * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is + * their first vote and should consider their stake activated. + */ + if( voter->last_vote_weight <= 0.0 ) { + _gstate.total_activated_stake += voter->staked; + } - eosio_assert( 0 <= voter_it->staked.amount, "negative stake" ); - eosio_assert( voter_it != voters_tbl.end() && ( 0 < voter_it->staked.amount || ( voter_it->is_proxy && 0 < voter_it->proxied_votes ) ), "no stake to vote" ); - if ( voter_it->is_proxy ) { - eosio_assert( proxy == 0 , "account registered as a proxy is not allowed to use a proxy" ); + auto new_vote_weight = stake2vote( voter->staked ); + if( voter->is_proxy ) { + new_vote_weight += voter->proxied_vote_weight; } - //find old producers, update old proxy if needed - const std::vector* old_producers = nullptr; - if( voter_it->proxy ) { - if ( voter_it->proxy == proxy ) { - return; // nothing changed - } - auto old_proxy = voters_tbl.find( voter_it->proxy ); - eosio_assert( old_proxy != voters_tbl.end(), "old proxy not found" ); //data corruption - voters_tbl.modify( old_proxy, 0, [&](auto& a) { a.proxied_votes -= uint64_t(voter_it->staked.amount); } ); - if ( old_proxy->is_proxy ) { //if proxy stopped being a proxy, the votes were already taken back from producers by on( const unregister_proxy& ) - old_producers = &old_proxy->producers; + boost::container::flat_map > producer_deltas; + if ( voter->last_vote_weight != 0 ) { + if( voter->proxy ) { + auto old_proxy = _voters.find( voter->proxy ); + eosio_assert( old_proxy != _voters.end(), "old proxy not found" ); //data corruption + _voters.modify( old_proxy, 0, [&]( auto& vp ) { + vp.proxied_vote_weight -= voter->last_vote_weight; + }); + propagate_weight_change( *old_proxy ); + } else { + for( const auto& p : voter->producers ) { + auto& d = producer_deltas[p]; + d.first -= voter->last_vote_weight; + d.second = false; + } } - } else { - old_producers = &voter_it->producers; } - //find new producers, update new proxy if needed - const std::vector* new_producers = nullptr; - if ( proxy ) { - auto new_proxy = voters_tbl.find( proxy ); - eosio_assert( new_proxy != voters_tbl.end() && new_proxy->is_proxy, "proxy not found" ); - voters_tbl.modify( new_proxy, 0, [&](auto& a) { a.proxied_votes += uint64_t(voter_it->staked.amount); } ); - new_producers = &new_proxy->producers; + if( proxy ) { + auto new_proxy = _voters.find( proxy ); + eosio_assert( new_proxy != _voters.end() && new_proxy->is_proxy, "invalid proxy specified" ); + if ( new_vote_weight >= 0 ) { + _voters.modify( new_proxy, 0, [&]( auto& vp ) { + vp.proxied_vote_weight += new_vote_weight; + }); + propagate_weight_change( *new_proxy ); + } } else { - new_producers = &producers; - } - - producers_table producers_tbl( _self, _self ); - uint128_t votes = uint64_t(voter_it->staked.amount); - if ( voter_it->is_proxy ) { - votes += voter_it->proxied_votes; - } - - if ( old_producers ) { //old_producers == nullptr if proxy has stopped being a proxy and votes were taken back from the producers at that moment - //revoke votes only from no longer elected - std::vector revoked( old_producers->size() ); - auto end_it = std::set_difference( old_producers->begin(), old_producers->end(), new_producers->begin(), new_producers->end(), revoked.begin() ); - for ( auto it = revoked.begin(); it != end_it; ++it ) { - auto prod = producers_tbl.find( *it ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes -= votes; } ); + if( new_vote_weight >= 0 ) { + for( const auto& p : producers ) { + auto& d = producer_deltas[p]; + d.first += new_vote_weight; + d.second = true; + } } } - //update newly elected - std::vector elected( new_producers->size() ); - auto end_it = elected.begin(); - if( old_producers ) { - end_it = std::set_difference( new_producers->begin(), new_producers->end(), old_producers->begin(), old_producers->end(), elected.begin() ); - } else { - end_it = std::copy( new_producers->begin(), new_producers->end(), elected.begin() ); - } - for ( auto it = elected.begin(); it != end_it; ++it ) { - auto prod = producers_tbl.find( *it ); - eosio_assert( prod != producers_tbl.end(), "producer is not registered" ); - if ( proxy == 0 ) { //direct voting, in case of proxy voting update total_votes even for inactive producers - eosio_assert( prod->active(), "producer is not currently registered" ); + for( const auto& pd : producer_deltas ) { + auto pitr = _producers.find( pd.first ); + if( pitr != _producers.end() ) { + eosio_assert( pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" ); + _producers.modify( pitr, 0, [&]( auto& p ) { + print( "orig total_votes: ", p.total_votes, " delta: ", pd.second.first, "\n" ); + p.total_votes += pd.second.first; + print( "new total_votes: ", p.total_votes, "\n" ); + //eosio_assert( p.total_votes >= 0, "something bad happened" ); + }); + } else { + eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); } - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes += votes; } ); } - // save new values to the account itself - voters_tbl.modify( voter_it, 0, [&](voter_info& a) { - a.proxy = proxy; - a.last_update = now(); - a.producers = producers; - }); + _voters.modify( voter, 0, [&]( auto& av ) { + print( "last_vote_weight: ", av.last_vote_weight, "\n" ); + print( "new_vote_weight: ", new_vote_weight, "\n" ); + av.last_vote_weight = new_vote_weight; + av.producers = producers; + av.proxy = proxy; + print( " vote weight: ", av.last_vote_weight, "\n" ); + }); } - void system_contract::regproxy( const account_name proxy ) { + /** + * An account marked as a proxy can vote with the weight of other accounts which + * have selected it as a proxy. Other accounts must refresh their voteproducer to + * update the proxy's weight. + * + * @param isproxy - true if proxy wishes to vote on behalf of others, false otherwise + * @pre proxy must have something staked (existing row in voters table) + * @pre new state must be different than current state + */ + void system_contract::regproxy( const account_name proxy, bool isproxy ) { require_auth( proxy ); - voters_table voters_tbl( _self, _self ); - auto proxy_it = voters_tbl.find( proxy ); - if ( proxy_it != voters_tbl.end() ) { - eosio_assert( proxy_it->is_proxy == 0, "account is already a proxy" ); - eosio_assert( proxy_it->proxy == 0, "account that uses a proxy is not allowed to become a proxy" ); - voters_tbl.modify( proxy_it, 0, [&](voter_info& a) { - a.is_proxy = 1; - a.last_update = now(); - //a.proxied_votes may be > 0, if the proxy has been unregistered, so we had to keep the value + auto pitr = _voters.find(proxy); + if ( pitr != _voters.end() ) { + eosio_assert( isproxy != pitr->is_proxy, "action has no effect" ); + eosio_assert( !isproxy || !pitr->proxy, "account that uses a proxy is not allowed to become a proxy" ); + _voters.modify( pitr, 0, [&]( auto& p ) { + p.is_proxy = isproxy; + print( " vote weight: ", p.last_vote_weight, "\n" ); }); - if ( 0 < proxy_it->proxied_votes ) { - producers_table producers_tbl( _self, _self ); - for ( auto p : proxy_it->producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes += proxy_it->proxied_votes; }); - } - } + propagate_weight_change( *pitr ); } else { - voters_tbl.emplace( proxy, [&]( voter_info& a ) { - a.owner = proxy; - a.last_update = now(); - a.proxy = 0; - a.is_proxy = 1; - a.proxied_votes = 0; - a.staked.amount = 0; + _voters.emplace( proxy, [&]( auto& p ) { + p.owner = proxy; + p.is_proxy = isproxy; }); } } - void system_contract::unregproxy( const account_name proxy ) { - require_auth( proxy ); - - voters_table voters_tbl( _self, _self ); - auto proxy_it = voters_tbl.find( proxy ); - eosio_assert( proxy_it != voters_tbl.end(), "proxy not found" ); - eosio_assert( proxy_it->is_proxy == 1, "account is not a proxy" ); - - voters_tbl.modify( proxy_it, 0, [&](voter_info& a) { - a.is_proxy = 0; - a.last_update = now(); - //a.proxied_votes should be kept in order to be able to reenable this proxy in the future - }); + void system_contract::propagate_weight_change( const voter_info& voter ) { + eosio_assert( voter.proxy == 0 || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + double new_weight = stake2vote( voter.staked ); + if ( voter.is_proxy ) { + new_weight += voter.proxied_vote_weight; + } - if ( 0 < proxy_it->proxied_votes ) { - producers_table producers_tbl( _self, _self ); - for ( auto p : proxy_it->producers ) { - auto prod_it = producers_tbl.find( p ); - eosio_assert( prod_it != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod_it, 0, [&]( auto& pi ) { pi.total_votes -= proxy_it->proxied_votes; }); + if ( new_weight != voter.last_vote_weight ) { + if ( voter.proxy ) { + auto& proxy = _voters.get( voter.proxy, "proxy not found" ); //data corruption + _voters.modify( proxy, 0, [&]( auto& p ) { + p.proxied_vote_weight += new_weight - voter.last_vote_weight; + } + ); + propagate_weight_change( proxy ); + } else { + for ( auto acnt : voter.producers ) { + auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption + _producers.modify( pitr, 0, [&]( auto& p ) { + p.total_votes += new_weight - voter.last_vote_weight; + } + ); + } } } + _voters.modify( voter, 0, [&]( auto& v ) { + v.last_vote_weight = new_weight; + } + ); } -} +} /// namespace eosiosystem diff --git a/contracts/eosio.token/eosio.token.cpp b/contracts/eosio.token/eosio.token.cpp index eadaa149469..de1884391b9 100644 --- a/contracts/eosio.token/eosio.token.cpp +++ b/contracts/eosio.token/eosio.token.cpp @@ -38,14 +38,21 @@ void token::create( account_name issuer, void token::issue( account_name to, asset quantity, string memo ) { print( "issue" ); - auto sym = quantity.symbol.name(); - stats statstable( _self, sym ); - const auto& st = statstable.get( sym ); + auto sym = quantity.symbol; + eosio_assert( sym.is_valid(), "invalid symbol name" ); + + auto sym_name = sym.name(); + stats statstable( _self, sym_name ); + auto existing = statstable.find( sym_name ); + eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" ); + const auto& st = *existing; require_auth( st.issuer ); eosio_assert( quantity.is_valid(), "invalid quantity" ); eosio_assert( quantity.amount > 0, "must issue positive quantity" ); - eosio_assert( quantity <= st.max_supply - st.supply, "quantity exceeds available supply"); + + eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); + eosio_assert( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply"); statstable.modify( st, 0, [&]( auto& s ) { s.supply += quantity; @@ -53,8 +60,7 @@ void token::issue( account_name to, asset quantity, string memo ) add_balance( st.issuer, quantity, st, st.issuer ); - if( to != st.issuer ) - { + if( to != st.issuer ) { SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} ); } } @@ -64,8 +70,10 @@ void token::transfer( account_name from, asset quantity, string /*memo*/ ) { - print( "transfer" ); + print( "transfer from ", eosio::name{from}, " to ", eosio::name{to}, " ", quantity, "\n" ); + eosio_assert( from != to, "cannot transfer to self" ); require_auth( from ); + eosio_assert( is_account( to ), "to account does not exist"); auto sym = quantity.symbol.name(); stats statstable( _self, sym ); const auto& st = statstable.get( sym ); @@ -75,6 +83,8 @@ void token::transfer( account_name from, eosio_assert( quantity.is_valid(), "invalid quantity" ); eosio_assert( quantity.amount > 0, "must transfer positive quantity" ); + eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); + sub_balance( from, quantity, st ); add_balance( to, quantity, st, from ); @@ -98,6 +108,7 @@ void token::sub_balance( account_name owner, asset value, const currency_stats& from_acnts.modify( from, owner, [&]( auto& a ) { a.balance -= value; + print( eosio::name{owner}, " balance: ", a.balance, "\n" ); }); } @@ -109,11 +120,13 @@ void token::add_balance( account_name owner, asset value, const currency_stats& eosio_assert( !st.enforce_whitelist, "can only transfer to white listed accounts" ); to_acnts.emplace( ram_payer, [&]( auto& a ){ a.balance = value; + print( eosio::name{owner}, " balance: ", a.balance, "\n" ); }); } else { eosio_assert( !st.enforce_whitelist || to->whitelist, "receiver requires whitelist by issuer" ); to_acnts.modify( to, 0, [&]( auto& a ) { a.balance += value; + print( eosio::name{owner}, " balance: ", a.balance, "\n" ); }); } } diff --git a/contracts/eosiolib/account.h b/contracts/eosiolib/account.h deleted file mode 100644 index c35e424a932..00000000000 --- a/contracts/eosiolib/account.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @file account.h - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -/** - * @defgroup accountapi Account API - * @brief Define API for querying account data - * @ingroup contractdev - */ - -/** - * @defgroup accountcapi Account C API - * @brief C API for querying account data - * @ingroup accountapi - * @{ - */ - -extern "C" { - /** - * @brief Retrieve the balance for the provided account - * @details Retrieve the balance for the provided account - * - * @param balance - a pointer to a range of memory to store balance data - * @param len - length of the range of memory to store balance data - * @return true if account information is retrieved - * - * @pre data is a valid pointer to a range of memory at least datalen bytes long - * @pre data is a pointer to a balance object - * @pre *((uint64_t*)data) stores the primary key - * - * Example: - * @code - * balance b; - * b.account = N(myaccount); - * balance(b, sizeof(balance)); - * @endcode - * - */ - - bool account_balance_get( void* balance, uint32_t len ); - ///@ } accountcapi -} diff --git a/contracts/eosiolib/account.hpp b/contracts/eosiolib/account.hpp deleted file mode 100644 index f79764661d8..00000000000 --- a/contracts/eosiolib/account.hpp +++ /dev/null @@ -1,81 +0,0 @@ -/** -* @file account.hpp -* @copyright defined in eos/LICENSE.txt -* @brief Defines types and ABI for account API interactions -* -*/ -#pragma once -#include -#include -#include - - -namespace eosio { namespace account { - /** - * @defgroup accountcppapi Account C++ API - * @brief C++ API for querying account data, e.g. account balance - * @ingroup accountapi - * - * @{ - */ - -/** -* @brief The binary structure expected and populated by native balance function. -* @details -* Example: -* @code -* account_balance test1_balance; -* test1_balance.account = N(test1); -* if (account_api::get(test1_balance)) -* { -* eosio::print("test1 balance=", test1_balance.eos_balance, "\n"); -* } -* @endcode -* @{ -*/ -struct PACKED(account_balance) { - /** - * @brief Name of the account who's balance this is - * @details Name of the account who's balance this is - */ - account_name account; - - /** - * @brief Balance for this account - * @details Balance for this account - */ - asset eos_balance; - - /** - * @brief Staked balance for this account - * @details Staked balance for this account - */ - asset staked_balance; - - /** - * @brief Unstaking balance for this account - * @details Unstaking balance for this account - */ - asset unstaking_balance; - - /** - * @brief Time at which last unstaking occurred for this account - * @details Time at which last unstaking occurred for this account - */ - time last_unstaking_time; -}; -/// @} account_balance - -/** - * @brief Retrieve a populated balance structure - * @details Retrieve a populated balance structure - * @param acnt - account - * @return true if account's balance is found - */ -bool get(account_balance& acnt) -{ - return account_balance_get(&acnt, sizeof(account_balance)); -} - -/// @} eosio -} } diff --git a/contracts/eosiolib/action.h b/contracts/eosiolib/action.h index d4b92a50b02..7356c65daba 100644 --- a/contracts/eosiolib/action.h +++ b/contracts/eosiolib/action.h @@ -58,7 +58,7 @@ extern "C" { * require_auth(N(inita)); // Do nothing since inita exists in the auth list * require_auth(N(initb)); // Throws an exception * - * print(now()); // Output: timestamp of last accepted block + * print(current_time()); // Output: timestamp (in microseconds since 1970) of current block * * @endcode * @@ -106,6 +106,8 @@ extern "C" { */ void require_auth2( account_name name, permission_name permission ); + bool is_account( account_name name ); + /** * Send an inline action in the context of this action's parent transaction * @param serialized_action - serialized action @@ -135,18 +137,11 @@ extern "C" { void require_read_lock( account_name name ); /** - * Returns the time in seconds from 1970 of the publication_time + * Returns the time in microseconds from 1970 of the publication_time * @brief Get the publication time - * @return the time in seconds from 1970 of the publication_time - */ - time publication_time(); - - /** - * Get the account which specifies the sender of the action - * @brief Get the sender of the action - * @return the account which specifies the sender of the action + * @return the time in microseconds from 1970 of the publication_time */ - account_name current_sender(); + uint64_t publication_time(); /** * Get the current receiver of the action diff --git a/contracts/eosiolib/datastream.hpp b/contracts/eosiolib/datastream.hpp index fd5ce734180..cd38c01fa19 100644 --- a/contracts/eosiolib/datastream.hpp +++ b/contracts/eosiolib/datastream.hpp @@ -6,9 +6,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -350,6 +352,28 @@ DataStream& operator >> ( DataStream& ds, vector& v ) { return ds; } +template +DataStream& operator << ( DataStream& ds, const std::set& s ) { + ds << unsigned_int( s.size() ); + for( const auto& i : s ) { + ds << i; + } + return ds; +} + +template +DataStream& operator >> ( DataStream& ds, std::set& s ) { + s.clear(); + unsigned_int sz; ds >> sz; + + for( uint32_t i = 0; i < sz.value; ++i ) { + T v; + ds >> v; + s.emplace( std::move(v) ); + } + return ds; +} + template DataStream& operator << ( DataStream& ds, const std::map& m ) { ds << unsigned_int( m.size() ); @@ -372,6 +396,28 @@ DataStream& operator >> ( DataStream& ds, std::map& m ) { return ds; } +template +DataStream& operator << ( DataStream& ds, const boost::container::flat_set& s ) { + ds << unsigned_int( s.size() ); + for( const auto& i : s ) { + ds << i; + } + return ds; +} + +template +DataStream& operator >> ( DataStream& ds, boost::container::flat_set& s ) { + s.clear(); + unsigned_int sz; ds >> sz; + + for( uint32_t i = 0; i < sz.value; ++i ) { + T v; + ds >> v; + s.emplace( std::move(v) ); + } + return ds; +} + template DataStream& operator<<( DataStream& ds, const boost::container::flat_map& m ) { ds << unsigned_int( m.size() ); diff --git a/contracts/eosiolib/dispatcher.hpp b/contracts/eosiolib/dispatcher.hpp index 3c55aa52219..d1ed3fbfb4a 100644 --- a/contracts/eosiolib/dispatcher.hpp +++ b/contracts/eosiolib/dispatcher.hpp @@ -66,7 +66,7 @@ namespace eosio { #define EOSIO_API_CALL( r, OP, elem ) \ case ::eosio::string_to_name( BOOST_PP_STRINGIZE(elem) ): \ eosio::execute_action( &thiscontract, &OP::elem ); \ - return; + break; #define EOSIO_API( TYPE, MEMBERS ) \ BOOST_PP_SEQ_FOR_EACH( EOSIO_API_CALL, TYPE, MEMBERS ) @@ -75,12 +75,16 @@ namespace eosio { extern "C" { \ void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \ auto self = receiver; \ - if( code == self ) { \ + if( action == N(onerror)) { \ + /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \ + eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \ + } \ + if( code == self || action == N(onerror) ) { \ TYPE thiscontract( self ); \ switch( action ) { \ EOSIO_API( TYPE, MEMBERS ) \ } \ - eosio_exit(0); \ + /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \ } \ } \ } \ diff --git a/contracts/eosiolib/eosio.hpp b/contracts/eosiolib/eosio.hpp index 75438b1e73c..3e850a69dea 100644 --- a/contracts/eosiolib/eosio.hpp +++ b/contracts/eosiolib/eosio.hpp @@ -6,11 +6,6 @@ #include #include #include -#include #include #include #include - - - - diff --git a/contracts/eosiolib/fixed_key.hpp b/contracts/eosiolib/fixed_key.hpp index 99fc64cf40b..a011f2f743f 100644 --- a/contracts/eosiolib/fixed_key.hpp +++ b/contracts/eosiolib/fixed_key.hpp @@ -203,7 +203,7 @@ namespace eosio { * @return if c1 > c2, return true, otherwise false */ template - bool operator>(const fixed_key &c1, const fixed_key &c2) { + bool operator>(const fixed_key& c1, const fixed_key& c2) { return c1._data > c2._data; } diff --git a/contracts/eosiolib/math.h b/contracts/eosiolib/math.h deleted file mode 100644 index 9aa41bea91f..00000000000 --- a/contracts/eosiolib/math.h +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -extern "C" { - /** - * @defgroup mathcapi Math C API - * @brief Defines basic mathematical operations for higher abstractions to use. - * @ingroup mathapi - * - * @{ - */ - - /** - * Multiply two 128 bit unsigned integers and assign the value to the first parameter. - * @brief Multiply two 128 unsigned bit integers. Throws exception if pointers are invalid. - * @param self Pointer to the value to be multiplied. It will be replaced with the result. - * @param other Pointer to the Value to be multiplied. - * - * Example: - * @code - * uint128_t self(100); - * uint128_t other(100); - * multeq_i128(&self, &other); - * printi128(self); // Output: 10000 - * @endcode - */ - void multeq_i128( uint128_t* self, const uint128_t* other ); - /** - * Divide two 128 bit unsigned integers and assign the value to the first parameter. - * It will throw an exception if the value of other is zero. - * @brief Divide two 128 unsigned bit integers and throws an exception in case of invalid pointers - * @param self Pointer to numerator. It will be replaced with the result - * @param other Pointer to denominator - * Example: - * @code - * uint128_t self(100); - * uint128_t other(100); - * diveq_i128(&self, &other); - * printi128(self); // Output: 1 - * @endcode - */ - void diveq_i128 ( uint128_t* self, const uint128_t* other ); - - /** - * Get the result of addition between two double interpreted as 64 bit unsigned integer - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision), add them together, and reinterpret_cast the result back to 64 bit unsigned integer. - * @brief Addition between two double - * @param a Value in double interpreted as 64 bit unsigned integer - * @param b Value in double interpreted as 64 bit unsigned integer - * @return Result of addition reinterpret_cast to 64 bit unsigned integers - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(5), i64_to_double(10) ); - * uint64_t b = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_add( a, b ); - * printd(res); // Output: 3 - * @endcode - */ - uint64_t double_add(uint64_t a, uint64_t b); - - /** - * Get the result of multiplication between two double interpreted as 64 bit unsigned integer - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision), multiply them together, and reinterpret_cast the result back to 64 bit unsigned integer. - * @brief Multiplication between two double - * @param a Value in double interpreted as 64 bit unsigned integer - * @param b Value in double interpreted as 64 bit unsigned integer - * @return Result of multiplication reinterpret_cast to 64 bit unsigned integers - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(10), i64_to_double(10) ); - * uint64_t b = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_mult( a, b ); - * printd(res); // Output: 2.5 - * @endcode - */ - uint64_t double_mult(uint64_t a, uint64_t b); - - /** - * Get the result of division between two double interpreted as 64 bit unsigned integer - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision), divide numerator with denominator, and reinterpret_cast the result back to 64 bit unsigned integer. - * Throws an error if b is zero (after it is reinterpret_cast to double) - * @brief Division between two double - * @param a Numerator in double interpreted as 64 bit unsigned integer - * @param b Denominator in double interpreted as 64 bit unsigned integer - * @return Result of division reinterpret_cast to 64 bit unsigned integers - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(10), i64_to_double(100) ); - * printd(a); // Output: 0.1 - * @endcode - */ - uint64_t double_div(uint64_t a, uint64_t b); - - /** - * Get the result of less than comparison between two double - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision) before doing the less than comparison. - * @brief Less than comparison between two double - * @param a Value in double interpreted as 64 bit unsigned integer - * @param b Value in double interpreted as 64 bit unsigned integer - * @return 1 if first input is smaller than second input, 0 otherwise - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(10), i64_to_double(10) ); - * uint64_t b = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_lt( a, b ); - * printi(res); // Output: 1 - * @endcode - */ - uint32_t double_lt(uint64_t a, uint64_t b); - - /** - * Get the result of equality check between two double - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision) before doing equality check. - * @brief Equality check between two double - * @param a Value in double interpreted as 64 bit unsigned integer - * @param b Value in double interpreted as 64 bit unsigned integer - * @return 1 if first input is equal to second input, 0 otherwise - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(10), i64_to_double(10) ); - * uint64_t b = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_eq( a, b ); - * printi(res); // Output: 0 - * @endcode - */ - uint32_t double_eq(uint64_t a, uint64_t b); - - /** - * Get the result of greater than comparison between two double - * This function will first reinterpret_cast both inputs to double (50 decimal digit precision) before doing the greater than comparison. - * @brief Greater than comparison between two double - * @param a Value in double interpreted as 64 bit unsigned integer - * @param b Value in double interpreted as 64 bit unsigned integer - * @return 1 if first input is greater than second input, 0 otherwise - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(10), i64_to_double(10) ); - * uint64_t b = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_gt( a, b ); - * printi(res); // Output: 0 - * @endcode - */ - uint32_t double_gt(uint64_t a, uint64_t b); - - /** - * Convert double (interpreted as 64 bit unsigned integer) to 64 bit unsigned integer. - * This function will first reinterpret_cast the input to double (50 decimal digit precision) then convert it to double, then reinterpret_cast it to 64 bit unsigned integer. - * @brief Convert double to 64 bit unsigned integer - * @param a - value in double interpreted as 64 bit unsigned integer - * @return Result of conversion in 64 bit unsigned integer - * - * Example: - * @code - * uint64_t a = double_div( i64_to_double(5), i64_to_double(2) ); - * uint64_t res = double_to_i64( a ); - * printi(res); // Output: 2 - * @endcode - */ - uint64_t double_to_i64(uint64_t a); - - /** - * Convert 64 bit unsigned integer to double (interpreted as 64 bit unsigned integer). - * This function will convert the input to double (50 decimal digit precision) then reinterpret_cast it to 64 bit unsigned integer. - * @brief Convert 64 bit unsigned integer to double (interpreted as 64 bit unsigned integer) - * @param a - value to be converted - * @return Result of conversion in double (interpreted as 64 bit unsigned integer) - * - * Example: - * @code - * uint64_t res = i64_to_double( 3 ); - * printd(res); // Output: 3 - * @endcode - */ - uint64_t i64_to_double(uint64_t a); - - /// @} -} // extern "C" diff --git a/contracts/eosiolib/math.hpp b/contracts/eosiolib/math.hpp deleted file mode 100644 index 4b3db88f37b..00000000000 --- a/contracts/eosiolib/math.hpp +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include - -namespace eosio { - - /** - * @defgroup mathapi Math API - * @brief Defines common math functions - * @ingroup contractdev - */ - - /** - * @defgroup mathcppapi Math C++ API - * @brief Defines common math functions and helper types - * @ingroup mathapi - * - * @{ - */ - - /** - * Multiply two 128 bit unsigned integers and assign the value to the first parameter. - * This wraps multeq_i128 from @ref mathcapi. - * @brief wraps multeq_i128 from @ref mathcapi - * @param self Value to be multiplied. It will be replaced with the result - * @param other Value integer to be multiplied. - * - * Example: - * @code - * uint128_t self(100); - * uint128_t other(100); - * multeq(self, other); - * std::cout << self; // Output: 10000 - * @endcode - */ - inline void multeq( uint128_t& self, const uint128_t& other ) { - multeq_i128( &self, &other ); - } - - /** - * Divide two 128 bit unsigned integers and assign the value to the first parameter. - * It will throw an exception if other is zero. - * This wraps diveq_i128 from @ref mathcapi - * @brief wraps diveq_i128 from @ref mathcapi - * @param self Numerator. It will be replaced with the result - * @param other Denominator - * - * Example: - * @code - * uint128_t self(100); - * uint128_t other(100); - * diveq(self, other); - * std::cout << self; // Output: 1 - * @endcode - */ - inline void diveq( uint128_t& self, const uint128_t& other ) { - diveq_i128( &self, &other ); - } - - /** - * @brief A struct that wraps uint128 integer and defines common operator overloads - */ - struct uint128 { - public: - uint128( uint128_t i = 0 ):value(i){} - uint128( uint64_t i ):value(i){} - uint128( uint32_t i ):value(i){} - - friend uint128 operator * ( uint128 a, const uint128& b ) { - return a *= b; - } - - friend uint128 operator / ( uint128 a, const uint128& b ) { - return a /= b; - } - friend bool operator <= ( const uint128& a, const uint128& b ) { - return a.value <= b.value; - } - friend bool operator >= ( const uint128& a, const uint128& b ) { - return a.value >= b.value; - } - - uint128& operator *= ( const uint128_t& other ) { - multeq( value, other ); - return *this; - } - uint128& operator *= ( const uint128& other ) { - multeq( value, other.value ); - return *this; - } - - uint128& operator /= ( const uint128_t& other ) { - diveq( value, other ); - return *this; - } - uint128& operator /= ( const uint128& other ) { - diveq( value, other.value ); - return *this; - } - - explicit operator uint64_t()const { - eosio_assert( !(value >> 64), "cast to 64 bit loss of precision" ); - return uint64_t(value); - } - - private: - uint128_t value = 0; - }; - - /** - * Get the smaller of the given values - * @brief Defined similar to std::min() - * @param a Value to compare - * @param b Value to compare - * @return The smaller of a and b. If they are equivalent, returns a - * - * Example: - * @code - * uint128_t a(1); - * uint128_t b(2); - * std::cout << min(a, b); // Output: 1 - * @endcode - */ - template - T min( const T& a, const T&b ) { - return a < b ? a : b; - } - - /** - * Get the greater of the given values. - * @brief Define similar to std::max() - * @param a Value to compare - * @param b Value to compare - * @return The greater of a and b. If they are equivalent, returns a - * - * Example: - * @code - * uint128_t a(1); - * uint128_t b(2); - * std::cout << max(a, b); // Output: 2 - * @endcode - */ - template - T max( const T& a, const T&b ) { - return a > b ? a : b; - } - - /// @} /// mathcppapi -} diff --git a/contracts/eosiolib/multi_index.hpp b/contracts/eosiolib/multi_index.hpp index f96301c0908..4c1bd1f1b18 100644 --- a/contracts/eosiolib/multi_index.hpp +++ b/contracts/eosiolib/multi_index.hpp @@ -209,12 +209,16 @@ class multi_index const T& operator*()const { return *static_cast(_item); } const T* operator->()const { return static_cast(_item); } - const_iterator operator++(int)const { - return ++(const_iterator(*this)); + const_iterator operator++(int){ + const_iterator result(*this); + ++(*this); + return result; } - const_iterator operator--(int)const { - return --(const_iterator(*this)); + const_iterator operator--(int){ + const_iterator result(*this); + --(*this); + return result; } const_iterator& operator++() { @@ -503,11 +507,16 @@ class multi_index const T& operator*()const { return *static_cast(_item); } const T* operator->()const { return static_cast(_item); } - const_iterator operator++(int)const { - return ++(const_iterator(*this)); + const_iterator operator++(int) { + const_iterator result(*this); + ++(*this); + return result; } - const_iterator operator--(int)const { - return --(const_iterator(*this)); + + const_iterator operator--(int) { + const_iterator result(*this); + --(*this); + return result; } const_iterator& operator++() { @@ -728,7 +737,7 @@ class multi_index typedef typename decltype(+hana::at_c<0>(idx))::type index_type; auto secondary = index_type::extract_secondary_key( obj ); - if( hana::at_c(secondary_keys) != secondary ) { + if( memcmp( &hana::at_c(secondary_keys), &secondary, sizeof(secondary) ) != 0 ) { auto indexitr = mutableitem.__iters[index_type::number()]; if( indexitr < 0 ) { @@ -742,9 +751,9 @@ class multi_index }); } - const T& get( uint64_t primary )const { + const T& get( uint64_t primary, const char* error_msg = "unable to find key" )const { auto result = find( primary ); - eosio_assert( result != cend(), "unable to find key" ); + eosio_assert( result != cend(), error_msg ); return *result; } diff --git a/contracts/eosiolib/permission.h b/contracts/eosiolib/permission.h index 6513ab938db..8acd07fd553 100644 --- a/contracts/eosiolib/permission.h +++ b/contracts/eosiolib/permission.h @@ -7,15 +7,62 @@ extern "C" { /** - * Checks if a set of public keys can authorize an account permission - * @brief Checks if a set of public keys can authorize an account permission - * @param account - the account owner of the permission - * @param permission - the permission name to check for authorization - * @param pubkeys - a pointer to an array of public keys (public_key) - * @param pubkeys_len - the lenght of the array of public keys - * @return 1 if the account permission can be authorized, 0 otherwise - */ - int32_t check_authorization( account_name account, permission_name permission, char* pubkeys, uint32_t pubkeys_len ); -} + * @brief Checks if a transaction is authorized by a provided set of keys and permissions + * + * @param trx_data - pointer to the start of the serialized transaction + * @param trx_size - size (in bytes) of the serialized transaction + * @param pubkeys_data - pointer to the start of the serialized vector of provided public keys + * @param pubkeys_size - size (in bytes) of serialized vector of provided public keys (can be 0 if no public keys are to be provided) + * @param perms_data - pointer to the start of the serialized vector of provided permissions (empty permission name acts as wildcard) + * @param perms_size - size (in bytes) of the serialized vector of provided permissions + * + * @return 1 if the transaction is authorized, 0 otherwise + */ + int32_t + check_transaction_authorization( const char* trx_data, uint32_t trx_size, + const char* pubkeys_data, uint32_t pubkeys_size, + const char* perms_data, uint32_t perms_size + ); + + /** + * @brief Checks if a permission is authorized by a provided delay and a provided set of keys and permissions + * + * @param account - the account owner of the permission + * @param permission - the name of the permission to check for authorization + * @param pubkeys_data - pointer to the start of the serialized vector of provided public keys + * @param pubkeys_size - size (in bytes) of serialized vector of provided public keys (can be 0 if no public keys are to be provided) + * @param perms_data - pointer to the start of the serialized vector of provided permissions (empty permission name acts as wildcard) + * @param perms_size - size (in bytes) of the serialized vector of provided permissions + * @param delay_us - the provided delay in microseconds (cannot exceed INT64_MAX) + * + * @return 1 if the permission is authorized, 0 otherwise + */ + int32_t + check_permission_authorization( account_name account, + permission_name permission, + const char* pubkeys_data, uint32_t pubkeys_size, + const char* perms_data, uint32_t perms_size, + uint64_t delay_us + ); + + /** + * @brief Returns the last used time of a permission + * + * @param account - the account owner of the permission + * @param permission - the name of the permission + * + * @return the last used time (in microseconds since Unix epoch) of the permission + */ + int64_t get_permission_last_used( account_name account, permission_name permission ); - + + /** + * @brief Returns the creation time of an account + * + * @param account - the account + * + * @return the creation time (in microseconds since Unix epoch) of the account + */ + int64_t get_account_creation_time( account_name account ); + +} diff --git a/contracts/eosiolib/permission.hpp b/contracts/eosiolib/permission.hpp new file mode 100644 index 00000000000..a6956f5dd28 --- /dev/null +++ b/contracts/eosiolib/permission.hpp @@ -0,0 +1,98 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include + +#include +#include + +namespace eosio { + + /** + * @brief Checks if a transaction is authorized by a provided set of keys and permissions + * + * @param trx - the transaction for which to check authorizations + * @param provided_permissions - the set of permissions which have authorized the transaction (empty permission name acts as wildcard) + * @param provided_keys - the set of public keys which have authorized the transaction + * + * @return whether the transaction was authorized by provided keys and permissions + */ + bool + check_transaction_authorization( const transaction& trx, + const std::set& provided_permissions , + const std::set& provided_keys = std::set() + ) + { + bytes packed_trx = pack(trx); + + bytes packed_keys; + auto nkeys = provided_keys.size(); + if( nkeys > 0 ) { + packed_keys = pack(provided_keys); + } + + bytes packed_perms; + auto nperms = provided_permissions.size(); + if( nperms > 0 ) { + packed_perms = pack(provided_permissions); + } + + auto res = ::check_transaction_authorization( packed_trx.data(), + packed_trx.size(), + (nkeys > 0) ? packed_keys.data() : (const char*)0, + (nkeys > 0) ? packed_keys.size() : 0, + (nperms > 0) ? packed_perms.data() : (const char*)0, + (nperms > 0) ? packed_perms.size() : 0 + ); + + return (res > 0); + } + + /** + * @brief Checks if a permission is authorized by a provided delay and a provided set of keys and permissions + * + * @param account - the account owner of the permission + * @param permission - the permission name to check for authorization + * @param provided_keys - the set of public keys which have authorized the transaction + * @param provided_permissions - the set of permissions which have authorized the transaction (empty permission name acts as wildcard) + * @param provided_delay_us - the provided delay in microseconds (cannot exceed INT64_MAX) + * + * @return whether the permission was authorized by provided delay, keys, and permissions + */ + bool + check_permission_authorization( account_name account, + permission_name permission, + const std::set& provided_keys, + const std::set& provided_permissions = std::set(), + uint64_t provided_delay_us = static_cast(std::numeric_limits::max()) + ) + { + bytes packed_keys; + auto nkeys = provided_keys.size(); + if( nkeys > 0 ) { + packed_keys = pack(provided_keys); + } + + bytes packed_perms; + auto nperms = provided_permissions.size(); + if( nperms > 0 ) { + packed_perms = pack(provided_permissions); + } + + auto res = ::check_permission_authorization( account, + permission, + (nkeys > 0) ? packed_keys.data() : (const char*)0, + (nkeys > 0) ? packed_keys.size() : 0, + (nperms > 0) ? packed_perms.data() : (const char*)0, + (nperms > 0) ? packed_perms.size() : 0, + provided_delay_us + ); + + return (res > 0); + } + +} diff --git a/contracts/eosiolib/print.hpp b/contracts/eosiolib/print.hpp index f2fa3302a10..4394d108b95 100644 --- a/contracts/eosiolib/print.hpp +++ b/contracts/eosiolib/print.hpp @@ -5,7 +5,6 @@ #pragma once #include #include -#include #include #include diff --git a/contracts/eosiolib/privileged.h b/contracts/eosiolib/privileged.h index c0a0fc81649..bffdbf00c91 100644 --- a/contracts/eosiolib/privileged.h +++ b/contracts/eosiolib/privileged.h @@ -18,9 +18,9 @@ extern "C" { * @{ */ - void set_resource_limits( account_name account, uint64_t ram_bytes, uint64_t net_weight, uint64_t cpu_weight ); + void set_resource_limits( account_name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight ); - void set_active_producers( char *producer_data, uint32_t producer_data_size ); + bool set_active_producers( char *producer_data, uint32_t producer_data_size ); bool is_privileged( account_name account ); diff --git a/contracts/eosiolib/privileged.hpp b/contracts/eosiolib/privileged.hpp index e982513820f..39039380d91 100644 --- a/contracts/eosiolib/privileged.hpp +++ b/contracts/eosiolib/privileged.hpp @@ -6,38 +6,46 @@ namespace eosio { struct blockchain_parameters { + uint64_t max_block_net_usage; + uint32_t target_block_net_usage_pct; + uint32_t max_transaction_net_usage; uint32_t base_per_transaction_net_usage; + uint32_t net_usage_leeway; + uint32_t context_free_discount_net_usage_num; + uint32_t context_free_discount_net_usage_den; + + uint32_t max_block_cpu_usage; + uint32_t target_block_cpu_usage_pct; + uint32_t max_transaction_cpu_usage; uint32_t base_per_transaction_cpu_usage; uint32_t base_per_action_cpu_usage; uint32_t base_setcode_cpu_usage; uint32_t per_signature_cpu_usage; - uint32_t per_lock_net_usage; - uint64_t context_free_discount_cpu_usage_num; - uint64_t context_free_discount_cpu_usage_den; - uint32_t max_transaction_cpu_usage; - uint32_t max_transaction_net_usage; - uint64_t max_block_cpu_usage; - uint32_t target_block_cpu_usage_pct; - uint64_t max_block_net_usage; - uint32_t target_block_net_usage_pct; + uint32_t cpu_usage_leeway; + uint32_t context_free_discount_cpu_usage_num; + uint32_t context_free_discount_cpu_usage_den; + uint32_t max_transaction_lifetime; - uint32_t max_transaction_exec_time; - uint16_t max_authority_depth; - uint16_t max_inline_depth; + uint32_t deferred_trx_expiration_window; + uint32_t max_transaction_delay; uint32_t max_inline_action_size; + uint16_t max_inline_action_depth; + uint16_t max_authority_depth; uint32_t max_generated_transaction_count; - uint32_t max_transaction_delay; EOSLIB_SERIALIZE( blockchain_parameters, - (base_per_transaction_net_usage)(base_per_transaction_cpu_usage)(base_per_action_cpu_usage) - (base_setcode_cpu_usage)(per_signature_cpu_usage)(per_lock_net_usage) - (context_free_discount_cpu_usage_num)(context_free_discount_cpu_usage_den) - (max_transaction_cpu_usage)(max_transaction_net_usage) - (max_block_cpu_usage)(target_block_cpu_usage_pct) (max_block_net_usage)(target_block_net_usage_pct) - (max_transaction_lifetime)(max_transaction_exec_time)(max_authority_depth) - (max_inline_depth)(max_inline_action_size)(max_generated_transaction_count) - (max_transaction_delay) + (max_transaction_net_usage)(base_per_transaction_net_usage)(net_usage_leeway) + (context_free_discount_net_usage_num)(context_free_discount_net_usage_den) + + (max_block_cpu_usage)(target_block_cpu_usage_pct) + (max_transaction_cpu_usage)(base_per_transaction_cpu_usage) + (base_per_action_cpu_usage)(base_setcode_cpu_usage)(per_signature_cpu_usage)(cpu_usage_leeway) + (context_free_discount_cpu_usage_num)(context_free_discount_cpu_usage_den) + + (max_transaction_lifetime)(deferred_trx_expiration_window)(max_transaction_delay) + (max_inline_action_size)(max_inline_action_depth) + (max_authority_depth)(max_generated_transaction_count) ) }; @@ -49,14 +57,11 @@ namespace eosio { account_name producer_name; public_key block_signing_key; - EOSLIB_SERIALIZE( producer_key, (producer_name)(block_signing_key) ) - }; - - struct producer_schedule { - uint32_t version = 0; ///< sequentially incrementing version number - std::vector producers; + friend bool operator < ( const producer_key& a, const producer_key& b ) { + return a.producer_name < b.producer_name; + } - EOSLIB_SERIALIZE( producer_schedule, (version)(producers) ) + EOSLIB_SERIALIZE( producer_key, (producer_name)(block_signing_key) ) }; } diff --git a/contracts/eosiolib/producer_schedule.hpp b/contracts/eosiolib/producer_schedule.hpp index 41289e5fe1d..d1b9dd7f5da 100644 --- a/contracts/eosiolib/producer_schedule.hpp +++ b/contracts/eosiolib/producer_schedule.hpp @@ -1,15 +1,11 @@ #pragma once -#include +#include #include namespace eosio { - struct producer_key { - account_name producer_name; - public_key block_signing_key; - }; /** - * Defines both the order, account name, and signing keys of the active set of producers. + * Defines both the order, account name, and signing keys of the active set of producers. */ struct producer_schedule { uint32_t version; ///< sequentially incrementing version number diff --git a/contracts/eosiolib/public_key.hpp b/contracts/eosiolib/public_key.hpp index b7781a98943..414d535bedd 100644 --- a/contracts/eosiolib/public_key.hpp +++ b/contracts/eosiolib/public_key.hpp @@ -7,6 +7,12 @@ namespace eosio { unsigned_int type; std::array data; + friend bool operator == ( const public_key& a, const public_key& b ) { + return std::tie(a.type,a.data) == std::tie(b.type,b.data); + } + friend bool operator != ( const public_key& a, const public_key& b ) { + return std::tie(a.type,a.data) != std::tie(b.type,b.data); + } EOSLIB_SERIALIZE( public_key, (type)(data) ) }; } diff --git a/contracts/eosiolib/real.hpp b/contracts/eosiolib/real.hpp deleted file mode 100644 index c05b087900b..00000000000 --- a/contracts/eosiolib/real.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once -#include -#include - -namespace eosio { - /** - * @defgroup real Real number - * @ingroup mathcppapi - * @brief Real number data type with basic operators. Wrap double class of Math C API. - * - * Example: - * @code - * real a(100); - * real b(10); - * real c = a+b - * real d = a / b - * real e = a*b - * if(a == b) {} - * if(a > b) {} - * if(a < b) {} - * auto val = d.value(); - * @endcode - * @{ - */ - class real { - private: - uint64_t val; - public: - /** - * @brief Constructor to double object from uint64 value - * - * @details Constructor to double object from uint64 value - * @param _val data - */ - real(const uint64_t &_val) : val(_val) {} - - uint64_t value() const { return val; } - - // Arithmetic operations - real operator+(const real &rhs) const; - - real operator*(const real &rhs) const; - - real operator/(const real &rhs) const; - - // Comparison operators - friend bool operator==(const real &c1, const real &c2); - - friend bool operator>(const real &c1, const real &c2); - - friend bool operator<(const real &c1, const real &c2); - - }; - - /** - * @brief Add two real variables - * - * @details Add two real variables - * @param rhs double variable to be added with this - * @return the sum of this and rhs - */ - real real::operator+(const real &rhs) const { - auto _result = double_add(value(), rhs.value()); - return real(_result); - } - - /** - * @brief Multiply two real variables - * - * @details Multiply two real variables - * @param rhs double variable to be multiplied with this - * @return the result after multiplication - */ - real real::operator*(const real &rhs) const { - auto _result = double_mult(value(), rhs.value()); - return real(_result); - } - - /** - * @brief Division between two real variables - * - * @details Division between two real variables - * @param rhs double variable to be multiplied with this - * @return the result after division - */ - real real::operator/(const real &rhs) const { - auto _result = double_div(i64_to_double(value()), i64_to_double(rhs.value())); - return real(_result); - } - - /** - * @brief Compares two double variables c1 and c2 - * - * @details Compares two double variables c1 and c2 - * @return if c1 == c2, return true, otherwise false - */ - bool operator==(const real &c1, const real &c2) { - auto res = double_eq(c1.value(), c2.value()); - return (res == 1); - } - - /** - * @brief Compares two double variables c1 and c2 - * - * @details Compares two double variables c1 and c2 - * @return if c1 > c2, return true, otherwise false - */ - bool operator>(const real &c1, const real &c2) { - auto res = double_gt(c1.value(), c2.value()); - return (res == 1); - } - - /** - * @brief Compares two double variables c1 and c2 - * - * @details Compares two double variables c1 and c2 - * @return if c1 < c2, return true, otherwise false - */ - bool operator<(const real &c1, const real &c2) { - auto res = double_lt(c1.value(), c2.value()); - return (res == 1); - } - /// @} real -} diff --git a/contracts/eosiolib/system.h b/contracts/eosiolib/system.h index 9e9b87e925a..cd14a7d1267 100644 --- a/contracts/eosiolib/system.h +++ b/contracts/eosiolib/system.h @@ -37,14 +37,23 @@ extern "C" { */ [[noreturn]] void eosio_exit( int32_t code ); + /** - * Returns the time in seconds from 1970 of the last accepted block (not the block including this action) - * @brief Get time of the last accepted block - * @return time in seconds from 1970 of the last accepted block + * Returns the time in microseconds from 1970 of the current block + * @brief Get time of the current block (i.e. the block including this action) + * @return time in microseconds from 1970 of the current block */ - time now(); + uint64_t current_time(); + + /** + * Returns the time in seconds from 1970 of the block including this action + * @brief Get time (rounded down to the nearest second) of the current block (i.e. the block including this action) + * @return time in seconds from 1970 of the current block + */ + uint32_t now() { + return (uint32_t)( current_time() / 1000000 ); + } ///@ } systemcapi } - diff --git a/contracts/eosiolib/transaction.h b/contracts/eosiolib/transaction.h index bd6618231a9..4b0b384d5fa 100644 --- a/contracts/eosiolib/transaction.h +++ b/contracts/eosiolib/transaction.h @@ -112,10 +112,5 @@ extern "C" { */ int get_context_free_data( uint32_t index, char* buff, size_t size ); - /** - * Check that prodived authorizations is enough to execute the transaction - */ - void check_auth( const char *serialized_transaction, size_t size, const char* permissions, size_t psize ); - ///@ } transactioncapi } diff --git a/contracts/eosiolib/transaction.hpp b/contracts/eosiolib/transaction.hpp index 79c96bebf6c..e80aca4d82a 100644 --- a/contracts/eosiolib/transaction.hpp +++ b/contracts/eosiolib/transaction.hpp @@ -24,24 +24,23 @@ namespace eosio { class transaction_header { public: - transaction_header( time exp = now() + 60, region_id r = 0 ) - :expiration(exp),region(r) - {} + transaction_header( time exp = now() + 60 ) + :expiration(exp) + { eosio::print("now=", now(), " exp=", expiration, "\n"); } time expiration; - region_id region; uint16_t ref_block_num; uint32_t ref_block_prefix; unsigned_int net_usage_words = 0UL; /// number of 8 byte words this transaction can serialize into after compressions - unsigned_int kcpu_usage = 0UL; /// number of CPU usage units to bill transaction for + uint8_t max_cpu_usage_ms = 0UL; /// number of CPU usage units to bill transaction for unsigned_int delay_sec = 0UL; /// number of CPU usage units to bill transaction for - EOSLIB_SERIALIZE( transaction_header, (expiration)(region)(ref_block_num)(ref_block_prefix)(net_usage_words)(kcpu_usage)(delay_sec) ) + EOSLIB_SERIALIZE( transaction_header, (expiration)(ref_block_num)(ref_block_prefix)(net_usage_words)(max_cpu_usage_ms)(delay_sec) ) }; class transaction : public transaction_header { public: - transaction(time exp = now() + 60, region_id r = 0) : transaction_header( exp, r ) {} + transaction(time exp = now() + 60) : transaction_header( exp ) {} void send(uint64_t sender_id, account_name payer) const { auto serialize = pack(*this); @@ -50,22 +49,24 @@ namespace eosio { vector context_free_actions; vector actions; + extensions_type transaction_extensions; - EOSLIB_SERIALIZE_DERIVED( transaction, transaction_header, (context_free_actions)(actions) ) + EOSLIB_SERIALIZE_DERIVED( transaction, transaction_header, (context_free_actions)(actions)(transaction_extensions) ) }; - class deferred_transaction : public transaction { - public: - uint128_t sender_id; - account_name sender; - account_name payer; - time execute_after; + struct onerror { + uint128_t sender_id; + bytes sent_trx; - static deferred_transaction from_current_action() { - return unpack_action_data(); - } + static onerror from_current_action() { + return unpack_action_data(); + } + + transaction unpack_sent_trx() const { + return unpack(sent_trx); + } - EOSLIB_SERIALIZE_DERIVED( deferred_transaction, transaction, (sender_id)(sender)(payer)(execute_after) ) + EOSLIB_SERIALIZE( onerror, (sender_id)(sent_trx) ) }; /** @@ -83,22 +84,6 @@ namespace eosio { return eosio::unpack(&buf[0], static_cast(size)); } - inline void check_auth(const bytes& trx_packed, const vector& permissions) { - auto perm_packed = pack(permissions); - ::check_auth( trx_packed.data(), trx_packed.size(), perm_packed.data(), perm_packed.size() ); - } - - inline void check_auth(const char *serialized_transaction, size_t size, const vector& permissions) { - auto perm_packed = pack(permissions); - ::check_auth( serialized_transaction, size, perm_packed.data(), perm_packed.size() ); - } - - inline void check_auth(const transaction& trx, const vector& permissions) { - auto trx_packed = pack(trx); - check_auth( trx_packed, permissions ); - //return res > 0; - } - ///@} transactioncpp api } // namespace eos diff --git a/contracts/eosiolib/types.h b/contracts/eosiolib/types.h index e1fa894f38d..e941cb825af 100644 --- a/contracts/eosiolib/types.h +++ b/contracts/eosiolib/types.h @@ -24,6 +24,7 @@ typedef uint64_t permission_name; typedef uint64_t token_name; typedef uint64_t table_name; typedef uint32_t time; +typedef uint32_t block_timestamp; typedef uint64_t scope_name; typedef uint64_t action_name; typedef uint16_t region_id; @@ -61,6 +62,7 @@ struct fixed_string16 { }; typedef struct checksum256 transaction_id_type; +typedef struct checksum256 block_id_type; typedef struct fixed_string16 field_name; diff --git a/contracts/eosiolib/types.hpp b/contracts/eosiolib/types.hpp index 736a4874ed4..cc65059548e 100644 --- a/contracts/eosiolib/types.hpp +++ b/contracts/eosiolib/types.hpp @@ -9,6 +9,8 @@ namespace eosio { + typedef std::vector>> extensions_type; + /** * @brief Converts a base32 symbol into its binary representation, used by string_to_name() * @@ -101,3 +103,9 @@ namespace std { bool operator==(const checksum256& lhs, const checksum256& rhs) { return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; } +bool operator==(const checksum160& lhs, const checksum160& rhs) { + return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +} +bool operator!=(const checksum160& lhs, const checksum160& rhs) { + return memcmp(&lhs, &rhs, sizeof(lhs)) != 0; +} diff --git a/contracts/proxy/proxy.cpp b/contracts/proxy/proxy.cpp index 049b2fcd934..5217ae51356 100644 --- a/contracts/proxy/proxy.cpp +++ b/contracts/proxy/proxy.cpp @@ -68,7 +68,7 @@ namespace proxy { } template - void apply_onerror(uint64_t receiver, const deferred_transaction& failed_dtrx ) { + void apply_onerror(uint64_t receiver, const onerror& error ) { eosio::print("starting onerror\n"); const auto self = receiver; config code_config; @@ -77,10 +77,10 @@ namespace proxy { auto id = code_config.next_id++; configs::store(code_config, self); - eosio::print("Resending Transaction: ", failed_dtrx.sender_id, " as ", id, "\n"); - deferred_transaction failed_dtrx_copy = failed_dtrx; - failed_dtrx_copy.delay_sec = code_config.delay; - failed_dtrx_copy.send(id, self); + eosio::print("Resending Transaction: ", error.sender_id, " as ", id, "\n"); + transaction dtrx = error.unpack_sent_trx(); + dtrx.delay_sec = code_config.delay; + dtrx.send(id, self); } } @@ -91,18 +91,16 @@ extern "C" { /// The apply method implements the dispatch of events to this contract void apply( uint64_t receiver, uint64_t code, uint64_t action ) { - if ( code == N(eosio)) { - if (action == N(onerror)) { - apply_onerror(receiver, deferred_transaction::from_current_action()); - } - } else if ( code == N(eosio.token) ) { - if( action == N(transfer) ) { - apply_transfer(receiver, code, unpack_action_data()); - } - } else if (code == receiver ) { - if ( action == N(setowner)) { - apply_setowner(receiver, unpack_action_data()); - } - } - } + if( code == N(eosio) && action == N(onerror) ) { + apply_onerror( receiver, onerror::from_current_action() ); + } else if( code == N(eosio.token) ) { + if( action == N(transfer) ) { + apply_transfer(receiver, code, unpack_action_data()); + } + } else if( code == receiver ) { + if( action == N(setowner) ) { + apply_setowner(receiver, unpack_action_data()); + } + } + } } diff --git a/contracts/skeleton/skeleton.abi b/contracts/skeleton/skeleton.abi deleted file mode 100644 index 5e32bf0fce9..00000000000 --- a/contracts/skeleton/skeleton.abi +++ /dev/null @@ -1,39 +0,0 @@ -{ - "types": [{ - "new_type_name": "account_name", - "type": "name" - } - ], - "structs": [{ - "name": "transfer", - "base": "", - "fields": [ - {"name":"from", "type":"account_name"}, - {"name":"to", "type":"account_name"}, - {"name":"amount", "type":"uint64"} - ] - },{ - "name": "account", - "base": "", - "fields": [ - {"name":"account", "type":"name"}, - {"name":"balance", "type":"uint64"} - ] - } - ], - "actions": [{ - "name": "transfer", - "type": "transfer", - "ricardian_contract": "" - } - ], - "tables": [{ - "name": "account", - "type": "account", - "index_type": "i64", - "key_names" : ["account"], - "key_types" : ["name"] - } - ], - "ricardian_clauses": [] -} diff --git a/contracts/skeleton/skeleton.cpp b/contracts/skeleton/skeleton.cpp index 85b4480e770..a4ad22e1f1b 100644 --- a/contracts/skeleton/skeleton.cpp +++ b/contracts/skeleton/skeleton.cpp @@ -1,18 +1,15 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include +#include -/** - * The init() and apply() methods must have C calling convention so that the blockchain can lookup and - * call these methods. - */ -extern "C" { +using namespace eosio; - /// The apply method implements the dispatch of events to this contract - void apply( uint64_t receiver, uint64_t code, uint64_t action ) { - eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" ); - } +class hello : public eosio::contract { + public: + using contract::contract; -} // extern "C" + /// @abi action + void hi( account_name user ) { + print( "Hello, ", name{user} ); + } +}; + +EOSIO_ABI( hello, (hi) ) diff --git a/contracts/test_api/test_action.cpp b/contracts/test_api/test_action.cpp index d1b37ec7d7b..3ff1c521f6b 100644 --- a/contracts/test_api/test_action.cpp +++ b/contracts/test_api/test_action.cpp @@ -101,6 +101,16 @@ void test_action::test_cf_action() { memccpy(&v, &i, sizeof(i), sizeof(i)); // verify transaction api access eosio_assert(transaction_size() > 0, "transaction_size failed"); + // verify softfloat api access + float f1 = 1.0f, f2 = 2.0f; + float f3 = f1 + f2; + eosio_assert( f3 > 2.0f, "Unable to add float."); + // verify compiler builtin api access + __int128 ret; + __divti3(ret, 2, 2, 2, 2); + // verify context_free_system_api + eosio_assert( true, "verify eosio_assert can be called" ); + } else if ( cfa.payload == 200 ) { // attempt to access non context free api, privileged_api @@ -120,10 +130,31 @@ void test_action::test_cf_action() { db_idx64_store( N(testapi), N(testapi), N(testapi), 0, &i ); eosio_assert( false, "db_api should not be allowed" ); } else if ( cfa.payload == 204 ) { + db_find_i64( N(testapi), N(testapi), N(testapi), 1); + eosio_assert( false, "db_api should not be allowed" ); + } else if ( cfa.payload == 205 ) { // attempt to access non context free api, send action eosio::action dum_act; dum_act.send(); eosio_assert( false, "action send should not be allowed" ); + } else if ( cfa.payload == 206 ) { + eosio::require_auth(N(test)); + eosio_assert( false, "authorization_api should not be allowed" ); + } else if ( cfa.payload == 207 ) { + now(); + eosio_assert( false, "system_api should not be allowed" ); + } else if ( cfa.payload == 208 ) { + current_time(); + eosio_assert( false, "system_api should not be allowed" ); + } else if ( cfa.payload == 209 ) { + publication_time(); + eosio_assert( false, "system_api should not be allowed" ); + } else if ( cfa.payload == 210 ) { + send_inline( "hello", 6 ); + eosio_assert( false, "transaction_api should not be allowed" ); + } else if ( cfa.payload == 211 ) { + send_deferred( N(testapi), N(testapi), "hello", 6 ); + eosio_assert( false, "transaction_api should not be allowed" ); } } @@ -165,8 +196,9 @@ void test_action::test_abort() { } void test_action::test_publication_time() { - uint32_t pub_time = 0; - read_action_data(&pub_time, sizeof(uint32_t)); + uint64_t pub_time = 0; + uint32_t total = read_action_data(&pub_time, sizeof(uint64_t)); + eosio_assert( total == sizeof(uint64_t), "total == sizeof(uint64_t)"); eosio_assert( pub_time == publication_time(), "pub_time == publication_time()" ); } @@ -178,15 +210,9 @@ void test_action::test_current_receiver(uint64_t receiver, uint64_t code, uint64 eosio_assert( receiver == cur_rec, "the current receiver does not match" ); } -void test_action::test_current_sender() { - account_name cur_send; - read_action_data(&cur_send, sizeof(account_name)); - eosio_assert( current_sender() == cur_send, "the current sender does not match" ); -} - -void test_action::now() { - uint32_t tmp = 0; - uint32_t total = read_action_data(&tmp, sizeof(uint32_t)); - eosio_assert( total == sizeof(uint32_t), "total == sizeof(uint32_t)"); - eosio_assert( tmp == ::now(), "tmp == now()" ); +void test_action::test_current_time() { + uint64_t tmp = 0; + uint32_t total = read_action_data(&tmp, sizeof(uint64_t)); + eosio_assert( total == sizeof(uint64_t), "total == sizeof(uint64_t)"); + eosio_assert( tmp == current_time(), "tmp == current_time()" ); } diff --git a/contracts/test_api/test_api.cpp b/contracts/test_api/test_api.cpp index acad0e4fa26..941ca6169fb 100644 --- a/contracts/test_api/test_api.cpp +++ b/contracts/test_api/test_api.cpp @@ -10,9 +10,7 @@ #include "test_print.cpp" #include "test_types.cpp" #include "test_fixedpoint.cpp" -#include "test_math.cpp" #include "test_compiler_builtins.cpp" -#include "test_real.cpp" #include "test_crypto.cpp" #include "test_chain.cpp" #include "test_transaction.cpp" @@ -25,9 +23,10 @@ account_name global_receiver; extern "C" { void apply( uint64_t receiver, uint64_t code, uint64_t action ) { if( code == N(eosio) && action == N(onerror) ) { - auto error_dtrx = eosio::deferred_transaction::from_current_action(); + auto error = eosio::onerror::from_current_action(); eosio::print("onerror called\n"); - auto error_action = error_dtrx.actions.at(0).name; + auto error_trx = error.unpack_sent_trx(); + auto error_action = error_trx.actions.at(0).name; // Error handlers for deferred transactions in these tests currently only support the first action @@ -43,7 +42,8 @@ extern "C" { } WASM_TEST_HANDLER(test_action, assert_true_cf); - require_auth(code); + if (action != WASM_TEST_ACTION("test_transaction", "stateful_api") && action != WASM_TEST_ACTION("test_transaction", "context_free_api")) + require_auth(code); //test_types WASM_TEST_HANDLER(test_types, types_size); @@ -74,10 +74,9 @@ extern "C" { WASM_TEST_HANDLER(test_action, require_auth); WASM_TEST_HANDLER(test_action, assert_false); WASM_TEST_HANDLER(test_action, assert_true); - WASM_TEST_HANDLER(test_action, now); + WASM_TEST_HANDLER(test_action, test_current_time); WASM_TEST_HANDLER(test_action, test_abort); WASM_TEST_HANDLER_EX(test_action, test_current_receiver); - WASM_TEST_HANDLER(test_action, test_current_sender); WASM_TEST_HANDLER(test_action, test_publication_time); // test named actions @@ -98,15 +97,6 @@ extern "C" { WASM_TEST_HANDLER(test_print, test_printdf); WASM_TEST_HANDLER(test_print, test_printqf); - //test_math - WASM_TEST_HANDLER(test_math, test_multeq); - WASM_TEST_HANDLER(test_math, test_diveq); - WASM_TEST_HANDLER(test_math, test_i64_to_double); - WASM_TEST_HANDLER(test_math, test_double_to_i64); - WASM_TEST_HANDLER(test_math, test_diveq_by_0); - WASM_TEST_HANDLER(test_math, test_double_api); - WASM_TEST_HANDLER(test_math, test_double_api_div_0); - //test crypto WASM_TEST_HANDLER(test_crypto, test_recover_key); WASM_TEST_HANDLER(test_crypto, test_recover_key_assert_true); @@ -144,14 +134,16 @@ extern "C" { WASM_TEST_HANDLER_EX(test_transaction, send_transaction_trigger_error_handler); WASM_TEST_HANDLER_EX(test_transaction, send_transaction_large); WASM_TEST_HANDLER_EX(test_transaction, send_action_sender); - WASM_TEST_HANDLER_EX(test_transaction, send_transaction_expiring_late); WASM_TEST_HANDLER(test_transaction, deferred_print); WASM_TEST_HANDLER_EX(test_transaction, send_deferred_transaction); + WASM_TEST_HANDLER(test_transaction, send_deferred_tx_with_dtt_action); WASM_TEST_HANDLER(test_transaction, cancel_deferred_transaction); WASM_TEST_HANDLER(test_transaction, send_cf_action); WASM_TEST_HANDLER(test_transaction, send_cf_action_fail); - WASM_TEST_HANDLER(test_transaction, read_inline_action); - WASM_TEST_HANDLER(test_transaction, read_inline_cf_action); + WASM_TEST_HANDLER(test_transaction, stateful_api); + WASM_TEST_HANDLER(test_transaction, context_free_api); + WASM_TEST_HANDLER(test_transaction, new_feature); + WASM_TEST_HANDLER(test_transaction, active_new_feature); //test chain WASM_TEST_HANDLER(test_chain, test_activeprods); @@ -164,13 +156,6 @@ extern "C" { WASM_TEST_HANDLER(test_fixedpoint, test_division); WASM_TEST_HANDLER(test_fixedpoint, test_division_by_0); - // test double - WASM_TEST_HANDLER(test_real, create_instances); - WASM_TEST_HANDLER(test_real, test_addition); - WASM_TEST_HANDLER(test_real, test_multiplication); - WASM_TEST_HANDLER(test_real, test_division); - WASM_TEST_HANDLER(test_real, test_division_by_0); - // test checktime WASM_TEST_HANDLER(test_checktime, checktime_pass); WASM_TEST_HANDLER(test_checktime, checktime_failure); @@ -180,6 +165,8 @@ extern "C" { // test permission WASM_TEST_HANDLER_EX(test_permission, check_authorization); + WASM_TEST_HANDLER_EX(test_permission, test_permission_last_used); + WASM_TEST_HANDLER_EX(test_permission, test_account_creation_time); //unhandled test call eosio_assert(false, "Unknown Test"); diff --git a/contracts/test_api/test_api.hpp b/contracts/test_api/test_api.hpp index 704a0257c54..682895ce30f 100644 --- a/contracts/test_api/test_api.hpp +++ b/contracts/test_api/test_api.hpp @@ -8,7 +8,7 @@ namespace eosio { - class deferred_transaction; + class transaction; } @@ -31,7 +31,7 @@ namespace eosio { #define WASM_TEST_ERROR_HANDLER(CALLED_CLASS_STR, CALLED_METHOD_STR, HANDLER_CLASS, HANDLER_METHOD) \ if( error_action == WASM_TEST_ACTION(CALLED_CLASS_STR, CALLED_METHOD_STR) ) { \ - HANDLER_CLASS::HANDLER_METHOD(error_dtrx); \ + HANDLER_CLASS::HANDLER_METHOD(error_trx); \ return; \ } @@ -67,23 +67,12 @@ struct test_action { static void assert_false(); static void assert_true(); static void assert_true_cf(); - static void now(); + static void test_current_time(); static void test_abort() __attribute__ ((noreturn)) ; static void test_current_receiver(uint64_t receiver, uint64_t code, uint64_t action); - static void test_current_sender(); static void test_publication_time(); }; -struct test_math { - static void test_multeq(); - static void test_diveq(); - static void test_diveq_by_0(); - static void test_double_api(); - static void test_double_api_div_0(); - static void test_i64_to_double(); - static void test_double_to_i64(); -}; - struct test_db { static void primary_i64_general(uint64_t receiver, uint64_t code, uint64_t action); static void primary_i64_lowerbound(uint64_t receiver, uint64_t code, uint64_t action); @@ -98,6 +87,8 @@ struct test_db { static void idx_double_nan_create_fail(uint64_t receiver, uint64_t code, uint64_t action); static void idx_double_nan_modify_fail(uint64_t receiver, uint64_t code, uint64_t action); static void idx_double_nan_lookup_fail(uint64_t receiver, uint64_t code, uint64_t action); + + static void misaligned_secondary_key256_tests(uint64_t, uint64_t, uint64_t); }; struct test_multi_index { @@ -170,18 +161,20 @@ struct test_transaction { static void send_transaction(uint64_t receiver, uint64_t code, uint64_t action); static void send_transaction_empty(uint64_t receiver, uint64_t code, uint64_t action); static void send_transaction_trigger_error_handler(uint64_t receiver, uint64_t code, uint64_t action); - static void assert_false_error_handler(const eosio::deferred_transaction&); + static void assert_false_error_handler(const eosio::transaction&); static void send_transaction_max(); static void send_transaction_large(uint64_t receiver, uint64_t code, uint64_t action); - static void send_transaction_expiring_late(uint64_t receiver, uint64_t code, uint64_t action); static void send_action_sender(uint64_t receiver, uint64_t code, uint64_t action); static void deferred_print(); static void send_deferred_transaction(uint64_t receiver, uint64_t code, uint64_t action); + static void send_deferred_tx_with_dtt_action(); static void cancel_deferred_transaction(); static void send_cf_action(); static void send_cf_action_fail(); - static void read_inline_action(); - static void read_inline_cf_action(); + static void stateful_api(); + static void context_free_api(); + static void new_feature(); + static void active_new_feature(); }; struct test_chain { @@ -197,14 +190,6 @@ struct test_fixedpoint { static void test_division_by_0(); }; -struct test_real { - static void create_instances(); - static void test_addition(); - static void test_multiplication(); - static void test_division(); - static void test_division_by_0(); -}; - struct test_compiler_builtins { static void test_multi3(); static void test_divti3(); @@ -269,6 +254,8 @@ struct test_softfloat { struct test_permission { static void check_authorization(uint64_t receiver, uint64_t code, uint64_t action); + static void test_permission_last_used(uint64_t receiver, uint64_t code, uint64_t action); + static void test_account_creation_time(uint64_t receiver, uint64_t code, uint64_t action); }; struct test_datastream { diff --git a/contracts/test_api/test_api_common.hpp b/contracts/test_api/test_api_common.hpp index e74c234291c..8d63a535089 100644 --- a/contracts/test_api/test_api_common.hpp +++ b/contracts/test_api/test_api_common.hpp @@ -58,6 +58,25 @@ struct cf_action { EOSLIB_SERIALIZE( cf_action, (payload)(cfd_idx) ) }; + +// Deferred Transaction Trigger Action +struct dtt_action { + static uint64_t get_name() { + return WASM_TEST_ACTION("test_transaction", "send_deferred_tx_with_dtt_action"); + } + static uint64_t get_account() { + return N(testapi); + } + + uint64_t payer = N(testapi); + uint64_t deferred_account = N(testapi); + uint64_t deferred_action = WASM_TEST_ACTION("test_transaction", "deferred_print"); + uint64_t permission_name = N(active); + uint32_t delay_sec = 2; + + EOSLIB_SERIALIZE( dtt_action, (payer)(deferred_account)(deferred_action)(permission_name)(delay_sec) ) +}; + #pragma pack(pop) static_assert( sizeof(dummy_action) == 13 , "unexpected packing" ); diff --git a/contracts/test_api/test_math.cpp b/contracts/test_api/test_math.cpp deleted file mode 100644 index fb8ecc2e26d..00000000000 --- a/contracts/test_api/test_math.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include -#include -#include - -#include "test_api.hpp" - -void test_math::test_multeq() { - u128_action act = eosio::unpack_action_data(); - - uint128_t self = *(act.values); - uint128_t other = *(act.values+1); - eosio::multeq(self, other); - eosio_assert( self == act.values[2], "test_multeq act.values[0] == act.values[2]" ); -} - -void test_math::test_diveq() { - u128_action act = eosio::unpack_action_data(); - - uint128_t self = *(act.values); - uint128_t other = *(act.values+1); - - eosio::diveq(self, other); - eosio_assert( self == act.values[2], "test_diveq act.values[0] == act.values[2]" ); -} - -void test_math::test_diveq_by_0() { - unsigned __int128 a = 100; - unsigned __int128 b = 0; - eosio::diveq(a, b); -} - - -void test_math::test_i64_to_double() -{ - uint64_t i[4]; - read_action_data(&i, sizeof(i)); - - uint64_t d = i64_to_double(2); - eosio_assert(i[0] == d, "test_i64_to_double i[0] == d"); - - d = i64_to_double(uint64_t(-2)); - eosio_assert(i[1] == d, "test_i64_to_double i[1] == d"); - - d = i64_to_double(100000); - eosio_assert(i[2] == d, "test_i64_to_double i[2] == d"); - - d = i64_to_double(uint64_t(-100000)); - eosio_assert(i[3] == d, "test_i64_to_double i[3] == d"); - - d = i64_to_double(0); - eosio_assert(0 == d, "test_i64_to_double 0 == d"); -} - -void test_math::test_double_to_i64() -{ - uint64_t d[4]; - read_action_data(&d, sizeof(d)); - - int64_t i = (int64_t)double_to_i64(d[0]); - eosio_assert(2 == i, "test_double_to_i64 2 == i"); - - i = (int64_t)double_to_i64(d[1]); - eosio_assert(-2 == i, "test_double_to_i64 -2 == i"); - - i = (int64_t)double_to_i64(d[2]); - eosio_assert(100000 == i, "test_double_to_i64 100000 == i"); - - i = (int64_t)double_to_i64(d[3]); - eosio_assert(-100000 == i, "test_double_to_i64 -100000 == i"); - - i = (int64_t)double_to_i64(0); - eosio_assert(0 == i, "test_double_to_i64 0 == i"); -} - -void test_math::test_double_api() { - - uint64_t res = double_mult( - double_div( i64_to_double(2), i64_to_double(7) ), - double_add( i64_to_double(3), i64_to_double(2) ) - ); - - eosio_assert( double_to_i64(res) == 1, "double funcs"); - - res = double_eq( - double_div( i64_to_double(5), i64_to_double(9) ), - double_div( i64_to_double(5), i64_to_double(9) ) - ); - - eosio_assert(res == 1, "double_eq"); - - res = double_gt( - double_div( i64_to_double(9999999), i64_to_double(7777777) ), - double_div( i64_to_double(9999998), i64_to_double(7777777) ) - ); - - eosio_assert(res == 1, "double_gt"); - - res = double_lt( - double_div( i64_to_double(9999998), i64_to_double(7777777) ), - double_div( i64_to_double(9999999), i64_to_double(7777777) ) - ); - - eosio_assert(res == 1, "double_lt"); -} - -void test_math::test_double_api_div_0() { - double_div( i64_to_double(1), - double_add( - i64_to_double((uint64_t)-5), i64_to_double(5) - )); - - eosio_assert(false, "should've thrown an error"); -} diff --git a/contracts/test_api/test_permission.cpp b/contracts/test_api/test_permission.cpp index 9e902e57504..b899bab9166 100644 --- a/contracts/test_api/test_permission.cpp +++ b/contracts/test_api/test_permission.cpp @@ -13,6 +13,8 @@ #include "test_api.hpp" +#include + struct check_auth_msg { account_name account; permission_name permission; @@ -28,13 +30,50 @@ void test_permission::check_authorization(uint64_t receiver, uint64_t code, uint auto self = receiver; auto params = unpack_action_data(); - uint64_t res64 = (uint64_t)::check_authorization( params.account, params.permission, + auto packed_pubkeys = pack(params.pubkeys); + int64_t res64 = ::check_permission_authorization( params.account, + params.permission, + packed_pubkeys.data(), packed_pubkeys.size(), + (const char*)0, 0, + static_cast(std::numeric_limits::max()) + ); + /* + uint64_t res64 = (uint64_t)::check_authorization( params.account, params.permission, (char*)params.pubkeys.data(), params.pubkeys.size()*sizeof(public_key) ); + */ auto itr = db_lowerbound_i64(self, self, self, 1); if(itr == -1) { - db_store_i64(self, self, self, 1, &res64, sizeof(uint64_t)); + db_store_i64(self, self, self, 1, &res64, sizeof(int64_t)); } else { - db_update_i64(itr, self, &res64, sizeof(uint64_t)); + db_update_i64(itr, self, &res64, sizeof(int64_t)); } } + +struct test_permission_last_used_msg { + account_name account; + permission_name permission; + int64_t last_used_time; + + EOSLIB_SERIALIZE( test_permission_last_used_msg, (account)(permission)(last_used_time) ) +}; + +void test_permission::test_permission_last_used(uint64_t receiver, uint64_t code, uint64_t action) { + (void)code; + (void)action; + using namespace eosio; + + auto params = unpack_action_data(); + + eosio_assert( get_permission_last_used(params.account, params.permission) == params.last_used_time, "unexpected last used permission time" ); +} + +void test_permission::test_account_creation_time(uint64_t receiver, uint64_t code, uint64_t action) { + (void)code; + (void)action; + using namespace eosio; + + auto params = unpack_action_data(); + + eosio_assert( get_account_creation_time(params.account) == params.last_used_time, "unexpected account creation time" ); +} diff --git a/contracts/test_api/test_real.cpp b/contracts/test_api/test_real.cpp deleted file mode 100644 index d208f0b43ac..00000000000 --- a/contracts/test_api/test_real.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include - -#include "test_api.hpp" - -void test_real::create_instances() { - eosio::real lhs1(5); - eosio_assert(lhs1.value() == 5, "real instance value is wrong"); -} - -void test_real::test_division() { - eosio::real lhs1(5); - eosio::real rhs1(10); - eosio::real result1 = lhs1 / rhs1; - - uint64_t a = double_div(i64_to_double(5), i64_to_double(10)); - eosio_assert(a == result1.value(), "real division result is wrong"); -} - -void test_real::test_division_by_0() { - eosio::real lhs1(5); - eosio::real rhs1(0); - eosio::real result1 = lhs1 / rhs1; - // in order to get rid of unused parameter warning - result1 = 0; - - eosio_assert(false, "should've thrown an error"); -} - -void test_real::test_multiplication() { - eosio::real lhs1(5); - eosio::real rhs1(10); - eosio::real result1 = lhs1 * rhs1; - uint64_t res = double_mult( 5, 10 ); - eosio_assert(res == result1.value(), "real multiplication result is wrong"); -} - -void test_real::test_addition() -{ - eosio::real lhs1(5); - eosio::real rhs1(10); - eosio::real result1 = lhs1 / rhs1; - uint64_t a = double_div(i64_to_double(5), i64_to_double(10)); - - eosio::real lhs2(5); - eosio::real rhs2(2); - eosio::real result2 = lhs2 / rhs2; - uint64_t b = double_div(i64_to_double(5), i64_to_double(2)); - - - eosio::real sum = result1+result2; - uint64_t c = double_add( a, b ); - eosio_assert(sum.value() == c, "real addition operation result is wrong"); -} - - diff --git a/contracts/test_api/test_transaction.cpp b/contracts/test_api/test_transaction.cpp index 9f9bc1d98f4..3f686b94b2c 100644 --- a/contracts/test_api/test_transaction.cpp +++ b/contracts/test_api/test_transaction.cpp @@ -202,10 +202,13 @@ void test_transaction::send_transaction_trigger_error_handler(uint64_t receiver, trx.send(0, receiver); } -void test_transaction::assert_false_error_handler(const eosio::deferred_transaction& dtrx) { - auto onerror_action = eosio::get_action(1, 0); - eosio_assert( onerror_action.authorization.at(0).actor == dtrx.actions.at(0).account, - "authorizer of onerror action does not match receiver of original action in the deferred transaction" ); +void test_transaction::assert_false_error_handler(const eosio::transaction& dtrx) { + eosio_assert(dtrx.actions.size() == 1, "transaction should only have one action"); + eosio_assert(dtrx.actions[0].account == N(testapi), "transaction has wrong code"); + eosio_assert(dtrx.actions[0].name == WASM_TEST_ACTION("test_action", "assert_false"), "transaction has wrong name"); + eosio_assert(dtrx.actions[0].authorization.size() == 1, "action should only have one authorization"); + eosio_assert(dtrx.actions[0].authorization[0].actor == N(testapi), "action's authorization has wrong actor"); + eosio_assert(dtrx.actions[0].authorization[0].permission == N(active), "action's authorization has wrong permission"); } /** @@ -226,20 +229,6 @@ void test_transaction::send_transaction_large(uint64_t receiver, uint64_t, uint6 eosio_assert(false, "send_transaction_large() should've thrown an error"); } -void test_transaction::send_transaction_expiring_late(uint64_t receiver, uint64_t, uint64_t) { - using namespace eosio; - account_name cur_send; - read_action_data( &cur_send, sizeof(account_name) ); - test_action_action test_action; - copy_data((char*)&cur_send, sizeof(account_name), test_action.data); - - auto trx = transaction(now() + 60*60*24*365); - trx.actions.emplace_back(vector{{N(testapi), N(active)}}, test_action); - trx.send(0, receiver); - - eosio_assert(false, "send_transaction_expiring_late() should've thrown an error"); -} - /** * deferred transaction */ @@ -256,6 +245,23 @@ void test_transaction::send_deferred_transaction(uint64_t receiver, uint64_t, ui trx.send( 0xffffffffffffffff, receiver ); } +void test_transaction::send_deferred_tx_with_dtt_action() { + using namespace eosio; + dtt_action dtt_act; + read_action_data(&dtt_act, action_data_size()); + + action deferred_act; + deferred_act.account = dtt_act.deferred_account; + deferred_act.name = dtt_act.deferred_action; + deferred_act.authorization = vector{{N(testapi), dtt_act.permission_name}}; + + auto trx = transaction(); + trx.actions.emplace_back(deferred_act); + trx.delay_sec = dtt_act.delay_sec; + trx.send( 0xffffffffffffffff, dtt_act.payer ); +} + + void test_transaction::cancel_deferred_transaction() { using namespace eosio; cancel_deferred( 0xffffffffffffffff ); //use the same id (0) as in send_deferred_transaction @@ -276,62 +282,21 @@ void test_transaction::send_cf_action_fail() { eosio_assert(false, "send_cfa_action_fail() should've thrown an error"); } -void test_transaction::read_inline_action() { - using namespace eosio; - using dummy_act_t = test_dummy_action; - - char buffer[64]; - auto res = get_action( 3, 0, buffer, 64); - eosio_assert(res == -1, "get_action error 0"); - - auto dummy_act = dummy_act_t{1, 2, 3}; - - action act(vector{{N(testapi), N(active)}}, dummy_act); - act.send(); - - res = get_action( 3, 0, buffer, 64); - eosio_assert(res != -1, "get_action error"); - - action tmp; - datastream ds(buffer, (size_t)res); - ds >> tmp.account; - ds >> tmp.name; - ds >> tmp.authorization; - ds >> tmp.data; - - auto dres = tmp.data_as(); - eosio_assert(dres.a == 1 && dres.b == 2 && dres.c == 3, "data_as error"); - - res = get_action( 3, 1, buffer, 64); - eosio_assert(res == -1, "get_action error -1"); +void test_transaction::stateful_api() { + char buf[4] = {1}; + db_store_i64(N(test_transaction), N(table), N(test_transaction), 0, buf, 4); } -void test_transaction::read_inline_cf_action() { - using namespace eosio; - using dummy_act_t = test_dummy_action; - - char buffer[64]; - auto res = get_action( 2, 0, buffer, 64); - eosio_assert(res == -1, "get_action error 0"); - - auto dummy_act = dummy_act_t{1, 2, 3}; - - action act(dummy_act); - act.send_context_free(); - - res = get_action( 2, 0, buffer, 64); - eosio_assert(res != -1, "get_action error"); - - action tmp; - datastream ds(buffer, (size_t)res); - ds >> tmp.account; - ds >> tmp.name; - ds >> tmp.authorization; - ds >> tmp.data; +void test_transaction::context_free_api() { + char buf[128] = {0}; + get_context_free_data(0, buf, sizeof(buf)); +} - auto dres = tmp.data_as(); - eosio_assert(dres.a == 1 && dres.b == 2 && dres.c == 3, "data_as error"); +extern "C" { int is_feature_active(int64_t); } +void test_transaction::new_feature() { + eosio_assert(false == is_feature_active(N(newfeature)), "we should not have new features unless hardfork"); +} - res = get_action( 2, 1, buffer, 64); - eosio_assert(res == -1, "get_action error -1"); +void test_transaction::active_new_feature() { + activate_feature(N(newfeature)); } diff --git a/contracts/test_api_db/test_api_db.cpp b/contracts/test_api_db/test_api_db.cpp index e7b079c74fa..706734ba967 100644 --- a/contracts/test_api_db/test_api_db.cpp +++ b/contracts/test_api_db/test_api_db.cpp @@ -20,6 +20,7 @@ extern "C" { WASM_TEST_HANDLER_EX(test_db, idx_double_nan_create_fail); WASM_TEST_HANDLER_EX(test_db, idx_double_nan_modify_fail); WASM_TEST_HANDLER_EX(test_db, idx_double_nan_lookup_fail); + WASM_TEST_HANDLER_EX(test_db, misaligned_secondary_key256_tests); //unhandled test call eosio_assert(false, "Unknown Test"); diff --git a/contracts/test_api_db/test_db.cpp b/contracts/test_api_db/test_db.cpp index 7fbb6d0996e..1743a907da7 100644 --- a/contracts/test_api_db/test_db.cpp +++ b/contracts/test_api_db/test_db.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../test_api/test_api.hpp" int primary[11] = {0,1,2,3,4,5,6,7,8,9,10}; @@ -534,3 +535,13 @@ void test_db::idx_double_nan_lookup_fail(uint64_t receiver, uint64_t, uint64_t) eosio_assert( false, "idx_double_nan_lookup_fail: unexpected lookup_type" ); } } + +void test_db::misaligned_secondary_key256_tests(uint64_t receiver, uint64_t, uint64_t) { + auto key = eosio::key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL); + char* ptr = (char*)(&key); + ptr += 1; + // test that store doesn't crash on unaligned data + db_idx256_store( N(testapi), N(testtable), N(testapi), 1, (eosio::key256*)(ptr), 2 ); + // test that find_primary doesn't crash on unaligned data + db_idx256_find_primary( N(testapi), N(testtable), N(testapi), (eosio::key256*)(ptr), 2, 0); +} diff --git a/externals/binaryen b/externals/binaryen index 7ba0b53193b..579f3a099c2 160000 --- a/externals/binaryen +++ b/externals/binaryen @@ -1 +1 @@ -Subproject commit 7ba0b53193be81548c0ccb3bb4ee64c13a2b9c86 +Subproject commit 579f3a099c286a45f58ea1ffc7bf671c415be0a6 diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 1cbf6bf0f31..a36e79cf5d2 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory( fc ) +add_subdirectory( builtins ) add_subdirectory( softfloat ) add_subdirectory( chainbase ) add_subdirectory( wasm-jit ) diff --git a/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp b/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp index cc2df52b6d3..093409d22ba 100644 --- a/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp +++ b/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp @@ -8,8 +8,8 @@ #include #include -#include -#include +#include +#include #include //clashes with something deep in the AST includes in clang 6 and possibly other versions of clang @@ -40,10 +40,10 @@ using namespace clang; using namespace std; using namespace clang::tooling; -using namespace eosio::chain::contracts; namespace cl = llvm::cl; namespace eosio { + using namespace eosio::chain; FC_DECLARE_EXCEPTION( abi_generation_exception, 999999, "Unable to generate abi" ); diff --git a/libraries/appbase b/libraries/appbase index 7f2b9581b0d..50dc015b2f0 160000 --- a/libraries/appbase +++ b/libraries/appbase @@ -1 +1 @@ -Subproject commit 7f2b9581b0de10bc94917ef6a1f0d5311284a4b5 +Subproject commit 50dc015b2f0e25c0cd01cf520245da23c0ed446b diff --git a/libraries/builtins/CMakeLists.txt b/libraries/builtins/CMakeLists.txt new file mode 100644 index 00000000000..74417c65168 --- /dev/null +++ b/libraries/builtins/CMakeLists.txt @@ -0,0 +1,33 @@ +# Defines builtins library +project (builtins C) + +# generate compile command database for external tools +set (CMAKE_EXPORT_COMPILE_COMMANDS "ON") + +message ( STATUS "Configuring Builtins" ) +set(C_DEFINES, "-D__wasm__ -DQUAD_PRECISION") +set( CMAKE_C_FLAGS " -Wall ${CMAKE_C_FLAGS} ${C_DEFINES}" ) +set ( builtins_sources + fixtfti.c + fixunstfti.c + fixsfti.c + fixdfti.c + fixunssfti.c + fixunsdfti.c + floattidf.c + floatuntidf.c +) + +file ( GLOB builtins_headers "${CMAKE_CURRENT_SOURCE_DIR}*.h" ) +list( APPEND builtins_sources ${builtins_headers} ) +add_library ( builtins STATIC ${builtins_sources} ) +target_include_directories( builtins PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}../softfloat/source/include" ) + +install ( TARGETS + builtins + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/libraries/builtins/README.txt b/libraries/builtins/README.txt new file mode 100755 index 00000000000..e603dfa0535 --- /dev/null +++ b/libraries/builtins/README.txt @@ -0,0 +1,346 @@ +Compiler-RT +================================ + +This directory and its subdirectories contain source code for the compiler +support routines. + +Compiler-RT is open source software. You may freely distribute it under the +terms of the license agreement found in LICENSE.txt. + +================================ + +This is a replacement library for libgcc. Each function is contained +in its own file. Each function has a corresponding unit test under +test/Unit. + +A rudimentary script to test each file is in the file called +test/Unit/test. + +Here is the specification for this library: + +http://gcc.gnu.org/onlinedocs/gccint/Libgcc.html#Libgcc + +Here is a synopsis of the contents of this library: + +typedef int si_int; +typedef unsigned su_int; + +typedef long long di_int; +typedef unsigned long long du_int; + +// Integral bit manipulation + +di_int __ashldi3(di_int a, si_int b); // a << b +ti_int __ashlti3(ti_int a, si_int b); // a << b + +di_int __ashrdi3(di_int a, si_int b); // a >> b arithmetic (sign fill) +ti_int __ashrti3(ti_int a, si_int b); // a >> b arithmetic (sign fill) +di_int __lshrdi3(di_int a, si_int b); // a >> b logical (zero fill) +ti_int __lshrti3(ti_int a, si_int b); // a >> b logical (zero fill) + +si_int __clzsi2(si_int a); // count leading zeros +si_int __clzdi2(di_int a); // count leading zeros +si_int __clzti2(ti_int a); // count leading zeros +si_int __ctzsi2(si_int a); // count trailing zeros +si_int __ctzdi2(di_int a); // count trailing zeros +si_int __ctzti2(ti_int a); // count trailing zeros + +si_int __ffssi2(si_int a); // find least significant 1 bit +si_int __ffsdi2(di_int a); // find least significant 1 bit +si_int __ffsti2(ti_int a); // find least significant 1 bit + +si_int __paritysi2(si_int a); // bit parity +si_int __paritydi2(di_int a); // bit parity +si_int __parityti2(ti_int a); // bit parity + +si_int __popcountsi2(si_int a); // bit population +si_int __popcountdi2(di_int a); // bit population +si_int __popcountti2(ti_int a); // bit population + +uint32_t __bswapsi2(uint32_t a); // a byteswapped +uint64_t __bswapdi2(uint64_t a); // a byteswapped + +// Integral arithmetic + +di_int __negdi2 (di_int a); // -a +ti_int __negti2 (ti_int a); // -a +di_int __muldi3 (di_int a, di_int b); // a * b +ti_int __multi3 (ti_int a, ti_int b); // a * b +si_int __divsi3 (si_int a, si_int b); // a / b signed +di_int __divdi3 (di_int a, di_int b); // a / b signed +ti_int __divti3 (ti_int a, ti_int b); // a / b signed +su_int __udivsi3 (su_int n, su_int d); // a / b unsigned +du_int __udivdi3 (du_int a, du_int b); // a / b unsigned +tu_int __udivti3 (tu_int a, tu_int b); // a / b unsigned +si_int __modsi3 (si_int a, si_int b); // a % b signed +di_int __moddi3 (di_int a, di_int b); // a % b signed +ti_int __modti3 (ti_int a, ti_int b); // a % b signed +su_int __umodsi3 (su_int a, su_int b); // a % b unsigned +du_int __umoddi3 (du_int a, du_int b); // a % b unsigned +tu_int __umodti3 (tu_int a, tu_int b); // a % b unsigned +du_int __udivmoddi4(du_int a, du_int b, du_int* rem); // a / b, *rem = a % b unsigned +tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem); // a / b, *rem = a % b unsigned +su_int __udivmodsi4(su_int a, su_int b, su_int* rem); // a / b, *rem = a % b unsigned +si_int __divmodsi4(si_int a, si_int b, si_int* rem); // a / b, *rem = a % b signed + + + +// Integral arithmetic with trapping overflow + +si_int __absvsi2(si_int a); // abs(a) +di_int __absvdi2(di_int a); // abs(a) +ti_int __absvti2(ti_int a); // abs(a) + +si_int __negvsi2(si_int a); // -a +di_int __negvdi2(di_int a); // -a +ti_int __negvti2(ti_int a); // -a + +si_int __addvsi3(si_int a, si_int b); // a + b +di_int __addvdi3(di_int a, di_int b); // a + b +ti_int __addvti3(ti_int a, ti_int b); // a + b + +si_int __subvsi3(si_int a, si_int b); // a - b +di_int __subvdi3(di_int a, di_int b); // a - b +ti_int __subvti3(ti_int a, ti_int b); // a - b + +si_int __mulvsi3(si_int a, si_int b); // a * b +di_int __mulvdi3(di_int a, di_int b); // a * b +ti_int __mulvti3(ti_int a, ti_int b); // a * b + + +// Integral arithmetic which returns if overflow + +si_int __mulosi4(si_int a, si_int b, int* overflow); // a * b, overflow set to one if result not in signed range +di_int __mulodi4(di_int a, di_int b, int* overflow); // a * b, overflow set to one if result not in signed range +ti_int __muloti4(ti_int a, ti_int b, int* overflow); // a * b, overflow set to + one if result not in signed range + + +// Integral comparison: a < b -> 0 +// a == b -> 1 +// a > b -> 2 + +si_int __cmpdi2 (di_int a, di_int b); +si_int __cmpti2 (ti_int a, ti_int b); +si_int __ucmpdi2(du_int a, du_int b); +si_int __ucmpti2(tu_int a, tu_int b); + +// Integral / floating point conversion + +di_int __fixsfdi( float a); +di_int __fixdfdi( double a); +di_int __fixxfdi(long double a); + +ti_int __fixsfti( float a); +ti_int __fixdfti( double a); +ti_int __fixxfti(long double a); +uint64_t __fixtfdi(long double input); // ppc only, doesn't match documentation + +su_int __fixunssfsi( float a); +su_int __fixunsdfsi( double a); +su_int __fixunsxfsi(long double a); + +du_int __fixunssfdi( float a); +du_int __fixunsdfdi( double a); +du_int __fixunsxfdi(long double a); + +tu_int __fixunssfti( float a); +tu_int __fixunsdfti( double a); +tu_int __fixunsxfti(long double a); +uint64_t __fixunstfdi(long double input); // ppc only + +float __floatdisf(di_int a); +double __floatdidf(di_int a); +long double __floatdixf(di_int a); +long double __floatditf(int64_t a); // ppc only + +float __floattisf(ti_int a); +double __floattidf(ti_int a); +long double __floattixf(ti_int a); + +float __floatundisf(du_int a); +double __floatundidf(du_int a); +long double __floatundixf(du_int a); +long double __floatunditf(uint64_t a); // ppc only + +float __floatuntisf(tu_int a); +double __floatuntidf(tu_int a); +long double __floatuntixf(tu_int a); + +// Floating point raised to integer power + +float __powisf2( float a, si_int b); // a ^ b +double __powidf2( double a, si_int b); // a ^ b +long double __powixf2(long double a, si_int b); // a ^ b +long double __powitf2(long double a, si_int b); // ppc only, a ^ b + +// Complex arithmetic + +// (a + ib) * (c + id) + + float _Complex __mulsc3( float a, float b, float c, float d); + double _Complex __muldc3(double a, double b, double c, double d); +long double _Complex __mulxc3(long double a, long double b, + long double c, long double d); +long double _Complex __multc3(long double a, long double b, + long double c, long double d); // ppc only + +// (a + ib) / (c + id) + + float _Complex __divsc3( float a, float b, float c, float d); + double _Complex __divdc3(double a, double b, double c, double d); +long double _Complex __divxc3(long double a, long double b, + long double c, long double d); +long double _Complex __divtc3(long double a, long double b, + long double c, long double d); // ppc only + + +// Runtime support + +// __clear_cache() is used to tell process that new instructions have been +// written to an address range. Necessary on processors that do not have +// a unified instruction and data cache. +void __clear_cache(void* start, void* end); + +// __enable_execute_stack() is used with nested functions when a trampoline +// function is written onto the stack and that page range needs to be made +// executable. +void __enable_execute_stack(void* addr); + +// __gcc_personality_v0() is normally only called by the system unwinder. +// C code (as opposed to C++) normally does not need a personality function +// because there are no catch clauses or destructors to be run. But there +// is a C language extension __attribute__((cleanup(func))) which marks local +// variables as needing the cleanup function "func" to be run when the +// variable goes out of scope. That includes when an exception is thrown, +// so a personality handler is needed. +_Unwind_Reason_Code __gcc_personality_v0(int version, _Unwind_Action actions, + uint64_t exceptionClass, struct _Unwind_Exception* exceptionObject, + _Unwind_Context_t context); + +// for use with some implementations of assert() in +void __eprintf(const char* format, const char* assertion_expression, + const char* line, const char* file); + +// for systems with emulated thread local storage +void* __emutls_get_address(struct __emutls_control*); + + +// Power PC specific functions + +// There is no C interface to the saveFP/restFP functions. They are helper +// functions called by the prolog and epilog of functions that need to save +// a number of non-volatile float point registers. +saveFP +restFP + +// PowerPC has a standard template for trampoline functions. This function +// generates a custom trampoline function with the specific realFunc +// and localsPtr values. +void __trampoline_setup(uint32_t* trampOnStack, int trampSizeAllocated, + const void* realFunc, void* localsPtr); + +// adds two 128-bit double-double precision values ( x + y ) +long double __gcc_qadd(long double x, long double y); + +// subtracts two 128-bit double-double precision values ( x - y ) +long double __gcc_qsub(long double x, long double y); + +// multiples two 128-bit double-double precision values ( x * y ) +long double __gcc_qmul(long double x, long double y); + +// divides two 128-bit double-double precision values ( x / y ) +long double __gcc_qdiv(long double a, long double b); + + +// ARM specific functions + +// There is no C interface to the switch* functions. These helper functions +// are only needed by Thumb1 code for efficient switch table generation. +switch16 +switch32 +switch8 +switchu8 + +// There is no C interface to the *_vfp_d8_d15_regs functions. There are +// called in the prolog and epilog of Thumb1 functions. When the C++ ABI use +// SJLJ for exceptions, each function with a catch clause or destuctors needs +// to save and restore all registers in it prolog and epliog. But there is +// no way to access vector and high float registers from thumb1 code, so the +// compiler must add call outs to these helper functions in the prolog and +// epilog. +restore_vfp_d8_d15_regs +save_vfp_d8_d15_regs + + +// Note: long ago ARM processors did not have floating point hardware support. +// Floating point was done in software and floating point parameters were +// passed in integer registers. When hardware support was added for floating +// point, new *vfp functions were added to do the same operations but with +// floating point parameters in floating point registers. + +// Undocumented functions + +float __addsf3vfp(float a, float b); // Appears to return a + b +double __adddf3vfp(double a, double b); // Appears to return a + b +float __divsf3vfp(float a, float b); // Appears to return a / b +double __divdf3vfp(double a, double b); // Appears to return a / b +int __eqsf2vfp(float a, float b); // Appears to return one + // iff a == b and neither is NaN. +int __eqdf2vfp(double a, double b); // Appears to return one + // iff a == b and neither is NaN. +double __extendsfdf2vfp(float a); // Appears to convert from + // float to double. +int __fixdfsivfp(double a); // Appears to convert from + // double to int. +int __fixsfsivfp(float a); // Appears to convert from + // float to int. +unsigned int __fixunssfsivfp(float a); // Appears to convert from + // float to unsigned int. +unsigned int __fixunsdfsivfp(double a); // Appears to convert from + // double to unsigned int. +double __floatsidfvfp(int a); // Appears to convert from + // int to double. +float __floatsisfvfp(int a); // Appears to convert from + // int to float. +double __floatunssidfvfp(unsigned int a); // Appears to convert from + // unisgned int to double. +float __floatunssisfvfp(unsigned int a); // Appears to convert from + // unisgned int to float. +int __gedf2vfp(double a, double b); // Appears to return __gedf2 + // (a >= b) +int __gesf2vfp(float a, float b); // Appears to return __gesf2 + // (a >= b) +int __gtdf2vfp(double a, double b); // Appears to return __gtdf2 + // (a > b) +int __gtsf2vfp(float a, float b); // Appears to return __gtsf2 + // (a > b) +int __ledf2vfp(double a, double b); // Appears to return __ledf2 + // (a <= b) +int __lesf2vfp(float a, float b); // Appears to return __lesf2 + // (a <= b) +int __ltdf2vfp(double a, double b); // Appears to return __ltdf2 + // (a < b) +int __ltsf2vfp(float a, float b); // Appears to return __ltsf2 + // (a < b) +double __muldf3vfp(double a, double b); // Appears to return a * b +float __mulsf3vfp(float a, float b); // Appears to return a * b +int __nedf2vfp(double a, double b); // Appears to return __nedf2 + // (a != b) +double __negdf2vfp(double a); // Appears to return -a +float __negsf2vfp(float a); // Appears to return -a +float __negsf2vfp(float a); // Appears to return -a +double __subdf3vfp(double a, double b); // Appears to return a - b +float __subsf3vfp(float a, float b); // Appears to return a - b +float __truncdfsf2vfp(double a); // Appears to convert from + // double to float. +int __unorddf2vfp(double a, double b); // Appears to return __unorddf2 +int __unordsf2vfp(float a, float b); // Appears to return __unordsf2 + + +Preconditions are listed for each function at the definition when there are any. +Any preconditions reflect the specification at +http://gcc.gnu.org/onlinedocs/gccint/Libgcc.html#Libgcc. + +Assumptions are listed in "int_lib.h", and in individual files. Where possible +assumptions are checked at compile time. diff --git a/libraries/builtins/compiler_builtins.hpp b/libraries/builtins/compiler_builtins.hpp new file mode 100644 index 00000000000..b7ddb446fed --- /dev/null +++ b/libraries/builtins/compiler_builtins.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +extern "C" { + __int128 ___fixdfti(uint64_t); + __int128 ___fixsfti(uint32_t); + __int128 ___fixtfti( float128_t); + unsigned __int128 ___fixunsdfti(uint64_t); + unsigned __int128 ___fixunssfti(uint32_t); + unsigned __int128 ___fixunstfti(float128_t); + double ___floattidf(__int128); + double ___floatuntidf(unsigned __int128); +} diff --git a/libraries/builtins/fixdfti.c b/libraries/builtins/fixdfti.c new file mode 100755 index 00000000000..00389ab861f --- /dev/null +++ b/libraries/builtins/fixdfti.c @@ -0,0 +1,41 @@ +/* ===-- fixdfti.c - Implement __fixdfti -----------------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp64.h" + +typedef __int128 fixint_t; +typedef unsigned __int128 fixuint_t; + +fixint_t ___fixdfti(uint64_t a) { + const fixint_t fixint_max = (fixint_t)((~(fixuint_t)0) / 2); + const fixint_t fixint_min = -fixint_max - 1; + // Break a into sign, exponent, significand + const rep_t aRep = a; + const rep_t aAbs = aRep & absMask; + const fixint_t sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const rep_t significand = (aAbs & significandMask) | implicitBit; + + // If exponent is negative, the result is zero. + if (exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(fixint_t) * CHAR_BIT) + return sign == 1 ? fixint_max : fixint_min; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return sign * (significand >> (significandBits - exponent)); + else + return sign * ((fixint_t)significand << (exponent - significandBits)); + +} diff --git a/libraries/builtins/fixsfti.c b/libraries/builtins/fixsfti.c new file mode 100755 index 00000000000..20f26e644fc --- /dev/null +++ b/libraries/builtins/fixsfti.c @@ -0,0 +1,40 @@ +/* ===-- fixsfti.c - Implement __fixsfti -----------------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp32.h" + +typedef __int128 fixint_t; +typedef unsigned __int128 fixuint_t; + +fixint_t ___fixsfti(uint32_t a) { + const fixint_t fixint_max = (fixint_t)((~(fixuint_t)0) / 2); + const fixint_t fixint_min = -fixint_max - 1; + // Break a into sign, exponent, significand + const rep_t aRep = a; + const rep_t aAbs = aRep & absMask; + const fixint_t sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const rep_t significand = (aAbs & significandMask) | implicitBit; + + // If exponent is negative, the result is zero. + if (exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(fixint_t) * CHAR_BIT) + return sign == 1 ? fixint_max : fixint_min; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return sign * (significand >> (significandBits - exponent)); + else + return sign * ((fixint_t)significand << (exponent - significandBits)); +} diff --git a/libraries/builtins/fixtfti.c b/libraries/builtins/fixtfti.c new file mode 100755 index 00000000000..7e13553257b --- /dev/null +++ b/libraries/builtins/fixtfti.c @@ -0,0 +1,37 @@ +/* ===-- fixtfti.c - Implement __fixtfti -----------------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp128.h" + +__int128 ___fixtfti( float128_t a) { + const __int128 fixint_max = (__int128)((~(unsigned __int128)0) / 2); + const __int128 fixint_min = -fixint_max - 1; + // Break a into sign, exponent, significand + const __int128 aRep = toRep(a); + const __int128 aAbs = aRep & absMask; + const __int128 sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const __int128 significand = (aAbs & significandMask) | implicitBit; + + // If exponent is negative, the result is zero. + if (exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(__int128) * CHAR_BIT) + return sign == 1 ? fixint_max : fixint_min; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return sign * (significand >> (significandBits - exponent)); + else + return sign * ((__int128)significand << (exponent - significandBits)); +} diff --git a/libraries/builtins/fixunsdfti.c b/libraries/builtins/fixunsdfti.c new file mode 100755 index 00000000000..8e45bb33ac6 --- /dev/null +++ b/libraries/builtins/fixunsdfti.c @@ -0,0 +1,37 @@ +/* ===-- fixunsdfti.c - Implement __fixunsdfti -----------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp64.h" + +typedef unsigned __int128 fixuint_t; + +fixuint_t ___fixunsdfti(uint64_t a) { + // Break a into sign, exponent, significand + const rep_t aRep = a; + const rep_t aAbs = aRep & absMask; + const int sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const rep_t significand = (aAbs & significandMask) | implicitBit; + + // If either the value or the exponent is negative, the result is zero. + if (sign == -1 || exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(fixuint_t) * CHAR_BIT) + return ~(fixuint_t)0; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return significand >> (significandBits - exponent); + else + return (fixuint_t)significand << (exponent - significandBits); +} diff --git a/libraries/builtins/fixunssfti.c b/libraries/builtins/fixunssfti.c new file mode 100755 index 00000000000..537d6c472d3 --- /dev/null +++ b/libraries/builtins/fixunssfti.c @@ -0,0 +1,41 @@ +/* ===-- fixunssfti.c - Implement __fixunssfti -----------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + * + * This file implements __fixunssfti for the compiler_rt library. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp32.h" + +typedef unsigned __int128 fixuint_t; + +fixuint_t ___fixunssfti(uint32_t a) { + // Break a into sign, exponent, significand + const rep_t aRep = a; + const rep_t aAbs = aRep & absMask; + const int sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const rep_t significand = (aAbs & significandMask) | implicitBit; + + // If either the value or the exponent is negative, the result is zero. + if (sign == -1 || exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(fixuint_t) * CHAR_BIT) + return ~(fixuint_t)0; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return significand >> (significandBits - exponent); + else + return (fixuint_t)significand << (exponent - significandBits); +} diff --git a/libraries/builtins/fixunstfti.c b/libraries/builtins/fixunstfti.c new file mode 100755 index 00000000000..ff179097762 --- /dev/null +++ b/libraries/builtins/fixunstfti.c @@ -0,0 +1,40 @@ +/* ===-- fixunstfsi.c - Implement __fixunstfsi -----------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ + +#include "fp128.h" + +typedef float128_t fp_t; +typedef unsigned __int128 fixuint_t; +typedef unsigned __int128 tu_int; +typedef __int128 rep_t; + +tu_int ___fixunstfti(fp_t a) { + // Break a into sign, exponent, significand + const rep_t aRep = toRep(a); + const rep_t aAbs = aRep & absMask; + const int sign = aRep & signBit ? -1 : 1; + const int exponent = (aAbs >> significandBits) - exponentBias; + const rep_t significand = (aAbs & significandMask) | implicitBit; + + // If either the value or the exponent is negative, the result is zero. + if (sign == -1 || exponent < 0) + return 0; + + // If the value is too large for the integer type, saturate. + if ((unsigned)exponent >= sizeof(fixuint_t) * CHAR_BIT) + return ~(fixuint_t)0; + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if (exponent < significandBits) + return significand >> (significandBits - exponent); + else + return (fixuint_t)significand << (exponent - significandBits); +} diff --git a/libraries/builtins/floattidf.c b/libraries/builtins/floattidf.c new file mode 100755 index 00000000000..6e415cea95b --- /dev/null +++ b/libraries/builtins/floattidf.c @@ -0,0 +1,79 @@ +/* ===-- floattidf.c - Implement __floattidf -------------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + * + * This file implements __floattidf for the compiler_rt library. + * + * ===----------------------------------------------------------------------=== + */ + +/* Returns: convert a to a double, rounding toward even.*/ + +/* Assumption: double is a IEEE 64 bit floating point type + * ti_int is a 128 bit integral type + */ + +/* seee eeee eeee mmmm mmmm mmmm mmmm mmmm | mmmm mmmm mmmm mmmm mmmm mmmm mmmm mmmm */ + +#include // needed for DBL_MANT_DIG +#include "int_t.h" + +double ___floattidf(__int128 a) +{ + if (a == 0) + return 0.0; + const unsigned N = sizeof(ti_int) * CHAR_BIT; + const ti_int s = a >> (N-1); + a = (a ^ s) - s; + int sd = N - __clzti2(a); /* number of significant digits */ + int e = sd - 1; /* exponent */ + if (sd > DBL_MANT_DIG) + { + /* start: 0000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQxxxxxxxxxxxxxxxxxx + * finish: 000000000000000000000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQR + * 12345678901234567890123456 + * 1 = msb 1 bit + * P = bit DBL_MANT_DIG-1 bits to the right of 1 + * Q = bit DBL_MANT_DIG bits to the right of 1 + * R = "or" of all bits to the right of Q + */ + switch (sd) + { + case DBL_MANT_DIG + 1: + a <<= 1; + break; + case DBL_MANT_DIG + 2: + break; + default: + a = ((tu_int)a >> (sd - (DBL_MANT_DIG+2))) | + ((a & ((tu_int)(-1) >> ((N + DBL_MANT_DIG+2) - sd))) != 0); + }; + /* finish: */ + a |= (a & 4) != 0; /* Or P into R */ + ++a; /* round - this step may add a significant bit */ + a >>= 2; /* dump Q and R */ + /* a is now rounded to DBL_MANT_DIG or DBL_MANT_DIG+1 bits */ + if (a & ((tu_int)1 << DBL_MANT_DIG)) + { + a >>= 1; + ++e; + } + /* a is now rounded to DBL_MANT_DIG bits */ + } + else + { + a <<= (DBL_MANT_DIG - sd); + /* a is now rounded to DBL_MANT_DIG bits */ + } + double_bits fb; + fb.u.s.high = ((uint32_t)s & 0x80000000) | /* sign */ + ((e + 1023) << 20) | /* exponent */ + ((uint32_t)(a >> 32) & 0x000FFFFF); /* mantissa-high */ + fb.u.s.low = (uint32_t)a; /* mantissa-low */ + return fb.f; +} diff --git a/libraries/builtins/floatuntidf.c b/libraries/builtins/floatuntidf.c new file mode 100755 index 00000000000..098e9e7042e --- /dev/null +++ b/libraries/builtins/floatuntidf.c @@ -0,0 +1,76 @@ +/* ===-- floatuntidf.c - Implement __floatuntidf ---------------------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + * + * This file implements __floatuntidf for the compiler_rt library. + * + * ===----------------------------------------------------------------------=== + */ + +#include "int_t.h" +#include + +/* Returns: convert a to a double, rounding toward even. */ + +/* Assumption: double is a IEEE 64 bit floating point type + * tu_int is a 128 bit integral type + */ + +/* seee eeee eeee mmmm mmmm mmmm mmmm mmmm | mmmm mmmm mmmm mmmm mmmm mmmm mmmm mmmm */ + +double ___floatuntidf(tu_int a) +{ + if (a == 0) + return 0.0; + const unsigned N = sizeof(tu_int) * CHAR_BIT; + int sd = N - __clzti2(a); /* number of significant digits */ + int e = sd - 1; /* exponent */ + if (sd > DBL_MANT_DIG) + { + /* start: 0000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQxxxxxxxxxxxxxxxxxx + * finish: 000000000000000000000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQR + * 12345678901234567890123456 + * 1 = msb 1 bit + * P = bit DBL_MANT_DIG-1 bits to the right of 1 + * Q = bit DBL_MANT_DIG bits to the right of 1 + * R = "or" of all bits to the right of Q + */ + switch (sd) + { + case DBL_MANT_DIG + 1: + a <<= 1; + break; + case DBL_MANT_DIG + 2: + break; + default: + a = (a >> (sd - (DBL_MANT_DIG+2))) | + ((a & ((tu_int)(-1) >> ((N + DBL_MANT_DIG+2) - sd))) != 0); + }; + /* finish: */ + a |= (a & 4) != 0; /* Or P into R */ + ++a; /* round - this step may add a significant bit */ + a >>= 2; /* dump Q and R */ + /* a is now rounded to DBL_MANT_DIG or DBL_MANT_DIG+1 bits */ + if (a & ((tu_int)1 << DBL_MANT_DIG)) + { + a >>= 1; + ++e; + } + /* a is now rounded to DBL_MANT_DIG bits */ + } + else + { + a <<= (DBL_MANT_DIG - sd); + /* a is now rounded to DBL_MANT_DIG bits */ + } + double_bits fb; + fb.u.s.high = ((e + 1023) << 20) | /* exponent */ + ((uint32_t)(a >> 32) & 0x000FFFFF); /* mantissa-high */ + fb.u.s.low = (uint32_t)a; /* mantissa-low */ + return fb.f; +} diff --git a/libraries/builtins/fp128.h b/libraries/builtins/fp128.h new file mode 100644 index 00000000000..c096b7f0d21 --- /dev/null +++ b/libraries/builtins/fp128.h @@ -0,0 +1,30 @@ +#ifndef __compiler_rt_fp_128_h__ +#define __compiler_rt_fp_128_h__ + +#include +#include +#include "../softfloat/source/include/softfloat.h" + +#define REP_C (__uint128_t) +#define significandBits 112 +#define typeWidth (sizeof(__int128)*CHAR_BIT) +#define exponentBits (typeWidth - significandBits - 1) +#define maxExponent ((1 << exponentBits) - 1) +#define exponentBias (maxExponent >> 1) + +#define implicitBit (REP_C(1) << significandBits) +#define significandMask (implicitBit - 1U) +#define signBit (REP_C(1) << (significandBits + exponentBits)) +#define absMask (signBit - 1U) +#define exponentMask (absMask ^ significandMask) +#define oneRep ((rep_t)exponentBias << significandBits) +#define infRep exponentMask +#define quietBit (implicitBit >> 1) +#define qnanRep (exponentMask | quietBit) + +static __inline __int128 toRep(float128_t x) { + const union { float128_t f; __int128 i; } rep = {.f = x}; + return rep.i; +} + +#endif //__compiler_rt_fp_h__ diff --git a/libraries/builtins/fp32.h b/libraries/builtins/fp32.h new file mode 100644 index 00000000000..8236c992de1 --- /dev/null +++ b/libraries/builtins/fp32.h @@ -0,0 +1,26 @@ +#ifndef __compiler_rt_fp_32_h__ +#define __compiler_rt_fp_32_h__ + +#include +#include + +typedef uint32_t rep_t; + +#define REP_C (uint32_t) +#define significandBits 23 +#define typeWidth (sizeof(rep_t)*CHAR_BIT) +#define exponentBits (typeWidth - significandBits - 1) +#define maxExponent ((1 << exponentBits) - 1) +#define exponentBias (maxExponent >> 1) + +#define implicitBit (REP_C(1) << significandBits) +#define significandMask (implicitBit - 1U) +#define signBit (REP_C(1) << (significandBits + exponentBits)) +#define absMask (signBit - 1U) +#define exponentMask (absMask ^ significandMask) +#define oneRep ((rep_t)exponentBias << significandBits) +#define infRep exponentMask +#define quietBit (implicitBit >> 1) +#define qnanRep (exponentMask | quietBit) + +#endif //__compiler_rt_fp_h__ diff --git a/libraries/builtins/fp64.h b/libraries/builtins/fp64.h new file mode 100644 index 00000000000..ac0c135a2b1 --- /dev/null +++ b/libraries/builtins/fp64.h @@ -0,0 +1,26 @@ +#ifndef __compiler_rt_fp_64_h__ +#define __compiler_rt_fp_64_h__ + +#include +#include + +typedef uint64_t rep_t; + +#define REP_C (uint64_t) +#define significandBits 52 +#define typeWidth (sizeof(rep_t)*CHAR_BIT) +#define exponentBits (typeWidth - significandBits - 1) +#define maxExponent ((1 << exponentBits) - 1) +#define exponentBias (maxExponent >> 1) + +#define implicitBit (REP_C(1) << significandBits) +#define significandMask (implicitBit - 1U) +#define signBit (REP_C(1) << (significandBits + exponentBits)) +#define absMask (signBit - 1U) +#define exponentMask (absMask ^ significandMask) +#define oneRep ((rep_t)exponentBias << significandBits) +#define infRep exponentMask +#define quietBit (implicitBit >> 1) +#define qnanRep (exponentMask | quietBit) + +#endif //__compiler_rt_fp_h__ diff --git a/libraries/builtins/int_t.h b/libraries/builtins/int_t.h new file mode 100644 index 00000000000..fa1959ce4ed --- /dev/null +++ b/libraries/builtins/int_t.h @@ -0,0 +1,54 @@ +#ifndef __compiler_rt_int_t_h__ +#define __compiler_rt_int_t_h__ +#include +#include + +typedef union +{ + __int128 all; + struct + { + uint64_t low; + int64_t high; + }s; +} twords; + +typedef union +{ + unsigned __int128 all; + struct + { + uint64_t low; + uint64_t high; + }s; +} utwords; + +typedef union +{ + uint64_t all; + struct + { + uint32_t low; + uint32_t high; + }s; +} udwords; + +typedef union +{ + udwords u; + double f; +} double_bits; + + +typedef __int128 ti_int; +typedef unsigned __int128 tu_int; +inline __int128 __clzti2(__int128 a) +{ + twords x; + x.all = a; + const int64_t f = -(x.s.high == 0); + return __builtin_clzll((x.s.high & ~f) | (x.s.low & f)) + + ((int32_t)f & ((int32_t)(sizeof(int64_t) * CHAR_BIT))); +} + +#endif// __compiler_rt_int_t_h__ diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 2a4cb7bf4c7..c5c46ebc5e3 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -2,41 +2,47 @@ file(GLOB HEADERS "include/eosio/chain/*.hpp" "include/eosio/chain/contracts/*.h ## SORT .cpp by most likely to change / break compile add_library( eosio_chain - chain_config.cpp merkle.cpp name.cpp transaction.cpp - block.cpp - block_trace.cpp - wast_to_wasm.cpp - wasm_interface.cpp - wasm_eosio_validation.cpp - wasm_eosio_injection.cpp - apply_context.cpp - resource_limits.cpp - + block_header.cpp + block_header_state.cpp + block_state.cpp fork_database.cpp - get_config.cpp + controller.cpp + authorization_manager.cpp + resource_limits.cpp block_log.cpp - asset.cpp - - - global_property_object.cpp - chain_controller.cpp - - contracts/eosio_contract.cpp - contracts/chain_initializer.cpp - contracts/genesis_state.cpp - contracts/abi_serializer.cpp + genesis_state.cpp + transaction_context.cpp + eosio_contract.cpp + eosio_contract_abi.cpp + +# chain_config.cpp +# block_trace.cpp + wast_to_wasm.cpp + wasm_interface.cpp + wasm_eosio_validation.cpp + wasm_eosio_injection.cpp + apply_context.cpp + abi_serializer.cpp + asset.cpp webassembly/wavm.cpp webassembly/binaryen.cpp +# get_config.cpp +# global_property_object.cpp +# +# contracts/chain_initializer.cpp + + +# transaction_metadata.cpp ${HEADERS} - transaction_metadata.cpp) + ) target_link_libraries( eosio_chain eos_utilities fc chainbase Logging IR WAST WASM Runtime - wasm asmjs passes cfg ast emscripten-optimizer support softfloat + wasm asmjs passes cfg ast emscripten-optimizer support softfloat builtins ) target_include_directories( eosio_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" @@ -44,8 +50,6 @@ target_include_directories( eosio_chain "${CMAKE_CURRENT_SOURCE_DIR}/../../externals/binaryen/src" ) -if(MSVC) - set_source_files_properties( db_init.cpp db_block.cpp database.cpp block_log.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) -endif(MSVC) - - +#if(MSVC) +# set_source_files_properties( db_init.cpp db_block.cpp database.cpp block_log.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) +#endif(MSVC) diff --git a/libraries/chain/contracts/abi_serializer.cpp b/libraries/chain/abi_serializer.cpp similarity index 97% rename from libraries/chain/contracts/abi_serializer.cpp rename to libraries/chain/abi_serializer.cpp index 13a67c29254..4cc8ba36eb8 100644 --- a/libraries/chain/contracts/abi_serializer.cpp +++ b/libraries/chain/abi_serializer.cpp @@ -2,12 +2,12 @@ * @file * @copyright defined in eos/LICENSE.txt */ -#include -#include -#include +#include +#include #include #include #include +#include #include #include #include @@ -15,7 +15,7 @@ using namespace boost; -namespace eosio { namespace chain { namespace contracts { +namespace eosio { namespace chain { using boost::algorithm::ends_with; using std::string; @@ -63,6 +63,7 @@ namespace eosio { namespace chain { namespace contracts { //asset.hpp built_in_types.emplace("asset", pack_unpack()); + built_in_types.emplace("extended_asset", pack_unpack()); //native.hpp built_in_types.emplace("string", pack_unpack()); @@ -78,17 +79,18 @@ namespace eosio { namespace chain { namespace contracts { built_in_types.emplace("fixed_string16", pack_unpack()); built_in_types.emplace("type_name", pack_unpack()); built_in_types.emplace("bytes", pack_unpack()); - built_in_types.emplace("uint8", pack_unpack()); - built_in_types.emplace("uint16", pack_unpack()); - built_in_types.emplace("uint32", pack_unpack()); - built_in_types.emplace("uint64", pack_unpack()); built_in_types.emplace("uint128", pack_unpack()); built_in_types.emplace("uint256", pack_unpack()); built_in_types.emplace("varuint32", pack_unpack()); + built_in_types.emplace("bool", pack_unpack()); built_in_types.emplace("int8", pack_unpack()); built_in_types.emplace("int16", pack_unpack()); built_in_types.emplace("int32", pack_unpack()); built_in_types.emplace("int64", pack_unpack()); + built_in_types.emplace("uint8", pack_unpack()); + built_in_types.emplace("uint16", pack_unpack()); + built_in_types.emplace("uint32", pack_unpack()); + built_in_types.emplace("uint64", pack_unpack()); built_in_types.emplace("varint32", pack_unpack()); built_in_types.emplace("float64", pack_unpack()); built_in_types.emplace("name", pack_unpack()); @@ -317,7 +319,6 @@ namespace eosio { namespace chain { namespace contracts { uint32_t i = 0; if (va.size() > 0) { for( const auto& field : st.fields ) { - idump((field.type)(va[i])(i)); if( va.size() > i ) variant_to_binary(field.type, va[i], ds); else @@ -352,4 +353,4 @@ namespace eosio { namespace chain { namespace contracts { return type_name(); } -} } } +} } diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 75c085ad653..c321eba8cb1 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -1,117 +1,106 @@ #include #include -#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include using boost::container::flat_set; namespace eosio { namespace chain { -void apply_context::exec_one() + +static inline void print_debug(account_name receiver, const action_trace& ar) { + if(fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::debug)) { + if (!ar.console.empty()) { + auto prefix = fc::format_string( + "\n[(${a},${n})->${r}]", + fc::mutable_variant_object() + ("a", ar.act.account) + ("n", ar.act.name) + ("r", receiver)); + dlog(prefix + ": CONSOLE OUTPUT BEGIN =====================\n" + + ar.console + + prefix + ": CONSOLE OUTPUT END =====================" ); + } + } +} + +action_trace apply_context::exec_one() { auto start = fc::time_point::now(); - _cpu_usage = 0; + + const auto& cfg = control.get_global_properties().configuration; try { - const auto &a = mutable_controller.get_database().get(receiver); + const auto &a = control.get_account(receiver); privileged = a.privileged; - auto native = mutable_controller.find_apply_handler(receiver, act.account, act.name); + auto native = control.find_apply_handler(receiver, act.account, act.name); if (native) { (*native)(*this); } - if (a.code.size() > 0 && !(act.name == N(setcode) && act.account == config::system_account_name)) { + if( a.code.size() > 0 + && !(act.account == config::system_account_name && act.name == N(setcode) && receiver == config::system_account_name) ) { try { - mutable_controller.get_wasm_interface().apply(a.code_version, a.code, *this); + control.get_wasm_interface().apply(a.code_version, a.code, *this); } catch ( const wasm_exit& ){} } - } FC_CAPTURE_AND_RETHROW((_pending_console_output.str())); - if (!_write_scopes.empty()) { - std::sort(_write_scopes.begin(), _write_scopes.end()); - } + } FC_CAPTURE_AND_RETHROW((_pending_console_output.str())); - if (!_read_locks.empty()) { - std::sort(_read_locks.begin(), _read_locks.end()); - // remove any write_scopes - auto r_iter = _read_locks.begin(); - for( auto w_iter = _write_scopes.cbegin(); (w_iter != _write_scopes.cend()) && (r_iter != _read_locks.end()); ++w_iter) { - shard_lock w_lock = {receiver, *w_iter}; - while(r_iter != _read_locks.end() && *r_iter < w_lock ) { - ++r_iter; - } + action_receipt r; + r.receiver = receiver; + r.act_digest = digest_type::hash(act); + r.global_sequence = next_global_sequence(); + r.recv_sequence = next_recv_sequence( receiver ); - if (*r_iter == w_lock) { - r_iter = _read_locks.erase(r_iter); - } - } + for( const auto& auth : act.authorization ) { + r.auth_sequence[auth.actor] = next_auth_sequence( auth.actor ); } - // create a receipt for this - vector data_access; - data_access.reserve(_write_scopes.size() + _read_locks.size()); - for (const auto& scope: _write_scopes) { - auto key = boost::make_tuple(scope, receiver); - const auto& scope_sequence = mutable_controller.get_database().find(key); - if (scope_sequence == nullptr) { - try { - mutable_controller.get_mutable_database().create([&](scope_sequence_object &ss) { - ss.scope = scope; - ss.receiver = receiver; - ss.sequence = 1; - }); - } FC_CAPTURE_AND_RETHROW((scope)(receiver)); - data_access.emplace_back(data_access_info{data_access_info::write, receiver, scope, 0}); - } else { - data_access.emplace_back(data_access_info{data_access_info::write, receiver, scope, scope_sequence->sequence}); - try { - mutable_controller.get_mutable_database().modify(*scope_sequence, [&](scope_sequence_object& ss) { - ss.sequence += 1; - }); - } FC_CAPTURE_AND_RETHROW((scope)(receiver)); - } - } + action_trace t(r); + t.trx_id = trx_context.id; + t.act = act; + t.console = _pending_console_output.str(); - for (const auto& lock: _read_locks) { - auto key = boost::make_tuple(lock.scope, lock.account); - const auto& scope_sequence = mutable_controller.get_database().find(key); - if (scope_sequence == nullptr) { - data_access.emplace_back(data_access_info{data_access_info::read, lock.account, lock.scope, 0}); - } else { - data_access.emplace_back(data_access_info{data_access_info::read, lock.account, lock.scope, scope_sequence->sequence}); - } - } + trx_context.executed.emplace_back( move(r) ); + + print_debug(receiver, t); - results.applied_actions.emplace_back(action_trace {receiver, context_free, _cpu_usage, act, _pending_console_output.str(), move(data_access)}); reset_console(); - _read_locks.clear(); - _write_scopes.clear(); - results.applied_actions.back()._profiling_us = fc::time_point::now() - start; + + t.elapsed = fc::time_point::now() - start; + return t; } void apply_context::exec() { - _notified.push_back(act.account); - for( uint32_t i = 0; i < _notified.size(); ++i ) { + _notified.push_back(receiver); + trace = exec_one(); + for( uint32_t i = 1; i < _notified.size(); ++i ) { receiver = _notified[i]; - exec_one(); + trace.inline_traces.emplace_back( exec_one() ); } - for( uint32_t i = 0; i < _cfa_inline_actions.size(); ++i ) { - EOS_ASSERT( recurse_depth < config::max_recursion_depth, transaction_exception, "inline action recursion depth reached" ); - apply_context ncontext( mutable_controller, mutable_db, _cfa_inline_actions[i], trx_meta, recurse_depth + 1 ); - ncontext.context_free = true; - ncontext.exec(); - append_results(move(ncontext.results)); + if( _cfa_inline_actions.size() > 0 || _inline_actions.size() > 0 ) { + EOS_ASSERT( recurse_depth < control.get_global_properties().configuration.max_inline_action_depth, + transaction_exception, "inline action recursion depth reached" ); } - for( uint32_t i = 0; i < _inline_actions.size(); ++i ) { - EOS_ASSERT( recurse_depth < config::max_recursion_depth, transaction_exception, "inline action recursion depth reached" ); - apply_context ncontext( mutable_controller, mutable_db, _inline_actions[i], trx_meta, recurse_depth + 1 ); - ncontext.exec(); - append_results(move(ncontext.results)); + for( const auto& inline_action : _cfa_inline_actions ) { + trace.inline_traces.emplace_back(); + trx_context.dispatch_action( trace.inline_traces.back(), inline_action, inline_action.account, true, recurse_depth + 1 ); + } + + for( const auto& inline_action : _inline_actions ) { + trace.inline_traces.emplace_back(); + trx_context.dispatch_action( trace.inline_traces.back(), inline_action, inline_action.account, false, recurse_depth + 1 ); } } /// exec() @@ -120,22 +109,6 @@ bool apply_context::is_account( const account_name& account )const { return nullptr != db.find( account ); } -bool apply_context::all_authorizations_used()const { - for ( bool has_auth : used_authorizations ) { - if ( !has_auth ) - return false; - } - return true; -} - -vector apply_context::unused_authorizations()const { - vector ret_auths; - for ( uint32_t i=0; i < act.authorization.size(); i++ ) - if ( !used_authorizations[i] ) - ret_auths.push_back( act.authorization[i] ); - return ret_auths; -} - void apply_context::require_authorization( const account_name& account ) { for( uint32_t i=0; i < act.authorization.size(); i++ ) { if( act.authorization[i].actor == account ) { @@ -143,7 +116,7 @@ void apply_context::require_authorization( const account_name& account ) { return; } } - EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}", ("account",account)); + EOS_ASSERT( false, missing_auth_exception, "missing authority of ${account}", ("account",account)); } bool apply_context::has_authorization( const account_name& account )const { @@ -162,42 +135,10 @@ void apply_context::require_authorization(const account_name& account, return; } } - EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}/${permission}", + EOS_ASSERT( false, missing_auth_exception, "missing authority of ${account}/${permission}", ("account",account)("permission",permission) ); } -static bool scopes_contain(const vector& scopes, const scope_name& scope) { - return std::find(scopes.begin(), scopes.end(), scope) != scopes.end(); -} - -static bool locks_contain(const vector& locks, const account_name& account, const scope_name& scope) { - return std::find(locks.begin(), locks.end(), shard_lock{account, scope}) != locks.end(); -} - -void apply_context::require_write_lock(const scope_name& scope) { - if (trx_meta.allowed_write_locks) { - EOS_ASSERT( locks_contain(**trx_meta.allowed_write_locks, receiver, scope), block_lock_exception, "write lock \"${a}::${s}\" required but not provided", ("a", receiver)("s",scope) ); - } - - if (!scopes_contain(_write_scopes, scope)) { - _write_scopes.emplace_back(scope); - } -} - -void apply_context::require_read_lock(const account_name& account, const scope_name& scope) { - if (trx_meta.allowed_read_locks || trx_meta.allowed_write_locks ) { - bool locked_for_read = trx_meta.allowed_read_locks && locks_contain(**trx_meta.allowed_read_locks, account, scope); - if (!locked_for_read && trx_meta.allowed_write_locks) { - locked_for_read = locks_contain(**trx_meta.allowed_write_locks, account, scope); - } - EOS_ASSERT( locked_for_read , block_lock_exception, "read lock \"${a}::${s}\" required but not provided", ("a", account)("s",scope) ); - } - - if (!locks_contain(_read_locks, account, scope)) { - _read_locks.emplace_back(shard_lock{account, scope}); - } -} - bool apply_context::has_recipient( account_name code )const { for( auto a : _notified ) if( a == code ) @@ -205,9 +146,10 @@ bool apply_context::has_recipient( account_name code )const { return false; } -void apply_context::require_recipient( account_name code ) { - if( !has_recipient(code) ) - _notified.push_back(code); +void apply_context::require_recipient( account_name recipient ) { + if( !has_recipient(recipient) ) { + _notified.push_back(recipient); + } } @@ -227,112 +169,147 @@ void apply_context::require_recipient( account_name code ) { * can better understand the security risk. */ void apply_context::execute_inline( action&& a ) { + auto* code = control.db().find(a.account); + EOS_ASSERT( code != nullptr, action_validate_exception, + "inline action's code account ${account} does not exist", ("account", a.account) ); + + for( const auto& auth : a.authorization ) { + auto* actor = control.db().find(auth.actor); + EOS_ASSERT( actor != nullptr, action_validate_exception, + "inline action's authorizing actor ${account} does not exist", ("account", auth.actor) ); + EOS_ASSERT( control.get_authorization_manager().find_permission(auth) != nullptr, action_validate_exception, + "inline action's authorizations include a non-existent permission: {permission}", + ("permission", auth) ); + } + if ( !privileged ) { - if( a.account != receiver ) { - const auto delay = controller.check_authorization({a}, flat_set(), false, {receiver}); - FC_ASSERT( trx_meta.published + delay <= controller.head_block_time(), - "inline action uses a permission that imposes a delay that is not met, set delay_sec in transaction header to at least ${delay} seconds", - ("delay", delay.to_seconds()) ); + if( a.account != receiver ) { // if a contract is calling itself then there is no need to check permissions + control.get_authorization_manager() + .check_authorization( {a}, + {}, + {{receiver, config::eosio_code_name}}, + control.pending_block_time() - trx_context.published, + std::bind(&apply_context::checktime, this, std::placeholders::_1), + false + ); + + //QUESTION: Is it smart to allow a deferred transaction that has been delayed for some time to get away + // with sending an inline action that requires a delay even though the decision to send that inline + // action was made at the moment the deferred transaction was executed with potentially no forewarning? } } + _inline_actions.emplace_back( move(a) ); } void apply_context::execute_context_free_inline( action&& a ) { - FC_ASSERT( a.authorization.size() == 0, "context free actions cannot have authorizations" ); + auto* code = control.db().find(a.account); + EOS_ASSERT( code != nullptr, action_validate_exception, + "inline action's code account ${account} does not exist", ("account", a.account) ); + + EOS_ASSERT( a.authorization.size() == 0, action_validate_exception, + "context-free actions cannot have authorizations" ); + _cfa_inline_actions.emplace_back( move(a) ); } -void apply_context::execute_deferred( deferred_transaction&& trx ) { - try { - trx.set_reference_block(controller.head_block_id()); // No TaPoS check necessary - trx.sender = receiver; - controller.validate_transaction_without_state(trx); - // transaction_api::send_deferred guarantees that trx.execute_after is at least head block time, so no need to check expiration. - // Any other called of this function needs to similarly meet that precondition. - EOS_ASSERT( trx.execute_after < trx.expiration, - transaction_exception, - "Transaction expires at ${trx.expiration} which is before the first allowed time to execute at ${trx.execute_after}", - ("trx.expiration",trx.expiration)("trx.execute_after",trx.execute_after) ); - - controller.validate_expiration_not_too_far(trx, trx.execute_after); - controller.validate_referenced_accounts(trx); - - controller.validate_uniqueness(trx); // TODO: Move this out of here when we have concurrent shards to somewhere we can check for conflicts between shards. - - const auto& gpo = controller.get_global_properties(); - FC_ASSERT( results.deferred_transactions_count < gpo.configuration.max_generated_transaction_count ); - - fc::microseconds delay; - - // privileged accounts can do anything, no need to check auth - if( !privileged ) { - // check to make sure the payer has authorized this deferred transaction's storage in RAM - if (trx.payer != receiver) { - require_authorization(trx.payer); - } - if (trx.payer != receiver) { - require_authorization(trx.payer); - } +void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx ) { + FC_ASSERT( trx.context_free_actions.size() == 0, "context free actions are not currently allowed in generated transactions" ); + trx.expiration = control.pending_block_time() + fc::microseconds(999'999); // Rounds up to nearest second (makes expiration check unnecessary) + trx.set_reference_block(control.head_block_id()); // No TaPoS check necessary + control.validate_referenced_accounts( trx ); - // if a contract is deferring only actions to itself then there is no need - // to check permissions, it could have done everything anyway. - bool check_auth = false; - for( const auto& act : trx.actions ) { - if( act.account != receiver ) { - check_auth = true; - break; - } - } - if( check_auth ) { - delay = controller.check_authorization(trx.actions, flat_set(), false, {receiver}); - FC_ASSERT( trx_meta.published + delay <= controller.head_block_time(), - "deferred transaction uses a permission that imposes a delay that is not met, set delay_sec in transaction header to at least ${delay} seconds", - ("delay", delay.to_seconds()) ); - } + // Charge ahead of time for the additional net usage needed to retire the deferred transaction + // whether that be by successfully executing, soft failure, hard failure, or expiration. + const auto& cfg = control.get_global_properties().configuration; + trx_context.add_net_usage( static_cast(cfg.base_per_transaction_net_usage) + + static_cast(config::transaction_id_net_usage) ); // Will exit early if net usage cannot be payed. + + auto delay = fc::seconds(trx.delay_sec); + + if( !privileged ) { + if (payer != receiver) { + require_authorization(payer); /// uses payer's storage } - auto now = controller.head_block_time(); - if( delay.count() ) { - auto min_execute_after_time = time_point_sec(now + delay + fc::microseconds(999'999)); // rounds up nearest second - EOS_ASSERT( min_execute_after_time <= trx.execute_after, - transaction_exception, - "deferred transaction is specified to execute after ${trx.execute_after} which is earlier than the earliest time allowed by authorization checker", - ("trx.execute_after",trx.execute_after)("min_execute_after_time",min_execute_after_time) ); + // if a contract is deferring only actions to itself then there is no need + // to check permissions, it could have done everything anyway. + bool check_auth = false; + for( const auto& act : trx.actions ) { + if( act.account != receiver ) { + check_auth = true; + break; + } + } + if( check_auth ) { + control.get_authorization_manager() + .check_authorization( trx.actions, + {}, + {{receiver, config::eosio_code_name}}, + delay, + std::bind(&apply_context::checktime, this, std::placeholders::_1), + false + ); } + } - results.deferred_transaction_requests.push_back(move(trx)); - results.deferred_transactions_count++; - } FC_CAPTURE_AND_RETHROW((trx)); -} + uint32_t trx_size = 0; + auto& d = control.db(); + if ( auto ptr = d.find(boost::make_tuple(receiver, sender_id)) ) { + d.modify( *ptr, [&]( auto& gtx ) { + gtx.sender = receiver; + gtx.sender_id = sender_id; + gtx.payer = payer; + gtx.published = control.pending_block_time(); + gtx.delay_until = gtx.published + delay; + gtx.expiration = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window); + + trx_size = gtx.set( trx ); + }); + } else { + d.create( [&]( auto& gtx ) { + gtx.trx_id = trx.id(); + gtx.sender = receiver; + gtx.sender_id = sender_id; + gtx.payer = payer; + gtx.published = control.pending_block_time(); + gtx.delay_until = gtx.published + delay; + gtx.expiration = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window); + + trx_size = gtx.set( trx ); + }); + } -void apply_context::cancel_deferred( const uint128_t& sender_id ) { - results.deferred_transaction_requests.push_back(deferred_reference(receiver, sender_id)); + trx_context.add_ram_usage( payer, (config::billable_size_v + trx_size) ); + checktime( trx_size * 4 ); /// 4 instructions per byte of packed generated trx (estimated) } -void apply_context::add_cpu_usage( const uint64_t usage ) { - // TODO for now just increase the usage, in the future check against some limit - _cpu_usage += usage; +void apply_context::cancel_deferred_transaction( const uint128_t& sender_id, account_name sender ) { + auto& generated_transaction_idx = db.get_mutable_index(); + const auto* gto = db.find(boost::make_tuple(sender, sender_id)); + EOS_ASSERT( gto != nullptr, transaction_exception, + "there is no generated transaction created by account ${sender} with sender id ${sender_id}", + ("sender", sender)("sender_id", sender_id) ); + + trx_context.add_ram_usage( gto->payer, -(config::billable_size_v + gto->packed_trx.size()) ); + generated_transaction_idx.remove(*gto); + checktime( 100 ); } -const contracts::table_id_object* apply_context::find_table( name code, name scope, name table ) { - require_read_lock(code, scope); - return db.find(boost::make_tuple(code, scope, table)); +const table_id_object* apply_context::find_table( name code, name scope, name table ) { + return db.find(boost::make_tuple(code, scope, table)); } -const contracts::table_id_object& apply_context::find_or_create_table( name code, name scope, name table, const account_name &payer ) { - require_read_lock(code, scope); - const auto* existing_tid = db.find(boost::make_tuple(code, scope, table)); +const table_id_object& apply_context::find_or_create_table( name code, name scope, name table, const account_name &payer ) { + const auto* existing_tid = db.find(boost::make_tuple(code, scope, table)); if (existing_tid != nullptr) { return *existing_tid; } - require_write_lock(scope); - - update_db_usage(payer, config::billable_size_v); + update_db_usage(payer, config::billable_size_v); - return mutable_db.create([&](contracts::table_id_object &t_id){ + return db.create([&](table_id_object &t_id){ t_id.code = code; t_id.scope = scope; t_id.table = table; @@ -340,15 +317,16 @@ const contracts::table_id_object& apply_context::find_or_create_table( name code }); } -void apply_context::remove_table( const contracts::table_id_object& tid ) { - update_db_usage(tid.payer, - config::billable_size_v); - mutable_db.remove(tid); +void apply_context::remove_table( const table_id_object& tid ) { + update_db_usage(tid.payer, - config::billable_size_v); + db.remove(tid); } vector apply_context::get_active_producers() const { - const auto& gpo = controller.get_global_properties(); - vector accounts; - for(const auto& producer : gpo.active_producers.producers) + const auto& ap = control.active_producers(); + vector accounts; accounts.reserve( ap.producers.size() ); + + for(const auto& producer : ap.producers ) accounts.push_back(producer.producer_name); return accounts; @@ -360,105 +338,76 @@ void apply_context::reset_console() { } void apply_context::checktime(uint32_t instruction_count) { - if (trx_meta.processing_deadline && fc::time_point::now() > (*trx_meta.processing_deadline)) { - throw checktime_exceeded(); - } - _cpu_usage += instruction_count; + trx_context.check_time(); } - -const bytes& apply_context::get_packed_transaction() { - if( !trx_meta.packed_trx.size() ) { - if (_cached_trx.empty()) { - auto size = fc::raw::pack_size(trx_meta.trx()); - _cached_trx.resize(size); - fc::datastream ds(_cached_trx.data(), size); - fc::raw::pack(ds, trx_meta.trx()); - } - - return _cached_trx; - } - - return trx_meta.packed_trx; +bytes apply_context::get_packed_transaction() { + auto r = fc::raw::pack( static_cast(trx_context.trx) ); + checktime( r.size() ); + return r; } void apply_context::update_db_usage( const account_name& payer, int64_t delta ) { - require_write_lock( payer ); - if( (delta > 0) ) { - if (!(privileged || payer == account_name(receiver))) { + if( delta > 0 ) { + if( !(privileged || payer == account_name(receiver)) ) { require_authorization( payer ); } - - mutable_controller.get_mutable_resource_limits_manager().add_pending_account_ram_usage(payer, delta); } + trx_context.add_ram_usage(payer, delta); } int apply_context::get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const { - const transaction& trx = trx_meta.trx(); - const action* act = nullptr; + const auto& trx = trx_context.trx; + const action* act_ptr = nullptr; + if( type == 0 ) { if( index >= trx.context_free_actions.size() ) return -1; - act = &trx.context_free_actions[index]; + act_ptr = &trx.context_free_actions[index]; } else if( type == 1 ) { if( index >= trx.actions.size() ) return -1; - act = &trx.actions[index]; - } - else if( type == 2 ) { - if( index >= _cfa_inline_actions.size() ) - return -1; - act = &_cfa_inline_actions[index]; - } - else if( type == 3 ) { - if( index >= _inline_actions.size() ) - return -1; - act = &_inline_actions[index]; + act_ptr = &trx.actions[index]; } - auto ps = fc::raw::pack_size( *act ); + auto ps = fc::raw::pack_size( *act_ptr ); if( ps <= buffer_size ) { fc::datastream ds(buffer, buffer_size); - fc::raw::pack( ds, *act ); + fc::raw::pack( ds, *act_ptr ); } return ps; } -int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const { - if( index >= trx_meta.context_free_data.size() ) return -1; +int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const +{ + const auto& trx = trx_context.trx; + + if( index >= trx.context_free_data.size() ) return -1; - auto s = trx_meta.context_free_data[index].size(); + auto s = trx.context_free_data[index].size(); if( buffer_size == 0 ) return s; auto copy_size = std::min( buffer_size, s ); - memcpy( buffer, trx_meta.context_free_data[index].data(), copy_size ); + memcpy( buffer, trx.context_free_data[index].data(), copy_size ); return copy_size; } -void apply_context::check_auth( const transaction& trx, const vector& perm ) { - controller.check_authorization( trx.actions, - {}, - true, - {}, - flat_set(perm.begin(), perm.end()) ); -} - int apply_context::db_store_i64( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) { return db_store_i64( receiver, scope, table, payer, id, buffer, buffer_size); } int apply_context::db_store_i64( uint64_t code, uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) { - require_write_lock( scope ); +// require_write_lock( scope ); const auto& tab = find_or_create_table( code, scope, table, payer ); auto tableid = tab.id; FC_ASSERT( payer != account_name(), "must specify a valid account to pay for new record" ); - const auto& obj = mutable_db.create( [&]( auto& o ) { + const auto& obj = db.create( [&]( auto& o ) { o.t_id = tableid; o.primary_key = id; o.value.resize( buffer_size ); @@ -466,7 +415,7 @@ int apply_context::db_store_i64( uint64_t code, uint64_t scope, uint64_t table, memcpy( o.value.data(), buffer, buffer_size ); }); - mutable_db.modify( tab, [&]( auto& t ) { + db.modify( tab, [&]( auto& t ) { ++t.count; }); @@ -483,7 +432,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* const auto& table_obj = keyval_cache.get_table( obj.t_id ); FC_ASSERT( table_obj.code == receiver, "db access violation" ); - require_write_lock( table_obj.scope ); +// require_write_lock( table_obj.scope ); const int64_t overhead = config::billable_size_v; int64_t old_size = (int64_t)(obj.value.size() + overhead); @@ -501,7 +450,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* update_db_usage( obj.payer, new_size - old_size); } - mutable_db.modify( obj, [&]( auto& o ) { + db.modify( obj, [&]( auto& o ) { o.value.resize( buffer_size ); memcpy( o.value.data(), buffer, buffer_size ); o.payer = payer; @@ -514,14 +463,14 @@ void apply_context::db_remove_i64( int iterator ) { const auto& table_obj = keyval_cache.get_table( obj.t_id ); FC_ASSERT( table_obj.code == receiver, "db access violation" ); - require_write_lock( table_obj.scope ); +// require_write_lock( table_obj.scope ); update_db_usage( obj.payer, -(obj.value.size() + config::billable_size_v) ); - mutable_db.modify( table_obj, [&]( auto& t ) { + db.modify( table_obj, [&]( auto& t ) { --t.count; }); - mutable_db.remove( obj ); + db.remove( obj ); if (table_obj.count == 0) { remove_table(table_obj); @@ -546,7 +495,7 @@ int apply_context::db_next_i64( int iterator, uint64_t& primary ) { if( iterator < -1 ) return -1; // cannot increment past end iterator of table const auto& obj = keyval_cache.get( iterator ); // Check for iterator != -1 happens in this call - const auto& idx = db.get_index(); + const auto& idx = db.get_index(); auto itr = idx.iterator_to( obj ); ++itr; @@ -558,7 +507,7 @@ int apply_context::db_next_i64( int iterator, uint64_t& primary ) { } int apply_context::db_previous_i64( int iterator, uint64_t& primary ) { - const auto& idx = db.get_index(); + const auto& idx = db.get_index(); if( iterator < -1 ) // is end iterator { @@ -590,28 +539,28 @@ int apply_context::db_previous_i64( int iterator, uint64_t& primary ) { } int apply_context::db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); // redundant? + //require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; auto table_end_itr = keyval_cache.cache_table( *tab ); - const key_value_object* obj = db.find( boost::make_tuple( tab->id, id ) ); + const key_value_object* obj = db.find( boost::make_tuple( tab->id, id ) ); if( !obj ) return table_end_itr; return keyval_cache.add( *obj ); } int apply_context::db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); // redundant? + //require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; auto table_end_itr = keyval_cache.cache_table( *tab ); - const auto& idx = db.get_index(); + const auto& idx = db.get_index(); auto itr = idx.lower_bound( boost::make_tuple( tab->id, id ) ); if( itr == idx.end() ) return table_end_itr; if( itr->t_id != tab->id ) return table_end_itr; @@ -620,14 +569,14 @@ int apply_context::db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t ta } int apply_context::db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); // redundant? + //require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; auto table_end_itr = keyval_cache.cache_table( *tab ); - const auto& idx = db.get_index(); + const auto& idx = db.get_index(); auto itr = idx.upper_bound( boost::make_tuple( tab->id, id ) ); if( itr == idx.end() ) return table_end_itr; if( itr->t_id != tab->id ) return table_end_itr; @@ -636,11 +585,37 @@ int apply_context::db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t ta } int apply_context::db_end_i64( uint64_t code, uint64_t scope, uint64_t table ) { - require_read_lock( code, scope ); // redundant? + //require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; return keyval_cache.cache_table( *tab ); } + + +uint64_t apply_context::next_global_sequence() { + const auto& p = control.get_dynamic_global_properties(); + db.modify( p, [&]( auto& dgp ) { + ++dgp.global_action_sequence; + }); + return p.global_action_sequence; +} + +uint64_t apply_context::next_recv_sequence( account_name receiver ) { + const auto& rs = db.get( receiver ); + db.modify( rs, [&]( auto& mrs ) { + ++mrs.recv_sequence; + }); + return rs.recv_sequence; +} +uint64_t apply_context::next_auth_sequence( account_name actor ) { + const auto& rs = db.get( actor ); + db.modify( rs, [&](auto& mrs ){ + ++mrs.auth_sequence; + }); + return rs.auth_sequence; +} + + } } /// eosio::chain diff --git a/libraries/chain/asset.cpp b/libraries/chain/asset.cpp index fb179689e8d..acfb845da11 100644 --- a/libraries/chain/asset.cpp +++ b/libraries/chain/asset.cpp @@ -6,7 +6,6 @@ #include #include #include -#include namespace eosio { namespace chain { typedef boost::multiprecision::int128_t int128_t; @@ -36,8 +35,6 @@ string asset::to_string()const { asset asset::from_string(const string& from) { try { - asset result; - string s = fc::trim(from); // Find space in order to split amount and symbol @@ -59,11 +56,12 @@ asset asset::from_string(const string& from) } else { precision_digit_str = "0"; } + string symbol_part = precision_digit_str + ',' + symbol_str; - result.sym = symbol::from_string(symbol_part); + symbol sym = symbol::from_string(symbol_part); // Parse amount - int64_t int_part, fract_part = 0; + safe int_part, fract_part; if (dot_pos != string::npos) { int_part = fc::to_int64(amount_str.substr(0, dot_pos)); fract_part = fc::to_int64(amount_str.substr(dot_pos + 1)); @@ -71,11 +69,12 @@ asset asset::from_string(const string& from) } else { int_part = fc::to_int64(amount_str); } - result.amount = int_part; - result.amount *= int64_t(result.precision()); - result.amount += fract_part; - return result; + safe amount = int_part; + amount *= safe(sym.precision()); + amount += fract_part; + + return asset(amount.value, sym); } FC_CAPTURE_LOG_AND_RETHROW( (from) ) } diff --git a/libraries/chain/authorization_manager.cpp b/libraries/chain/authorization_manager.cpp new file mode 100644 index 00000000000..4dceb7658a1 --- /dev/null +++ b/libraries/chain/authorization_manager.cpp @@ -0,0 +1,473 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio { namespace chain { + + authorization_manager::authorization_manager(controller& c, database& d) + :_control(c),_db(d){} + + void authorization_manager::add_indices() { + _db.add_index(); + _db.add_index(); + _db.add_index(); + } + + void authorization_manager::initialize_database() { + _db.create([](auto&){}); /// reserve perm 0 (used else where) + } + + const permission_object& authorization_manager::create_permission( account_name account, + permission_name name, + permission_id_type parent, + const authority& auth, + time_point initial_creation_time + ) + { + auto creation_time = initial_creation_time; + if( creation_time == time_point() ) { + creation_time = _control.pending_block_time(); + } + + const auto& perm_usage = _db.create([&](auto& p) { + p.last_used = creation_time; + }); + + const auto& perm = _db.create([&](auto& p) { + p.usage_id = perm_usage.id; + p.parent = parent; + p.owner = account; + p.name = name; + p.last_updated = creation_time; + p.auth = auth; + }); + return perm; + } + + const permission_object& authorization_manager::create_permission( account_name account, + permission_name name, + permission_id_type parent, + authority&& auth, + time_point initial_creation_time + ) + { + auto creation_time = initial_creation_time; + if( creation_time == time_point() ) { + creation_time = _control.pending_block_time(); + } + + const auto& perm_usage = _db.create([&](auto& p) { + p.last_used = creation_time; + }); + + const auto& perm = _db.create([&](auto& p) { + p.usage_id = perm_usage.id; + p.parent = parent; + p.owner = account; + p.name = name; + p.last_updated = creation_time; + p.auth = std::move(auth); + }); + return perm; + } + + void authorization_manager::modify_permission( const permission_object& permission, const authority& auth ) { + _db.modify( permission, [&](permission_object& po) { + po.auth = auth; + po.last_updated = _control.pending_block_time(); + }); + } + + void authorization_manager::remove_permission( const permission_object& permission ) { + const auto& index = _db.template get_index(); + auto range = index.equal_range(permission.id); + EOS_ASSERT( range.first == range.second, action_validate_exception, + "Cannot remove a permission which has children. Remove the children first."); + + _db.get_mutable_index().remove_object( permission.usage_id._id ); + _db.remove( permission ); + } + + void authorization_manager::update_permission_usage( const permission_object& permission ) { + const auto& puo = _db.get( permission.usage_id ); + _db.modify( puo, [&](permission_usage_object& p) { + p.last_used = _control.pending_block_time(); + }); + } + + fc::time_point authorization_manager::get_permission_last_used( const permission_object& permission )const { + return _db.get( permission.usage_id ).last_used; + } + + const permission_object* authorization_manager::find_permission( const permission_level& level )const + { try { + FC_ASSERT( !level.actor.empty() && !level.permission.empty(), "Invalid permission" ); + return _db.find( boost::make_tuple(level.actor,level.permission) ); + } EOS_RETHROW_EXCEPTIONS( chain::permission_query_exception, "Failed to retrieve permission: ${level}", ("level", level) ) } + + const permission_object& authorization_manager::get_permission( const permission_level& level )const + { try { + FC_ASSERT( !level.actor.empty() && !level.permission.empty(), "Invalid permission" ); + return _db.get( boost::make_tuple(level.actor,level.permission) ); + } EOS_RETHROW_EXCEPTIONS( chain::permission_query_exception, "Failed to retrieve permission: ${level}", ("level", level) ) } + + optional authorization_manager::lookup_linked_permission( account_name authorizer_account, + account_name scope, + action_name act_name + )const + { + try { + // First look up a specific link for this message act_name + auto key = boost::make_tuple(authorizer_account, scope, act_name); + auto link = _db.find(key); + // If no specific link found, check for a contract-wide default + if (link == nullptr) { + boost::get<2>(key) = ""; + link = _db.find(key); + } + + // If no specific or default link found, use active permission + if (link != nullptr) { + return link->required_permission; + } + return optional(); + + // return optional(); + } FC_CAPTURE_AND_RETHROW((authorizer_account)(scope)(act_name)) + } + + optional authorization_manager::lookup_minimum_permission( account_name authorizer_account, + account_name scope, + action_name act_name + )const + { + // Special case native actions cannot be linked to a minimum permission, so there is no need to check. + if( scope == config::system_account_name ) { + FC_ASSERT( act_name != updateauth::get_name() && + act_name != deleteauth::get_name() && + act_name != linkauth::get_name() && + act_name != unlinkauth::get_name() && + act_name != canceldelay::get_name(), + "cannot call lookup_minimum_permission on native actions that are not allowed to be linked to minimum permissions" ); + } + + try { + optional linked_permission = lookup_linked_permission(authorizer_account, scope, act_name); + if( !linked_permission ) + return config::active_name; + + if( *linked_permission == config::eosio_any_name ) + return optional(); + + return linked_permission; + } FC_CAPTURE_AND_RETHROW((authorizer_account)(scope)(act_name)) + } + + void authorization_manager::check_updateauth_authorization( const updateauth& update, + const vector& auths + )const + { + EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception, + "updateauth action should only have one declared authorization" ); + const auto& auth = auths[0]; + EOS_ASSERT( auth.actor == update.account, irrelevant_auth_exception, + "the owner of the affected permission needs to be the actor of the declared authorization" ); + + const auto* min_permission = find_permission({update.account, update.permission}); + if( !min_permission ) { // creating a new permission + min_permission = &get_permission({update.account, update.parent}); + } + + EOS_ASSERT( get_permission(auth).satisfies( *min_permission, + _db.get_index().indices() ), + irrelevant_auth_exception, + "updateauth action declares irrelevant authority '${auth}'; minimum authority is ${min}", + ("auth", auth)("min", permission_level{update.account, min_permission->name}) ); + } + + void authorization_manager::check_deleteauth_authorization( const deleteauth& del, + const vector& auths + )const + { + EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception, + "deleteauth action should only have one declared authorization" ); + const auto& auth = auths[0]; + EOS_ASSERT( auth.actor == del.account, irrelevant_auth_exception, + "the owner of the permission to delete needs to be the actor of the declared authorization" ); + + const auto& min_permission = get_permission({del.account, del.permission}); + + EOS_ASSERT( get_permission(auth).satisfies( min_permission, + _db.get_index().indices() ), + irrelevant_auth_exception, + "updateauth action declares irrelevant authority '${auth}'; minimum authority is ${min}", + ("auth", auth)("min", permission_level{min_permission.owner, min_permission.name}) ); + } + + void authorization_manager::check_linkauth_authorization( const linkauth& link, + const vector& auths + )const + { + EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception, + "link action should only have one declared authorization" ); + const auto& auth = auths[0]; + EOS_ASSERT( auth.actor == link.account, irrelevant_auth_exception, + "the owner of the linked permission needs to be the actor of the declared authorization" ); + + EOS_ASSERT( link.type != updateauth::get_name(), action_validate_exception, + "Cannot link eosio::updateauth to a minimum permission" ); + EOS_ASSERT( link.type != deleteauth::get_name(), action_validate_exception, + "Cannot link eosio::deleteauth to a minimum permission" ); + EOS_ASSERT( link.type != linkauth::get_name(), action_validate_exception, + "Cannot link eosio::linkauth to a minimum permission" ); + EOS_ASSERT( link.type != unlinkauth::get_name(), action_validate_exception, + "Cannot link eosio::unlinkauth to a minimum permission" ); + EOS_ASSERT( link.type != canceldelay::get_name(), action_validate_exception, + "Cannot link eosio::canceldelay to a minimum permission" ); + + const auto linked_permission_name = lookup_minimum_permission(link.account, link.code, link.type); + + if( !linked_permission_name ) // if action is linked to eosio.any permission + return; + + EOS_ASSERT( get_permission(auth).satisfies( get_permission({link.account, *linked_permission_name}), + _db.get_index().indices() ), + irrelevant_auth_exception, + "link action declares irrelevant authority '${auth}'; minimum authority is ${min}", + ("auth", auth)("min", permission_level{link.account, *linked_permission_name}) ); + } + + void authorization_manager::check_unlinkauth_authorization( const unlinkauth& unlink, + const vector& auths + )const + { + EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception, + "unlink action should only have one declared authorization" ); + const auto& auth = auths[0]; + EOS_ASSERT( auth.actor == unlink.account, irrelevant_auth_exception, + "the owner of the linked permission needs to be the actor of the declared authorization" ); + + const auto unlinked_permission_name = lookup_linked_permission(unlink.account, unlink.code, unlink.type); + EOS_ASSERT( unlinked_permission_name.valid(), transaction_exception, + "cannot unlink non-existent permission link of account '${account}' for actions matching '${code}::${action}'", + ("account", unlink.account)("code", unlink.code)("action", unlink.type) ); + + if( *unlinked_permission_name == config::eosio_any_name ) + return; + + EOS_ASSERT( get_permission(auth).satisfies( get_permission({unlink.account, *unlinked_permission_name}), + _db.get_index().indices() ), + irrelevant_auth_exception, + "unlink action declares irrelevant authority '${auth}'; minimum authority is ${min}", + ("auth", auth)("min", permission_level{unlink.account, *unlinked_permission_name}) ); + } + + fc::microseconds authorization_manager::check_canceldelay_authorization( const canceldelay& cancel, + const vector& auths + )const + { + EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception, + "canceldelay action should only have one declared authorization" ); + const auto& auth = auths[0]; + + EOS_ASSERT( get_permission(auth).satisfies( get_permission(cancel.canceling_auth), + _db.get_index().indices() ), + irrelevant_auth_exception, + "canceldelay action declares irrelevant authority '${auth}'; specified authority to satisfy is ${min}", + ("auth", auth)("min", cancel.canceling_auth) ); + + const auto& trx_id = cancel.trx_id; + + const auto& generated_transaction_idx = _control.db().get_index(); + const auto& generated_index = generated_transaction_idx.indices().get(); + const auto& itr = generated_index.lower_bound(trx_id); + FC_ASSERT( itr != generated_index.end() && itr->sender == account_name() && itr->trx_id == trx_id, + "cannot cancel trx_id=${tid}, there is no deferred transaction with that transaction id", + ("tid", trx_id) ); + + auto trx = fc::raw::unpack(itr->packed_trx.data(), itr->packed_trx.size()); + bool found = false; + for( const auto& act : trx.actions ) { + for( const auto& auth : act.authorization ) { + if( auth == cancel.canceling_auth ) { + found = true; + break; + } + } + if( found ) break; + } + + EOS_ASSERT( found, action_validate_exception, + "canceling_auth in canceldelay action was not found as authorization in the original delayed transaction" ); + + return (itr->delay_until - itr->published); + } + + void noop_checktime( uint32_t ) {} + + std::function authorization_manager::_noop_checktime{std::bind(&noop_checktime, std::placeholders::_1)}; + + void + authorization_manager::check_authorization( const vector& actions, + const flat_set& provided_keys, + const flat_set& provided_permissions, + fc::microseconds provided_delay, + const std::function& _checktime, + bool allow_unused_keys + )const + { + const auto& checktime = ( static_cast(_checktime) ? _checktime : _noop_checktime ); + + auto delay_max_limit = fc::seconds( _control.get_global_properties().configuration.max_transaction_delay ); + + auto effective_provided_delay = (provided_delay >= delay_max_limit) ? fc::microseconds::maximum() : provided_delay; + + auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, + _control.get_global_properties().configuration.max_authority_depth, + provided_keys, + provided_permissions, + effective_provided_delay, + checktime + ); + + map permissions_to_satisfy; + + for( const auto& act : actions ) { + bool special_case = false; + fc::microseconds delay = effective_provided_delay; + + if( act.account == config::system_account_name ) { + special_case = true; + + if( act.name == updateauth::get_name() ) { + check_updateauth_authorization( act.data_as(), act.authorization ); + } else if( act.name == deleteauth::get_name() ) { + check_deleteauth_authorization( act.data_as(), act.authorization ); + } else if( act.name == linkauth::get_name() ) { + check_linkauth_authorization( act.data_as(), act.authorization ); + } else if( act.name == unlinkauth::get_name() ) { + check_unlinkauth_authorization( act.data_as(), act.authorization ); + } else if( act.name == canceldelay::get_name() ) { + delay = std::max( delay, check_canceldelay_authorization(act.data_as(), act.authorization) ); + } else { + special_case = false; + } + } + + for( const auto& declared_auth : act.authorization ) { + + checktime( config::base_check_authorization_cpu_per_authorization ); + + if( !special_case ) { + auto min_permission_name = lookup_minimum_permission(declared_auth.actor, act.account, act.name); + if( min_permission_name ) { // since special cases were already handled, it should only be false if the permission is eosio.any + const auto& min_permission = get_permission({declared_auth.actor, *min_permission_name}); + EOS_ASSERT( get_permission(declared_auth).satisfies( min_permission, + _db.get_index().indices() ), + irrelevant_auth_exception, + "action declares irrelevant authority '${auth}'; minimum authority is ${min}", + ("auth", declared_auth)("min", permission_level{min_permission.owner, min_permission.name}) ); + } + } + + auto res = permissions_to_satisfy.emplace( declared_auth, delay ); + if( !res.second && res.first->second > delay) { // if the declared_auth was already in the map and with a higher delay + res.first->second = delay; + } + } + } + + // Now verify that all the declared authorizations are satisfied: + + // Although this can be made parallel (especially for input transactions) with the optimistic assumption that the + // CPU limit is not reached, because of the CPU limit the protocol must officially specify a sequential algorithm + // for checking the set of declared authorizations. + // The permission_levels are traversed in ascending order, which is: + // ascending order of the actor name with ties broken by ascending order of the permission name. + for( const auto& p : permissions_to_satisfy ) { + checktime( config::base_authority_checker_cpu_per_permission ); // TODO: this should eventually move into authority_checker instead + EOS_ASSERT( checker.satisfied( p.first, p.second ), unsatisfied_authorization, + "transaction declares authority '${auth}', " + "but does not have signatures for it under a provided delay of ${provided_delay} ms", + ("auth", p.first)("provided_delay", provided_delay.count()/1000) + ("delay_max_limit_ms", delay_max_limit.count()/1000) + ); + + } + + if( !allow_unused_keys ) { + EOS_ASSERT( checker.all_keys_used(), tx_irrelevant_sig, + "transaction bears irrelevant signatures from these keys: ${keys}", + ("keys", checker.unused_keys()) ); + } + } + + void + authorization_manager::check_authorization( account_name account, + permission_name permission, + const flat_set& provided_keys, + const flat_set& provided_permissions, + fc::microseconds provided_delay, + const std::function& _checktime, + bool allow_unused_keys + )const + { + const auto& checktime = ( static_cast(_checktime) ? _checktime : _noop_checktime ); + + auto delay_max_limit = fc::seconds( _control.get_global_properties().configuration.max_transaction_delay ); + + auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, + _control.get_global_properties().configuration.max_authority_depth, + provided_keys, + provided_permissions, + ( provided_delay >= delay_max_limit ) ? fc::microseconds::maximum() : provided_delay, + checktime + ); + + EOS_ASSERT( checker.satisfied({account, permission}), unsatisfied_authorization, + "permission '${auth}' was not satisfied under a provided delay of ${provided_delay} ms", + ("auth", permission_level{account, permission})("provided_delay", provided_delay.count()/1000) ); + + if( !allow_unused_keys ) { + EOS_ASSERT( checker.all_keys_used(), tx_irrelevant_sig, + "irrelevant keys provided: ${keys}", + ("keys", checker.unused_keys()) ); + } + } + + flat_set authorization_manager::get_required_keys( const transaction& trx, + const flat_set& candidate_keys, + fc::microseconds provided_delay + )const + { + auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, + _control.get_global_properties().configuration.max_authority_depth, + candidate_keys, + {}, + provided_delay, + _noop_checktime + ); + + for (const auto& act : trx.actions ) { + for (const auto& declared_auth : act.authorization) { + EOS_ASSERT( checker.satisfied(declared_auth), unsatisfied_authorization, + "transaction declares authority '${auth}', but does not have signatures for it.", + ("auth", declared_auth) ); + } + } + + return checker.used_keys(); + } + +} } /// namespace eosio::chain diff --git a/libraries/chain/block.cpp b/libraries/chain/block_header.cpp similarity index 57% rename from libraries/chain/block.cpp rename to libraries/chain/block_header.cpp index 6a7252fe48e..8ba95b705e2 100644 --- a/libraries/chain/block.cpp +++ b/libraries/chain/block_header.cpp @@ -19,28 +19,14 @@ namespace eosio { namespace chain { return fc::endian_reverse_u32(id._hash[0]); } - block_id_type signed_block_header::id()const + block_id_type block_header::id()const { // Do not include signed_block_header attributes in id, specifically exclude producer_signature. - block_id_type result = fc::sha256::hash(*static_cast(this)); + block_id_type result = digest(); //fc::sha256::hash(*static_cast(this)); result._hash[0] &= 0xffffffff00000000; result._hash[0] += fc::endian_reverse_u32(block_num()); // store the block num in the ID, 160 bits is plenty for the hash return result; } - public_key_type signed_block_header::signee()const - { - return fc::crypto::public_key(producer_signature, digest(), true/*enforce canonical*/); - } - - void signed_block_header::sign(const private_key_type& signer) - { - producer_signature = signer.sign(digest()); - } - - bool signed_block_header::validate_signee(const public_key_type& expected_signee)const - { - return signee() == expected_signee; - } } } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp new file mode 100644 index 00000000000..172b22b0940 --- /dev/null +++ b/libraries/chain/block_header_state.cpp @@ -0,0 +1,258 @@ +#include +#include +#include + +namespace eosio { namespace chain { + + /* + uint32_t block_header_state::calc_dpos_last_irreversible()const { + if( producer_to_last_produced.size() == 0 ) + return 0; + + vector irb; + irb.reserve( producer_to_last_produced.size() ); + for( const auto& item : producer_to_last_produced ) + irb.push_back(item.second); + + size_t offset = EOS_PERCENT(irb.size(), config::percent_100- config::irreversible_threshold_percent); + std::nth_element( irb.begin(), irb.begin() + offset, irb.end() ); + + return irb[offset]; + } + */ + + bool block_header_state::is_active_producer( account_name n )const { + return producer_to_last_produced.find(n) != producer_to_last_produced.end(); + } + + /* + block_timestamp_type block_header_state::get_slot_time( uint32_t slot_num )const { + auto t = header.timestamp; + FC_ASSERT( std::numeric_limits::max() - t.slot >= slot_num, "block timestamp overflow" ); + t.slot += slot_num; + return t; + } + + uint32_t block_header_state::get_slot_at_time( block_timestamp_type t )const { + auto first_slot_time = get_slot_time(1); + if( t < first_slot_time ) + return 0; + return (t.slot - first_slot_time.slot + 1); + } + + producer_key block_header_state::get_scheduled_producer( uint32_t slot_num )const { + return get_scheduled_producer( get_slot_time(slot_num) ); + } + */ + + producer_key block_header_state::get_scheduled_producer( block_timestamp_type t )const { + auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions); + index /= config::producer_repetitions; + return active_schedule.producers[index]; + } + + /* + uint32_t block_header_state::producer_participation_rate()const + { + return static_cast(config::percent_100); // Ignore participation rate for now until we construct a better metric. + } + */ + + + /** + * Generate a template block header state for a given block time, it will not + * contain a transaction mroot, action mroot, or new_producers as those components + * are derived from chain state. + */ + block_header_state block_header_state::generate_next( block_timestamp_type when )const { + block_header_state result; + + if( when != block_timestamp_type() ) { + FC_ASSERT( when > header.timestamp, "next block must be in the future" ); + } else { + (when = header.timestamp).slot++; + } + result.header.timestamp = when; + result.header.previous = id; + result.header.schedule_version = active_schedule.version; + + auto prokey = get_scheduled_producer(when); + result.block_signing_key = prokey.block_signing_key; + result.header.producer = prokey.producer_name; + + result.pending_schedule_lib_num = pending_schedule_lib_num; + result.pending_schedule_hash = pending_schedule_hash; + result.block_num = block_num + 1; + result.producer_to_last_produced = producer_to_last_produced; + result.producer_to_last_produced[prokey.producer_name] = result.block_num; + result.blockroot_merkle = blockroot_merkle; + result.blockroot_merkle.append( id ); + + auto block_mroot = result.blockroot_merkle.get_root(); + + result.active_schedule = active_schedule; + result.pending_schedule = pending_schedule; + result.dpos_irreversible_blocknum = dpos_irreversible_blocknum; + result.bft_irreversible_blocknum = bft_irreversible_blocknum; + + if( result.pending_schedule.producers.size() && + result.dpos_irreversible_blocknum >= pending_schedule_lib_num ) { + result.active_schedule = move( result.pending_schedule ); + + flat_map new_producer_to_last_produced; + for( const auto& pro : result.active_schedule.producers ) { + auto existing = producer_to_last_produced.find( pro.producer_name ); + if( existing != producer_to_last_produced.end() ) { + new_producer_to_last_produced[pro.producer_name] = existing->second; + } else { + new_producer_to_last_produced[pro.producer_name] = result.dpos_irreversible_blocknum; + } + } + result.producer_to_last_produced = move( new_producer_to_last_produced ); + result.producer_to_last_produced[prokey.producer_name] = result.block_num; + } + + /// grow the confirmed count + static_assert(std::numeric_limits::max() >= (config::max_producers * 2 / 3) + 1, "8bit confirmations may not be able to hold all of the needed confirmations"); + + // This uses the previous block active_schedule because thats the "schedule" that signs and therefore confirms _this_ block + auto num_active_producers = active_schedule.producers.size(); + uint32_t required_confs = (uint32_t)(num_active_producers * 2 / 3) + 1; + + if( confirm_count.size() < config::maximum_tracked_dpos_confirmations ) { + result.confirm_count.reserve( confirm_count.size() + 1 ); + result.confirm_count = confirm_count; + result.confirm_count.resize( confirm_count.size() + 1 ); + result.confirm_count.back() = (uint8_t)required_confs; + } else { + result.confirm_count.resize( confirm_count.size() ); + memcpy( &result.confirm_count[0], &confirm_count[1], confirm_count.size() - 1 ); + result.confirm_count.back() = (uint8_t)required_confs; + } + + return result; + } /// generate_next + + + void block_header_state::set_new_producers( producer_schedule_type pending ) { + FC_ASSERT( pending.version == active_schedule.version + 1, "wrong producer schedule version specified" ); + FC_ASSERT( pending_schedule.producers.size() == 0, + "cannot set new pending producers until last pending is confirmed" ); + header.new_producers = move(pending); + pending_schedule_hash = digest_type::hash( *header.new_producers ); + pending_schedule = *header.new_producers; + pending_schedule_lib_num = block_num; + } + + + /** + * Transitions the current header state into the next header state given the supplied signed block header. + * + * Given a signed block header, generate the expected template based upon the header time, + * then validate that the provided header matches the template. + * + * If the header specifies new_producers then apply them accordingly. + */ + block_header_state block_header_state::next( const signed_block_header& h )const { + FC_ASSERT( h.timestamp != block_timestamp_type(), "", ("h",h) ); + FC_ASSERT( h.header_extensions.size() == 0, "no supported extensions" ); + + FC_ASSERT( h.timestamp > header.timestamp, "block must be later in time" ); + FC_ASSERT( h.previous == id, "block must link to current state" ); + auto result = generate_next( h.timestamp ); + FC_ASSERT( result.header.producer == h.producer, "wrong producer specified" ); + FC_ASSERT( result.header.schedule_version == h.schedule_version, "schedule_version in signed block is corrupted" ); + + auto itr = producer_to_last_produced.find(h.producer); + if( itr != producer_to_last_produced.end() ) { + FC_ASSERT( itr->second <= result.block_num - h.confirmed, "producer double-confirming known range" ); + } + + // FC_ASSERT( result.header.block_mroot == h.block_mroot, "mismatch block merkle root" ); + + /// below this point is state changes that cannot be validated with headers alone, but never-the-less, + /// must result in header state changes + if( h.new_producers ) { + result.set_new_producers( *h.new_producers ); + } + + result.set_confirmed( h.confirmed ); + + // idump( (result.confirm_count.size()) ); + + result.header.action_mroot = h.action_mroot; + result.header.transaction_mroot = h.transaction_mroot; + result.header.producer_signature = h.producer_signature; + //idump((result.header)); + result.id = result.header.id(); + + FC_ASSERT( result.block_signing_key == result.signee(), "block not signed by expected key", + ("result.block_signing_key", result.block_signing_key)("signee", result.signee() ) ); + + return result; + } /// next + + void block_header_state::set_confirmed( uint16_t num_prev_blocks ) { + /* + idump((num_prev_blocks)(confirm_count.size())); + + for( uint32_t i = 0; i < confirm_count.size(); ++i ) { + std::cerr << "confirm_count["<= 0 && blocks_to_confirm ) { + --confirm_count[i]; + //idump((confirm_count[i])); + if( confirm_count[i] == 0 ) + { + uint32_t block_num_for_i = block_num - (uint32_t)(confirm_count.size() - 1 - i); + dpos_irreversible_blocknum = block_num_for_i; + //idump((dpos2_lib)(block_num)(dpos_irreversible_blocknum)); + + if (i == confirm_count.size() - 1) { + confirm_count.resize(0); + } else { + memmove( &confirm_count[0], &confirm_count[i + 1], confirm_count.size() - i - 1); + confirm_count.resize( confirm_count.size() - i - 1 ); + } + + return; + } + --i; + --blocks_to_confirm; + } + } + + digest_type block_header_state::sig_digest()const { + auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) ); + return digest_type::hash( std::make_pair(header_bmroot, pending_schedule_hash) ); + } + + void block_header_state::sign( const std::function& signer ) { + auto d = sig_digest(); + header.producer_signature = signer( d ); + FC_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ) ); + } + + public_key_type block_header_state::signee()const { + return fc::crypto::public_key( header.producer_signature, sig_digest(), true ); + } + + void block_header_state::add_confirmation( const header_confirmation& conf ) { + for( const auto& c : confirmations ) + FC_ASSERT( c.producer != conf.producer, "block already confirmed by this producer" ); + + auto key = active_schedule.get_producer_key( conf.producer ); + FC_ASSERT( key != public_key_type(), "producer not in current schedule" ); + auto signer = fc::crypto::public_key( conf.producer_signature, sig_digest(), true ); + FC_ASSERT( signer == key, "confirmation not signed by expected key" ); + + confirmations.emplace_back( conf ); + } + + +} } /// namespace eosio::chain diff --git a/libraries/chain/block_log.cpp b/libraries/chain/block_log.cpp index 64cf78117b1..3b2586840b1 100644 --- a/libraries/chain/block_log.cpp +++ b/libraries/chain/block_log.cpp @@ -14,7 +14,7 @@ namespace eosio { namespace chain { namespace detail { class block_log_impl { public: - optional head; + signed_block_ptr head; block_id_type head_id; std::fstream block_stream; std::fstream index_stream; @@ -120,6 +120,7 @@ namespace eosio { namespace chain { ilog("Log is nonempty"); my->head = read_head(); my->head_id = my->head->id(); + edump((my->head->block_num())); if (index_size) { my->check_block_read(); @@ -154,22 +155,22 @@ namespace eosio { namespace chain { } } - uint64_t block_log::append(const signed_block& b) { + uint64_t block_log::append(const signed_block_ptr& b) { try { my->check_block_write(); my->check_index_write(); uint64_t pos = my->block_stream.tellp(); - FC_ASSERT(my->index_stream.tellp() == sizeof(uint64_t) * (b.block_num() - 1), + FC_ASSERT(my->index_stream.tellp() == sizeof(uint64_t) * (b->block_num() - 1), "Append to index file occuring at wrong position.", ("position", (uint64_t) my->index_stream.tellp()) - ("expected", (b.block_num() - 1) * sizeof(uint64_t))); - auto data = fc::raw::pack(b); + ("expected", (b->block_num() - 1) * sizeof(uint64_t))); + auto data = fc::raw::pack(*b); my->block_stream.write(data.data(), data.size()); my->block_stream.write((char*)&pos, sizeof(pos)); my->index_stream.write((char*)&pos, sizeof(pos)); my->head = b; - my->head_id = b.id(); + my->head_id = b->id(); return pos; } @@ -181,19 +182,20 @@ namespace eosio { namespace chain { my->index_stream.flush(); } - std::pair block_log::read_block(uint64_t pos)const { + std::pair block_log::read_block(uint64_t pos)const { my->check_block_read(); my->block_stream.seekg(pos); - std::pair result; - fc::raw::unpack(my->block_stream, result.first); + std::pair result; + result.first = std::make_shared(); + fc::raw::unpack(my->block_stream, *result.first); result.second = uint64_t(my->block_stream.tellg()) + 8; return result; } - optional block_log::read_block_by_num(uint32_t block_num)const { + signed_block_ptr block_log::read_block_by_num(uint32_t block_num)const { try { - optional b; + signed_block_ptr b; uint64_t pos = get_block_pos(block_num); if (pos != npos) { b = read_block(pos).first; @@ -207,7 +209,7 @@ namespace eosio { namespace chain { uint64_t block_log::get_block_pos(uint32_t block_num) const { my->check_index_read(); - if (!(my->head.valid() && block_num <= block_header::num_from_id(my->head_id) && block_num > 0)) + if (!(my->head && block_num <= block_header::num_from_id(my->head_id) && block_num > 0)) return npos; my->index_stream.seekg(sizeof(uint64_t) * (block_num - 1)); uint64_t pos; @@ -215,7 +217,7 @@ namespace eosio { namespace chain { return pos; } - optional block_log::read_head()const { + signed_block_ptr block_log::read_head()const { my->check_block_read(); uint64_t pos; @@ -230,7 +232,7 @@ namespace eosio { namespace chain { return read_block(pos).first; } - const optional& block_log::head()const { + const signed_block_ptr& block_log::head()const { return my->head; } @@ -251,10 +253,11 @@ namespace eosio { namespace chain { my->block_stream.seekg(pos); - while (pos < end_pos) { + while( pos < end_pos ) { fc::raw::unpack(my->block_stream, tmp); my->block_stream.read((char*)&pos, sizeof(pos)); my->index_stream.write((char*)&pos, sizeof(pos)); } - } -} } + } // construct_index + +} } /// eosio::chain diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp new file mode 100644 index 00000000000..c85fe4502a0 --- /dev/null +++ b/libraries/chain/block_state.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace eosio { namespace chain { + + block_state::block_state( const block_header_state& prev, block_timestamp_type when ) + :block_header_state( prev.generate_next( when ) ), + block( std::make_shared() ) + { + static_cast(*block) = header; + } + + block_state::block_state( const block_header_state& prev, signed_block_ptr b ) + :block_header_state( prev.next( *b )), block( move(b) ) + { } + + + +} } /// eosio::chain diff --git a/libraries/chain/block_trace.cpp b/libraries/chain/block_trace.cpp deleted file mode 100644 index 70d132cd5a5..00000000000 --- a/libraries/chain/block_trace.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include -#include -#include - -namespace eosio { namespace chain { - - void shard_trace::finalize_shard() { - { - static const size_t GUESS_ACTS_PER_TX = 10; - vector action_roots; - cpu_usage = 0; - action_roots.reserve(transaction_traces.size() * GUESS_ACTS_PER_TX); - for (const auto& tx :transaction_traces) { - for (const auto& at: tx.action_traces) { - digest_type::encoder enc; - uint64_t region_id = tx.region_id; - uint64_t cycle_index = tx.cycle_index; - - fc::raw::pack(enc, at.receiver); - fc::raw::pack(enc, at.act.account); - fc::raw::pack(enc, at.act.name); - fc::raw::pack(enc, at.act.data); - fc::raw::pack(enc, region_id); - fc::raw::pack(enc, cycle_index); - fc::raw::pack(enc, at.data_access); - - action_roots.emplace_back(enc.result()); - - cpu_usage += at.cpu_usage; - } - } - shard_action_root = merkle(action_roots); - } - - { - vector trx_roots; - trx_roots.reserve(transaction_traces.size()); - for( uint64_t trx_index = 0, num_trxs = transaction_traces.size(); trx_index < num_trxs; ++trx_index ) { - const auto& tx = transaction_traces[trx_index]; - digest_type::encoder enc; - uint64_t region_id = tx.region_id; - uint64_t cycle_index = tx.cycle_index; - uint64_t shard_index = tx.shard_index; - fc::raw::pack( enc, region_id ); // Technically redundant since it is included in the trx header, but can still be useful here. - fc::raw::pack( enc, cycle_index ); - fc::raw::pack( enc, shard_index ); - fc::raw::pack( enc, trx_index ); - fc::raw::pack( enc, *static_cast(&tx) ); - if( tx.packed_trx_digest.valid() ) { - fc::raw::pack( enc, *tx.packed_trx_digest ); - } - trx_roots.emplace_back(enc.result()); - } - shard_transaction_root = merkle(trx_roots); - } - - } - - digest_type block_trace::calculate_action_merkle_root()const { - vector merkle_root_of_each_region; // Extra level of indirection is to allow parallel computation of region merkle roots - for(const auto& rt: region_traces ) { - vector merkle_list_for_region; - merkle_list_for_region.reserve(64); - for(const auto& ct: rt.cycle_traces ) { - for(const auto& st: ct.shard_traces) { - merkle_list_for_region.emplace_back(st.shard_action_root); - } - } - merkle_root_of_each_region.emplace_back( merkle(std::move(merkle_list_for_region)) ); - } - return merkle( std::move(merkle_root_of_each_region) ); - } - - uint64_t block_trace::calculate_cpu_usage() const { - uint64_t cpu_usage = 0; - for(const auto& rt: region_traces ) { - for(const auto& ct: rt.cycle_traces ) { - for(const auto& st: ct.shard_traces) { - cpu_usage += st.cpu_usage; - } - } - } - - return cpu_usage; - } - - digest_type block_trace::calculate_transaction_merkle_root()const { - vector merkle_root_of_each_region; // Extra level of indirection is to allow parallel computation of region merkle roots - for( const auto& rt : region_traces ) { - vector merkle_list_for_region; - for( const auto& ct : rt.cycle_traces ) { - for( const auto& st: ct.shard_traces ) { - merkle_list_for_region.emplace_back(st.shard_transaction_root); - } - } - merkle_root_of_each_region.emplace_back( merkle(std::move(merkle_list_for_region)) ); - } - return merkle( std::move(merkle_root_of_each_region) ); - } - -} } diff --git a/libraries/chain/chain_config.cpp b/libraries/chain/chain_config.cpp deleted file mode 100644 index 8d06e917b7d..00000000000 --- a/libraries/chain/chain_config.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include - -#include - -#include - -namespace eosio { namespace chain { - - -template -struct properties_median_calculator_visitor { - properties_median_calculator_visitor (T& medians, Range votes) - : medians(medians), votes(votes) - {} - - template - void operator() (const char*) const { - auto median_itr = boost::begin(votes) + boost::distance(votes)/2; - boost::nth_element(votes, median_itr, [](const T& a, const T& b) { return a.*member < b.*member; }); - medians.*member = (*median_itr).*member; - } - - T& medians; - mutable Range votes; -}; - - -template -properties_median_calculator_visitor get_median_properties_calculator(T& medians, Range&& votes) { - return properties_median_calculator_visitor(medians, std::move(votes)); -} - -chain_config chain_config::get_median_values( vector votes) { - chain_config results; - fc::reflector::visit(get_median_properties_calculator(results, std::move(votes))); - return results; -} - -template -struct comparison_visitor { - const T& a; - const T& b; - - template - void operator() (const char*) const { - if (a.*member != b.*member) - // Throw to stop comparing fields: the structs are unequal - throw false; - } -}; - -bool operator==(const chain_config& a, const chain_config& b) { - // Yes, it's gross, I'm using a boolean exception to direct normal control flow... that's why it's buried deep in an - // implementation detail file. I think it's worth it for the generalization, though: this code keeps working no - // matter what updates happen to chain_config - // - // TODO: this hack gives us short circuit evaluation when we could just use &= on a mutable variable and check at the - // end, but that would require visiting all members rather than aborting on first fail. - if (&a != &b) { - try { - fc::reflector::visit(comparison_visitor{a, b}); - } catch (bool) { - return false; - } - } - return true; -} - -} } // namespace eosio::chain diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp deleted file mode 100644 index 98e4975ff8b..00000000000 --- a/libraries/chain/chain_controller.cpp +++ /dev/null @@ -1,2367 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace eosio { namespace chain { - -bool chain_controller::is_start_of_round( block_num_type block_num )const { - return 0 == (block_num % blocks_per_round()); -} - -uint32_t chain_controller::blocks_per_round()const { - return get_global_properties().active_producers.producers.size()*config::producer_repetitions; -} - -chain_controller::chain_controller( const chain_controller::controller_config& cfg ) -:_db( cfg.shared_memory_dir, - (cfg.read_only ? database::read_only : database::read_write), - cfg.shared_memory_size), - _block_log(cfg.block_log_dir), - _wasm_interface(cfg.wasm_runtime), - _limits(cfg.limits), - _resource_limits(_db) -{ - _initialize_indexes(); - _resource_limits.initialize_database(); - - - for (auto& f : cfg.applied_block_callbacks) - applied_block.connect(f); - for (auto& f : cfg.applied_irreversible_block_callbacks) - applied_irreversible_block.connect(f); - for (auto& f : cfg.on_pending_transaction_callbacks) - on_pending_transaction.connect(f); - - contracts::chain_initializer starter(cfg.genesis); - starter.register_types(*this, _db); - - // Behave as though we are applying a block during chain initialization (it's the genesis block!) - with_applying_block([&] { - _initialize_chain(starter); - }); - - _spinup_db(); - _spinup_fork_db(); - - if (_block_log.read_head() && head_block_num() < _block_log.read_head()->block_num()) - replay(); -} /// chain_controller::chain_controller - - -chain_controller::~chain_controller() { - clear_pending(); - _db.flush(); -} - -bool chain_controller::is_known_block(const block_id_type& id)const -{ - return _fork_db.is_known_block(id) || _block_log.read_block_by_id(id); -} -/** - * Only return true *if* the transaction has not expired or been invalidated. If this - * method is called with a VERY old transaction we will return false, they should - * query things by blocks if they are that old. - */ -bool chain_controller::is_known_transaction(const transaction_id_type& id)const -{ - const auto& trx_idx = _db.get_index(); - return trx_idx.find( id ) != trx_idx.end(); -} - -block_id_type chain_controller::get_block_id_for_num(uint32_t block_num)const -{ try { - if (const auto& block = fetch_block_by_number(block_num)) - return block->id(); - - FC_THROW_EXCEPTION(unknown_block_exception, "Could not find block"); -} FC_CAPTURE_AND_RETHROW((block_num)) } - -optional chain_controller::fetch_block_by_id(const block_id_type& id)const -{ - auto b = _fork_db.fetch_block(id); - if(b) return b->data; - return _block_log.read_block_by_id(id); -} - -optional chain_controller::fetch_block_by_number(uint32_t num)const -{ - if (const auto& block = _block_log.read_block_by_num(num)) - return *block; - - // Not in _block_log, so it must be since the last irreversible block. Grab it from _fork_db instead - if (num <= head_block_num()) { - auto block = _fork_db.head(); - while (block && block->num > num) - block = block->prev.lock(); - if (block && block->num == num) - return block->data; - } - - return optional(); -} - -std::vector chain_controller::get_block_ids_on_fork(block_id_type head_of_fork) const -{ - pair branches = _fork_db.fetch_branch_from(head_block_id(), head_of_fork); - if( !((branches.first.back()->previous_id() == branches.second.back()->previous_id())) ) - { - edump( (head_of_fork) - (head_block_id()) - (branches.first.size()) - (branches.second.size()) ); - assert(branches.first.back()->previous_id() == branches.second.back()->previous_id()); - } - std::vector result; - for (const item_ptr& fork_block : branches.second) - result.emplace_back(fork_block->id); - result.emplace_back(branches.first.back()->previous_id()); - return result; -} - - -/** - * Push block "may fail" in which case every partial change is unwound. After - * push block is successful the block is appended to the chain database on disk. - * - * @return true if we switched forks as a result of this push. - */ -void chain_controller::push_block(const signed_block& new_block, uint32_t skip) -{ try { - with_skip_flags( skip, [&](){ - return without_pending_transactions( [&]() { - return _db.with_write_lock( [&]() { - return _push_block(new_block); - } ); - }); - }); - ilog( "push block #${n} from ${pro} ${time} ${id} lib: ${l} success", ("n",new_block.block_num())("pro",name(new_block.producer))("time",new_block.timestamp)("id",new_block.id())("l",last_irreversible_block_num())); -} FC_CAPTURE_AND_RETHROW((new_block)) } - -bool chain_controller::_push_block(const signed_block& new_block) -{ try { - uint32_t skip = _skip_flags; - if (!(skip&skip_fork_db)) { - if(new_block.block_num() > head_block_num()) { - if(!is_start_of_round(new_block.block_num())) { - auto schedule = get_global_properties().active_producers.producers; - if(std::find(schedule.begin(), schedule.end(), - producer_key{new_block.producer, new_block.signee()}) == schedule.end()) { - return false; // Not forking and not pushing block with unexpected signature - } - } - } - shared_ptr new_head = _fork_db.push_block(new_block); - //If the head block from the longest chain does not build off of the current head, we need to switch forks. - if (new_head->data.previous != head_block_id()) { - //If the newly pushed block is the same height as head, we get head back in new_head - //Only switch forks if new_head is actually higher than head - if (new_head->data.block_num() > head_block_num()) { - wlog("Switching to fork: ${id}", ("id",new_head->data.id())); - auto branches = _fork_db.fetch_branch_from(new_head->data.id(), head_block_id()); - - // pop blocks until we hit the forked block - while (head_block_id() != branches.second.back()->data.previous) - pop_block(); - - // push all blocks on the new fork - for (auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr) { - ilog("pushing blocks from fork ${n} ${id}", ("n",(*ritr)->data.block_num())("id",(*ritr)->data.id())); - { - uint32_t delta = 0; - if (ritr != branches.first.rbegin()) { - delta = (*ritr)->data.timestamp.slot - (*std::prev(ritr))->data.timestamp.slot; - } else { - optional prev = fetch_block_by_id((*ritr)->data.previous); - if (prev) - delta = (*ritr)->data.timestamp.slot - prev->timestamp.slot; - } - if (delta > 1) - wlog("Number of missed blocks: ${num}", ("num", delta-1)); - } - optional except; - try { - auto session = _db.start_undo_session(true); - _apply_block((*ritr)->data, skip); - session.push(); - } - catch (const fc::exception& e) { except = e; } - if (except) { - wlog("exception thrown while switching forks ${e}", ("e",except->to_detail_string())); - // remove the rest of branches.first from the fork_db, those blocks are invalid - while (ritr != branches.first.rend()) { - _fork_db.remove((*ritr)->data.id()); - ++ritr; - } - _fork_db.set_head(branches.second.front()); - - // pop all blocks from the bad fork - while (head_block_id() != branches.second.back()->data.previous) - pop_block(); - - // restore all blocks from the good fork - for (auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr) { - auto session = _db.start_undo_session(true); - _apply_block((*ritr)->data, skip); - session.push(); - } - throw *except; - } - } - return true; //swithced fork - } - else return false; // didn't switch fork - } - } - - try { - auto session = _db.start_undo_session(true); - _apply_block(new_block, skip); - session.push(); - } catch ( const fc::exception& e ) { - elog("Failed to push new block:\n${e}", ("e", e.to_detail_string())); - _fork_db.pop_block(); - _fork_db.remove(new_block.id()); - throw; - } - - return false; -} FC_CAPTURE_AND_RETHROW((new_block)) } - -/** - * Attempts to push the transaction into the pending queue - * - * When called to push a locally generated transaction, set the skip_block_size_check bit on the skip argument. This - * will allow the transaction to be pushed even if it causes the pending block size to exceed the maximum block size. - * Although the transaction will probably not propagate further now, as the peers are likely to have their pending - * queues full as well, it will be kept in the queue to be propagated later when a new block flushes out the pending - * queues. - */ -transaction_trace chain_controller::push_transaction(const packed_transaction& trx, uint32_t skip) -{ try { - // If this is the first transaction pushed after applying a block, start a new undo session. - // This allows us to quickly rewind to the clean state of the head block, in case a new block arrives. - if( !_pending_block ) { - _start_pending_block(); - } - - return with_skip_flags(skip, [&]() { - return _db.with_write_lock([&]() { - return _push_transaction(trx); - }); - }); -} EOS_CAPTURE_AND_RETHROW( transaction_exception ) } - -transaction_trace chain_controller::_push_transaction(const packed_transaction& packed_trx) -{ try { - //edump((transaction_header(packed_trx.get_transaction()))); - auto start = fc::time_point::now(); - transaction_metadata mtrx( packed_trx, get_chain_id(), head_block_time()); - //idump((transaction_header(mtrx.trx()))); - - const transaction& trx = mtrx.trx(); - mtrx.delay = fc::seconds(trx.delay_sec); - - validate_transaction_with_minimal_state( trx, mtrx.billable_packed_size ); - validate_expiration_not_too_far(trx, head_block_time() + mtrx.delay); - validate_referenced_accounts(trx); - validate_uniqueness(trx); - if( should_check_authorization() ) { - auto enforced_delay = check_transaction_authorization(trx, packed_trx.signatures, mtrx.context_free_data); - auto max_delay = fc::seconds( get_global_properties().configuration.max_transaction_delay ); - if ( max_delay < enforced_delay ) { - enforced_delay = max_delay; - } - EOS_ASSERT( mtrx.delay >= enforced_delay, - transaction_exception, - "authorization imposes a delay (${enforced_delay} sec) greater than the delay specified in transaction header (${specified_delay} sec)", - ("enforced_delay", enforced_delay.to_seconds())("specified_delay", mtrx.delay.to_seconds()) ); - } - - auto setup_us = fc::time_point::now() - start; - - transaction_trace result(mtrx.id); - - if( mtrx.delay.count() == 0 ) { - result = _push_transaction( std::move(mtrx) ); - } else { - - result = wrap_transaction_processing( std::move(mtrx), - [this](transaction_metadata& meta) { return delayed_transaction_processing(meta); } ); - } - - // notify anyone listening to pending transactions - on_pending_transaction(_pending_transaction_metas.back(), packed_trx); - - _pending_block->input_transactions.emplace_back(packed_trx); - - result._setup_profiling_us = setup_us; - return result; - -} FC_CAPTURE_AND_RETHROW( (transaction_header(packed_trx.get_transaction())) ) } - -transaction_trace chain_controller::_push_transaction( transaction_metadata&& data ) -{ try { - auto process_apply_transaction = [this](transaction_metadata& meta) { - auto cyclenum = _pending_block->regions.back().cycles_summary.size() - 1; - //wdump((transaction_header(meta.trx()))); - - const auto& trx = meta.trx(); - - // Validate uniqueness and expiration again - validate_uniqueness(trx); - validate_not_expired(trx); - - /// TODO: move _pending_cycle into db so that it can be undone if transation fails, for now we will apply - /// the transaction first so that there is nothing to undo... this only works because things are currently - /// single threaded - // set cycle, shard, region etc - meta.region_id = 0; - meta.cycle_index = cyclenum; - meta.shard_index = 0; - return _apply_transaction( meta ); - }; - // wdump((transaction_header(data.trx()))); - return wrap_transaction_processing( move(data), process_apply_transaction ); -} FC_CAPTURE_AND_RETHROW( ) } - -uint128_t chain_controller::transaction_id_to_sender_id( const transaction_id_type& tid )const { - fc::uint128_t _id(tid._hash[3], tid._hash[2]); - return (unsigned __int128)_id; -} - -transaction_trace chain_controller::delayed_transaction_processing( const transaction_metadata& mtrx ) -{ try { - transaction_trace result(mtrx.id); - result.status = transaction_trace::delayed; - - const auto& trx = mtrx.trx(); - - time_point_sec execute_after = head_block_time(); - execute_after += mtrx.delay; - - // TODO: update to better method post RC1? - account_name payer; - for(const auto& act : trx.actions ) { - if (act.authorization.size() > 0) { - payer = act.authorization.at(0).actor; - break; - } - } - - FC_ASSERT(!payer.empty(), "Failed to find a payer for delayed transaction!"); - - update_resource_usage(result, mtrx); - - auto sender_id = transaction_id_to_sender_id( mtrx.id ); - - const auto& generated_index = _db.get_index(); - auto colliding_trx = generated_index.find(boost::make_tuple(config::system_account_name, sender_id)); - FC_ASSERT( colliding_trx == generated_index.end(), - "sender_id conflict between two delayed transactions: ${cur_trx_id} and ${prev_trx_id}", - ("cur_trx_id", mtrx.id)("prev_trx_id", colliding_trx->trx_id) ); - - deferred_transaction dtrx(sender_id, config::system_account_name, payer, execute_after, trx); - FC_ASSERT( dtrx.execute_after < dtrx.expiration, "transaction expires before it can execute" ); - - result.deferred_transaction_requests.push_back(std::move(dtrx)); - - _create_generated_transaction(result.deferred_transaction_requests[0].get()); - - return result; - -} FC_CAPTURE_AND_RETHROW( ) } - -static void record_locks_for_data_access(transaction_trace& trace, flat_set& read_locks, flat_set& write_locks ) { - // Precondition: read_locks and write_locks do not intersect. - - for (const auto& at: trace.action_traces) { - for (const auto& access: at.data_access) { - if (access.type == data_access_info::read) { - trace.read_locks.emplace(shard_lock{access.code, access.scope}); - } else { - trace.write_locks.emplace(shard_lock{access.code, access.scope}); - } - } - } - - // Step RR: Remove from trace.read_locks and from read_locks only the read locks necessary to ensure they each do not intersect with trace.write_locks. - std::for_each(trace.write_locks.begin(), trace.write_locks.end(), [&]( const shard_lock& l){ - trace.read_locks.erase(l); read_locks.erase(l); // for step RR - // write_locks.insert(l); // Step AW could instead be done here, but it would add unnecessary work to the lookups in step AR. - }); - - // At this point, the trace.read_locks and trace.write_locks are good. - - // Step AR: Add into read_locks the subset of trace.read_locks that does not intersect with write_locks (must occur after step RR). - // (Works regardless of whether step AW is done before or after this step.) - std::for_each(trace.read_locks.begin(), trace.read_locks.end(), [&]( const shard_lock& l){ - if( write_locks.find(l) == write_locks.end() ) - read_locks.insert(l); - }); - - - // Step AW: Add trace.write_locks into write_locks. - write_locks.insert(trace.write_locks.begin(), trace.write_locks.end()); - - // Postcondition: read_locks and write_locks do not intersect - // Postcondition: trace.read_locks and trace.write_locks do not intersect -} - -block_header chain_controller::head_block_header() const -{ - auto b = _fork_db.fetch_block(head_block_id()); - if( b ) return b->data; - - if (auto head_block = fetch_block_by_id(head_block_id())) - return *head_block; - return block_header(); -} - -void chain_controller::_start_pending_block( bool skip_deferred ) -{ - FC_ASSERT( !_pending_block ); - _pending_block = signed_block(); - _pending_block_trace = block_trace(*_pending_block); - _pending_block_session = _db.start_undo_session(true); - _pending_block->regions.resize(1); - _pending_block_trace->region_traces.resize(1); - - _start_pending_cycle(); - _apply_on_block_transaction(); - _finalize_pending_cycle(); - - _start_pending_cycle(); - - if ( !skip_deferred ) { - _push_deferred_transactions( false ); - if (_pending_cycle_trace && _pending_cycle_trace->shard_traces.size() > 0 && _pending_cycle_trace->shard_traces.back().transaction_traces.size() > 0) { - _finalize_pending_cycle(); - _start_pending_cycle(); - } - } -} - -transaction chain_controller::_get_on_block_transaction() -{ - action on_block_act; - on_block_act.account = config::system_account_name; - on_block_act.name = N(onblock); - on_block_act.authorization = vector{{config::system_account_name, config::active_name}}; - on_block_act.data = fc::raw::pack(head_block_header()); - - transaction trx; - trx.actions.emplace_back(std::move(on_block_act)); - trx.set_reference_block(head_block_id()); - trx.expiration = head_block_time() + fc::seconds(1); - return trx; -} - -void chain_controller::_apply_on_block_transaction() -{ - _pending_block_trace->implicit_transactions.emplace_back(_get_on_block_transaction()); - transaction_metadata mtrx(packed_transaction(_pending_block_trace->implicit_transactions.back()), get_chain_id(), head_block_time(), optional(), true /*is implicit*/); - _push_transaction(std::move(mtrx)); -} - -/** - * Wraps up all work for current shards, starts a new cycle, and - * executes any pending transactions - */ -void chain_controller::_start_pending_cycle() { - // only add a new cycle if there are no cycles or if the previous cycle isn't empty - if (_pending_block->regions.back().cycles_summary.empty() || - (!_pending_block->regions.back().cycles_summary.back().empty() && - !_pending_block->regions.back().cycles_summary.back().back().empty())) - _pending_block->regions.back().cycles_summary.resize( _pending_block->regions[0].cycles_summary.size() + 1 ); - - - _pending_cycle_trace = cycle_trace(); - - _pending_cycle_trace->shard_traces.resize(_pending_cycle_trace->shard_traces.size() + 1 ); - - auto& bcycle = _pending_block->regions.back().cycles_summary.back(); - if(bcycle.empty() || !bcycle.back().empty()) - bcycle.resize( bcycle.size()+1 ); -} - -void chain_controller::_finalize_pending_cycle() -{ - // prune empty shard - if (!_pending_block->regions.back().cycles_summary.empty() && - !_pending_block->regions.back().cycles_summary.back().empty() && - _pending_block->regions.back().cycles_summary.back().back().empty()) { - _pending_block->regions.back().cycles_summary.back().resize( _pending_block->regions.back().cycles_summary.back().size() - 1 ); - _pending_cycle_trace->shard_traces.resize(_pending_cycle_trace->shard_traces.size() - 1 ); - } - // prune empty cycle - if (!_pending_block->regions.back().cycles_summary.empty() && - _pending_block->regions.back().cycles_summary.back().empty()) { - _pending_block->regions.back().cycles_summary.resize( _pending_block->regions.back().cycles_summary.size() - 1 ); - _pending_cycle_trace.reset(); - return; - } - - for( int idx = 0; idx < _pending_cycle_trace->shard_traces.size(); idx++ ) { - auto& trace = _pending_cycle_trace->shard_traces.at(idx); - auto& shard = _pending_block->regions.back().cycles_summary.back().at(idx); - - trace.finalize_shard(); - shard.read_locks.reserve(trace.read_locks.size()); - shard.read_locks.insert(shard.read_locks.end(), trace.read_locks.begin(), trace.read_locks.end()); - - shard.write_locks.reserve(trace.write_locks.size()); - shard.write_locks.insert(shard.write_locks.end(), trace.write_locks.begin(), trace.write_locks.end()); - } - - _apply_cycle_trace(*_pending_cycle_trace); - _pending_block_trace->region_traces.back().cycle_traces.emplace_back(std::move(*_pending_cycle_trace)); - _pending_cycle_trace.reset(); -} - -void chain_controller::_apply_cycle_trace( const cycle_trace& res ) -{ - auto &generated_transaction_idx = _db.get_mutable_index(); - const auto &generated_index = generated_transaction_idx.indices().get(); - - // TODO: Check for conflicts in deferred_transaction_requests between shards - for (const auto&st: res.shard_traces) { - for (const auto &tr: st.transaction_traces) { - for (const auto &req: tr.deferred_transaction_requests) { - if ( req.contains() ) { - const auto& dt = req.get(); - const auto itr = generated_index.lower_bound(boost::make_tuple(dt.sender, dt.sender_id)); - if ( itr != generated_index.end() && itr->sender == dt.sender && itr->sender_id == dt.sender_id ) { - _destroy_generated_transaction(*itr); - } - - _create_generated_transaction(dt); - } else if ( req.contains() ) { - const auto& dr = req.get(); - const auto itr = generated_index.lower_bound(boost::make_tuple(dr.sender, dr.sender_id)); - if ( itr != generated_index.end() && itr->sender == dr.sender && itr->sender_id == dr.sender_id ) { - _destroy_generated_transaction(*itr); - } - } - } - ///TODO: hook this up as a signal handler in a de-coupled "logger" that may just silently drop them - if(fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::debug)) { - for (const auto &ar : tr.action_traces) { - if (!ar.console.empty()) { - auto prefix = fc::format_string( - "\n[(${a},${n})->${r}]", - fc::mutable_variant_object() - ("a", ar.act.account) - ("n", ar.act.name) - ("r", ar.receiver)); - dlog(prefix + ": CONSOLE OUTPUT BEGIN =====================\n" - + ar.console - + prefix + ": CONSOLE OUTPUT END =====================" ); - } - } - } - } - } -} - -/** - * After applying all transactions successfully we can update - * the current block time, block number, producer stats, etc - */ -void chain_controller::_finalize_block( const block_trace& trace, const producer_object& signing_producer ) { try { - const auto& b = trace.block; - - update_global_properties( b ); - update_global_dynamic_data( b ); - update_signing_producer(signing_producer, b); - - create_block_summary(b); - clear_expired_transactions(); - - update_last_irreversible_block(); - _resource_limits.process_account_limit_updates(); - - const auto& chain_config = this->get_global_properties().configuration; - _resource_limits.set_block_parameters( - {EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct), chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}}, - {EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}} - ); - - // trigger an update of our elastic values for block limits - _resource_limits.process_block_usage(b.block_num()); - - // validate_block_header( _skip_flags, b ); - applied_block( trace ); //emit - if (_currently_replaying_blocks) - applied_irreversible_block(b); - -} FC_CAPTURE_AND_RETHROW( (trace.block) ) } - -signed_block chain_controller::generate_block( - block_timestamp_type when, - account_name producer, - const private_key_type& block_signing_private_key, - uint32_t skip /* = 0 */ - ) -{ try { - return with_skip_flags( skip | created_block, [&](){ - return _db.with_write_lock( [&](){ - return _generate_block( when, producer, block_signing_private_key ); - }); - }); -} FC_CAPTURE_AND_RETHROW( (when) ) } - -signed_block chain_controller::_generate_block( block_timestamp_type when, - account_name producer, - const private_key_type& block_signing_key ) -{ try { - - try { - FC_ASSERT( head_block_time() < (fc::time_point)when, "block must be generated at a timestamp after the head block time" ); - uint32_t skip = _skip_flags; - uint32_t slot_num = get_slot_at_time( when ); - FC_ASSERT( slot_num > 0 ); - account_name scheduled_producer = get_scheduled_producer( slot_num ); - FC_ASSERT( scheduled_producer == producer ); - - const auto& producer_obj = get_producer(scheduled_producer); - - if( !_pending_block ) { - _start_pending_block(); - } - - _finalize_pending_cycle(); - - if( !(skip & skip_producer_signature) ) - FC_ASSERT( producer_obj.signing_key == block_signing_key.get_public_key(), - "producer key ${pk}, block key ${bk}", ("pk", producer_obj.signing_key)("bk", block_signing_key.get_public_key()) ); - - _pending_block->timestamp = when; - _pending_block->producer = producer_obj.owner; - _pending_block->previous = head_block_id(); - _pending_block->block_mroot = get_dynamic_global_properties().block_merkle_root.get_root(); - _pending_block->transaction_mroot = _pending_block_trace->calculate_transaction_merkle_root(); - _pending_block->action_mroot = _pending_block_trace->calculate_action_merkle_root(); - - if( is_start_of_round( _pending_block->block_num() ) ) { - auto latest_producer_schedule = _calculate_producer_schedule(); - if( latest_producer_schedule != _head_producer_schedule() ) - _pending_block->new_producers = latest_producer_schedule; - } - _pending_block->schedule_version = get_global_properties().active_producers.version; - - if( !(skip & skip_producer_signature) ) - _pending_block->sign( block_signing_key ); - - _finalize_block( *_pending_block_trace, producer_obj ); - - _pending_block_session->push(); - - auto result = move( *_pending_block ); - - clear_pending(); - - if (!(skip&skip_fork_db)) { - _fork_db.push_block(result); - } - return result; - } catch ( ... ) { - clear_pending(); - - elog( "error while producing block" ); - _start_pending_block(); - throw; - } - -} FC_CAPTURE_AND_RETHROW( (producer) ) } - -/** - * Removes the most recent block from the database and undoes any changes it made. - */ -void chain_controller::pop_block() -{ try { - clear_pending(); - auto head_id = head_block_id(); - optional head_block = fetch_block_by_id( head_id ); - - EOS_ASSERT( head_block.valid(), pop_empty_chain, "there are no blocks to pop" ); - wlog( "pop block #${n} from ${pro} ${time} ${id}", ("n",head_block->block_num())("pro",name(head_block->producer))("time",head_block->timestamp)("id",head_block->id())); - - _fork_db.pop_block(); - _db.undo(); -} FC_CAPTURE_AND_RETHROW() } - -void chain_controller::clear_pending() -{ try { - _pending_block_trace.reset(); - _pending_block.reset(); - _pending_block_session.reset(); - _pending_transaction_metas.clear(); -} FC_CAPTURE_AND_RETHROW() } - -//////////////////// private methods //////////////////// - -void chain_controller::_apply_block(const signed_block& next_block, uint32_t skip) -{ - auto block_num = next_block.block_num(); - if (_checkpoints.size() && _checkpoints.rbegin()->second != block_id_type()) { - auto itr = _checkpoints.find(block_num); - if (itr != _checkpoints.end()) - FC_ASSERT(next_block.id() == itr->second, - "Block did not match checkpoint", ("checkpoint",*itr)("block_id",next_block.id())); - - if (_checkpoints.rbegin()->first >= block_num) - skip = ~0;// WE CAN SKIP ALMOST EVERYTHING - } - - with_applying_block([&] { - with_skip_flags(skip, [&] { - __apply_block(next_block); - }); - }); -} - -static void validate_shard_locks(const vector& locks, const string& tag) { - if (locks.size() < 2) { - return; - } - - for (auto cur = locks.begin() + 1; cur != locks.end(); ++cur) { - auto prev = cur - 1; - EOS_ASSERT(*prev != *cur, block_lock_exception, "${tag} lock \"${a}::${s}\" is not unique", ("tag",tag)("a",cur->account)("s",cur->scope)); - EOS_ASSERT(*prev < *cur, block_lock_exception, "${tag} locks are not sorted", ("tag",tag)); - } -} - -void chain_controller::__apply_block(const signed_block& next_block) -{ try { - optional processing_deadline; - if (!_currently_replaying_blocks && _limits.max_push_block_us.count() > 0) { - processing_deadline = fc::time_point::now() + _limits.max_push_block_us; - } - - uint32_t skip = _skip_flags; - - const producer_object& signing_producer = validate_block_header(skip, next_block); - - /// regions must be listed in order - for( uint32_t i = 1; i < next_block.regions.size(); ++i ) - FC_ASSERT( next_block.regions[i-1].region < next_block.regions[i].region ); - - block_trace next_block_trace(next_block); - - /// cache the input transaction ids so that they can be looked up when executing the - /// summary - vector input_metas; - // add all implicit transactions - { - next_block_trace.implicit_transactions.emplace_back(_get_on_block_transaction()); - } - - input_metas.reserve(next_block.input_transactions.size() + next_block_trace.implicit_transactions.size()); - - for ( const auto& t : next_block_trace.implicit_transactions ) { - input_metas.emplace_back(packed_transaction(t), get_chain_id(), head_block_time(), processing_deadline, true /*implicit*/); - } - - map trx_index; - for( const auto& t : next_block.input_transactions ) { - input_metas.emplace_back(t, chain_id_type(), next_block.timestamp, processing_deadline); - validate_transaction_with_minimal_state( input_metas.back().trx(), input_metas.back().billable_packed_size ); - if( should_check_signatures() ) { - input_metas.back().signing_keys = input_metas.back().trx().get_signature_keys( t.signatures, chain_id_type(), - input_metas.back().context_free_data, false ); - } - trx_index[input_metas.back().id] = input_metas.size() - 1; - } - - next_block_trace.region_traces.reserve(next_block.regions.size()); - - for( uint32_t region_index = 0; region_index < next_block.regions.size(); ++region_index ) { - const auto& r = next_block.regions[region_index]; - region_trace r_trace; - r_trace.cycle_traces.reserve(r.cycles_summary.size()); - - EOS_ASSERT(!r.cycles_summary.empty(), tx_empty_region,"region[${r_index}] has no cycles", ("r_index",region_index)); - for (uint32_t cycle_index = 0; cycle_index < r.cycles_summary.size(); cycle_index++) { - const auto& cycle = r.cycles_summary.at(cycle_index); - cycle_trace c_trace; - c_trace.shard_traces.reserve(cycle.size()); - - // validate that no read_scope is used as a write scope in this cycle and that no two shards - // share write scopes - set read_locks; - map write_locks; - - EOS_ASSERT(!cycle.empty(), tx_empty_cycle,"region[${r_index}] cycle[${c_index}] has no shards", ("r_index",region_index)("c_index",cycle_index)); - for (uint32_t shard_index = 0; shard_index < cycle.size(); shard_index++) { - const auto& shard = cycle.at(shard_index); - EOS_ASSERT(!shard.empty(), tx_empty_shard,"region[${r_index}] cycle[${c_index}] shard[${s_index}] is empty", - ("r_index",region_index)("c_index",cycle_index)("s_index",shard_index)); - - // Validate that the shards locks are unique and sorted - validate_shard_locks(shard.read_locks, "read"); - validate_shard_locks(shard.write_locks, "write"); - - for (const auto& s: shard.read_locks) { - EOS_ASSERT(write_locks.count(s) == 0, block_concurrency_exception, - "shard ${i} requires read lock \"${a}::${s}\" which is locked for write by shard ${j}", - ("i", shard_index)("s", s)("j", write_locks[s])); - read_locks.emplace(s); - } - - for (const auto& s: shard.write_locks) { - EOS_ASSERT(write_locks.count(s) == 0, block_concurrency_exception, - "shard ${i} requires write lock \"${a}::${s}\" which is locked for write by shard ${j}", - ("i", shard_index)("a", s.account)("s", s.scope)("j", write_locks[s])); - EOS_ASSERT(read_locks.count(s) == 0, block_concurrency_exception, - "shard ${i} requires write lock \"${a}::${s}\" which is locked for read", - ("i", shard_index)("a", s.account)("s", s.scope)); - write_locks[s] = shard_index; - } - - flat_set used_read_locks; - flat_set used_write_locks; - - shard_trace s_trace; - for (const auto& receipt : shard.transactions) { - optional _temp; - auto make_metadata = [&]() -> transaction_metadata* { - auto itr = trx_index.find(receipt.id); - if( itr != trx_index.end() ) { - auto& trx_meta = input_metas.at(itr->second); - const auto& trx = trx_meta.trx(); - trx_meta.delay = fc::seconds(trx.delay_sec); - - validate_expiration_not_too_far(trx, head_block_time() + trx_meta.delay); - validate_referenced_accounts(trx); - validate_uniqueness(trx); - if( should_check_authorization() ) { - FC_ASSERT( !should_check_signatures() || trx_meta.signing_keys, - "signing_keys missing from transaction_metadata of an input transaction" ); - auto enforced_delay = check_authorization( trx.actions, - should_check_signatures() ? *trx_meta.signing_keys - : flat_set() ); - EOS_ASSERT( trx_meta.delay >= enforced_delay, - transaction_exception, - "authorization imposes a delay (${enforced_delay} sec) greater than the delay specified in transaction header (${specified_delay} sec)", - ("enforced_delay", enforced_delay.to_seconds())("specified_delay", trx_meta.delay.to_seconds()) ); - } - - return &trx_meta; - } else { - const auto* gtrx = _db.find(receipt.id); - if (gtrx != nullptr) { - //ilog( "defer" ); - auto trx = fc::raw::unpack(gtrx->packed_trx.data(), gtrx->packed_trx.size()); - FC_ASSERT( trx.execute_after <= head_block_time() , "deferred transaction executed prematurely" ); - validate_not_expired( trx ); - validate_uniqueness( trx ); - _temp.emplace(trx, gtrx->published, trx.sender, trx.sender_id, gtrx->packed_trx.data(), gtrx->packed_trx.size(), processing_deadline ); - _destroy_generated_transaction(*gtrx); - return &*_temp; - } else { - //ilog( "implicit" ); - for ( size_t i=0; i < next_block_trace.implicit_transactions.size(); i++ ) { - if ( input_metas[i].id == receipt.id ) - return &input_metas[i]; - } - FC_ASSERT(false, "implicit transaction not found ${trx}", ("trx", receipt)); - } - } - }; - - auto *mtrx = make_metadata(); - - FC_ASSERT( mtrx->trx().region == r.region, "transaction was scheduled into wrong region" ); - - mtrx->region_id = r.region; - mtrx->cycle_index = cycle_index; - mtrx->shard_index = shard_index; - mtrx->allowed_read_locks.emplace(&shard.read_locks); - mtrx->allowed_write_locks.emplace(&shard.write_locks); - - if( mtrx->delay.count() == 0 ) { - s_trace.transaction_traces.emplace_back(_apply_transaction(*mtrx)); - record_locks_for_data_access(s_trace.transaction_traces.back(), used_read_locks, used_write_locks); - } else { - s_trace.transaction_traces.emplace_back(delayed_transaction_processing(*mtrx)); - } - - auto& t_trace = s_trace.transaction_traces.back(); - if( mtrx->raw_trx.valid() && !mtrx->is_implicit ) { // if an input transaction - t_trace.packed_trx_digest = mtrx->packed_digest; - } - t_trace.region_id = r.region; - t_trace.cycle_index = cycle_index; - t_trace.shard_index = shard_index; - - EOS_ASSERT( receipt.status == s_trace.transaction_traces.back().status, tx_receipt_inconsistent_status, - "Received status of transaction from block (${rstatus}) does not match the applied transaction's status (${astatus})", - ("rstatus",receipt.status)("astatus",s_trace.transaction_traces.back().status) ); - EOS_ASSERT( receipt.kcpu_usage == s_trace.transaction_traces.back().kcpu_usage, tx_receipt_inconsistent_cpu, - "Received kcpu_usage of transaction from block (${rcpu}) does not match the applied transaction's kcpu_usage (${acpu})", - ("rcpu",receipt.kcpu_usage)("acpu",s_trace.transaction_traces.back().kcpu_usage) ); - EOS_ASSERT( receipt.net_usage_words == s_trace.transaction_traces.back().net_usage_words, tx_receipt_inconsistent_net, - "Received net_usage_words of transaction from block (${rnet}) does not match the applied transaction's net_usage_words (${anet})", - ("rnet",receipt.net_usage_words)("anet",s_trace.transaction_traces.back().net_usage_words) ); - - } /// for each transaction id - - EOS_ASSERT( boost::equal( used_read_locks, shard.read_locks ), - block_lock_exception, "Read locks for executing shard: ${s} do not match those listed in the block", ("s", shard_index)); - EOS_ASSERT( boost::equal( used_write_locks, shard.write_locks ), - block_lock_exception, "Write locks for executing shard: ${s} do not match those listed in the block", ("s", shard_index)); - - s_trace.finalize_shard(); - c_trace.shard_traces.emplace_back(move(s_trace)); - } /// for each shard - - _resource_limits.synchronize_account_ram_usage(); - _apply_cycle_trace(c_trace); - r_trace.cycle_traces.emplace_back(move(c_trace)); - } /// for each cycle - - next_block_trace.region_traces.emplace_back(move(r_trace)); - } /// for each region - - FC_ASSERT( next_block.action_mroot == next_block_trace.calculate_action_merkle_root(), "action merkle root does not match"); - FC_ASSERT( next_block.transaction_mroot == next_block_trace.calculate_transaction_merkle_root(), "transaction merkle root does not match" ); - - _finalize_block( next_block_trace, signing_producer ); -} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) } - -flat_set chain_controller::get_required_keys(const transaction& trx, - const flat_set& candidate_keys)const -{ - auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, - noop_permission_visitor(), - get_global_properties().configuration.max_authority_depth, - candidate_keys); - - for (const auto& act : trx.actions ) { - for (const auto& declared_auth : act.authorization) { - if (!checker.satisfied(declared_auth)) { - EOS_ASSERT(checker.satisfied(declared_auth), tx_missing_sigs, - "transaction declares authority '${auth}', but does not have signatures for it.", - ("auth", declared_auth)); - } - } - } - - return checker.used_keys(); -} - - -class permission_visitor { -public: - permission_visitor(const chain_controller& controller) - : _chain_controller(controller), _track_delay(true) { - _max_delay_stack.emplace_back(); - } - - void operator()(const permission_level& perm_level) { - const auto obj = _chain_controller.get_permission(perm_level); - if( _track_delay && _max_delay_stack.back() < obj.delay ) - _max_delay_stack.back() = obj.delay; - } - - void push_undo() { - _max_delay_stack.emplace_back( _max_delay_stack.back() ); - } - - void pop_undo() { - FC_ASSERT( _max_delay_stack.size() >= 2, "invariant failure in permission_visitor" ); - _max_delay_stack.pop_back(); - } - - void squash_undo() { - FC_ASSERT( _max_delay_stack.size() >= 2, "invariant failure in permission_visitor" ); - auto delay_to_keep = _max_delay_stack.back(); - _max_delay_stack.pop_back(); - _max_delay_stack.back() = delay_to_keep; - } - - fc::microseconds get_max_delay()const { - FC_ASSERT( _max_delay_stack.size() == 1, "invariant failure in permission_visitor" ); - return _max_delay_stack.back(); - } - - void pause_delay_tracking() { - _track_delay = false; - } - - void resume_delay_tracking() { - _track_delay = true; - } - -private: - const chain_controller& _chain_controller; - vector _max_delay_stack; - bool _track_delay; -}; - -optional chain_controller::check_updateauth_authorization( const contracts::updateauth& update, - const vector& auths )const -{ - EOS_ASSERT( auths.size() == 1, tx_irrelevant_auth, - "updateauth action should only have one declared authorization" ); - const auto& auth = auths[0]; - EOS_ASSERT( auth.actor == update.account, tx_irrelevant_auth, - "the owner of the affected permission needs to be the actor of the declared authorization" ); - - const auto* min_permission = find_permission({update.account, update.permission}); - bool ignore_delay = false; - if( !min_permission ) { // creating a new permission - ignore_delay = true; - min_permission = &get_permission({update.account, update.parent}); - - } - const auto delay = get_permission(auth).satisfies( *min_permission, - _db.get_index().indices() ); - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "updateauth action declares irrelevant authority '${auth}'; minimum authority is ${min}", - ("auth", auth)("min", permission_level{update.account, min_permission->name}) ); - - return (ignore_delay ? optional() : *delay); -} - -fc::microseconds chain_controller::check_deleteauth_authorization( const contracts::deleteauth& del, - const vector& auths )const -{ - EOS_ASSERT( auths.size() == 1, tx_irrelevant_auth, - "deleteauth action should only have one declared authorization" ); - const auto& auth = auths[0]; - EOS_ASSERT( auth.actor == del.account, tx_irrelevant_auth, - "the owner of the permission to delete needs to be the actor of the declared authorization" ); - - const auto& min_permission = get_permission({del.account, del.permission}); - const auto delay = get_permission(auth).satisfies( min_permission, - _db.get_index().indices() ); - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "updateauth action declares irrelevant authority '${auth}'; minimum authority is ${min}", - ("auth", auth)("min", permission_level{min_permission.owner, min_permission.name}) ); - - return *delay; -} - -fc::microseconds chain_controller::check_linkauth_authorization( const contracts::linkauth& link, - const vector& auths )const -{ - EOS_ASSERT( auths.size() == 1, tx_irrelevant_auth, - "link action should only have one declared authorization" ); - const auto& auth = auths[0]; - EOS_ASSERT( auth.actor == link.account, tx_irrelevant_auth, - "the owner of the linked permission needs to be the actor of the declared authorization" ); - - EOS_ASSERT( link.type != contracts::updateauth::get_name(), action_validate_exception, - "Cannot link eosio::updateauth to a minimum permission" ); - EOS_ASSERT( link.type != contracts::deleteauth::get_name(), action_validate_exception, - "Cannot link eosio::deleteauth to a minimum permission" ); - EOS_ASSERT( link.type != contracts::linkauth::get_name(), action_validate_exception, - "Cannot link eosio::linkauth to a minimum permission" ); - EOS_ASSERT( link.type != contracts::unlinkauth::get_name(), action_validate_exception, - "Cannot link eosio::unlinkauth to a minimum permission" ); - EOS_ASSERT( link.type != contracts::canceldelay::get_name(), action_validate_exception, - "Cannot link eosio::canceldelay to a minimum permission" ); - - const auto linked_permission_name = lookup_minimum_permission(link.account, link.code, link.type); - - if( !linked_permission_name ) // if action is linked to eosio.any permission - return fc::microseconds(0); - - const auto delay = get_permission(auth).satisfies( get_permission({link.account, *linked_permission_name}), - _db.get_index().indices() ); - - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "link action declares irrelevant authority '${auth}'; minimum authority is ${min}", - ("auth", auth)("min", permission_level{link.account, *linked_permission_name}) ); - - return *delay; -} - -fc::microseconds chain_controller::check_unlinkauth_authorization( const contracts::unlinkauth& unlink, - const vector& auths )const -{ - EOS_ASSERT( auths.size() == 1, tx_irrelevant_auth, - "unlink action should only have one declared authorization" ); - const auto& auth = auths[0]; - EOS_ASSERT( auth.actor == unlink.account, tx_irrelevant_auth, - "the owner of the linked permission needs to be the actor of the declared authorization" ); - - const auto unlinked_permission_name = lookup_linked_permission(unlink.account, unlink.code, unlink.type); - EOS_ASSERT( unlinked_permission_name.valid(), transaction_exception, - "cannot unlink non-existent permission link of account '${account}' for actions matching '${code}::${action}'", - ("account", unlink.account)("code", unlink.code)("action", unlink.type) ); - - if( *unlinked_permission_name == config::eosio_any_name ) - return fc::microseconds(0); - - const auto delay = get_permission(auth).satisfies( get_permission({unlink.account, *unlinked_permission_name}), - _db.get_index().indices() ); - - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "unlink action declares irrelevant authority '${auth}'; minimum authority is ${min}", - ("auth", auth)("min", permission_level{unlink.account, *unlinked_permission_name}) ); - - return *delay; -} - -void chain_controller::check_canceldelay_authorization( const contracts::canceldelay& cancel, - const vector& auths )const -{ - EOS_ASSERT( auths.size() == 1, tx_irrelevant_auth, - "canceldelay action should only have one declared authorization" ); - const auto& auth = auths[0]; - - const auto delay = get_permission(auth).satisfies( get_permission(cancel.canceling_auth), - _db.get_index().indices() ); - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "canceldelay action declares irrelevant authority '${auth}'; specified authority to satisfy is ${min}", - ("auth", auth)("min", cancel.canceling_auth) ); -} - -fc::microseconds chain_controller::check_authorization( const vector& actions, - const flat_set& provided_keys, - bool allow_unused_signatures, - flat_set provided_accounts, - flat_set provided_levels)const -{ - auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, - permission_visitor(*this), - get_global_properties().configuration.max_authority_depth, - provided_keys, provided_accounts, provided_levels ); - - fc::microseconds max_delay; - - for( const auto& act : actions ) { - bool special_case = false; - bool ignore_delay = false; - - if( act.account == config::system_account_name ) { - special_case = true; - - if( act.name == contracts::updateauth::get_name() ) { - const auto delay = check_updateauth_authorization(act.data_as(), act.authorization); - if( delay.valid() ) // update auth is used to modify an existing permission - max_delay = std::max( max_delay, *delay ); - else // updateauth is used to create a new permission - ignore_delay = true; - } else if( act.name == contracts::deleteauth::get_name() ) { - max_delay = std::max( max_delay, - check_deleteauth_authorization(act.data_as(), act.authorization) ); - } else if( act.name == contracts::linkauth::get_name() ) { - max_delay = std::max( max_delay, - check_linkauth_authorization(act.data_as(), act.authorization) ); - } else if( act.name == contracts::unlinkauth::get_name() ) { - max_delay = std::max( max_delay, - check_unlinkauth_authorization(act.data_as(), act.authorization) ); - } else if( act.name == contracts::canceldelay::get_name() ) { - check_canceldelay_authorization(act.data_as(), act.authorization); - ignore_delay = true; - } else { - special_case = false; - } - } - - for( const auto& declared_auth : act.authorization ) { - - if( !special_case ) { - auto min_permission_name = lookup_minimum_permission(declared_auth.actor, act.account, act.name); - if( min_permission_name ) { // since special cases were already handled, it should only be false if the permission is eosio.any - const auto& min_permission = get_permission({declared_auth.actor, *min_permission_name}); - auto delay = get_permission(declared_auth).satisfies( min_permission, - _db.get_index().indices() ); - EOS_ASSERT( delay.valid(), - tx_irrelevant_auth, - "action declares irrelevant authority '${auth}'; minimum authority is ${min}", - ("auth", declared_auth)("min", permission_level{min_permission.owner, min_permission.name}) ); - max_delay = std::max( max_delay, *delay ); - } - } - - if( should_check_signatures() ) { - if( ignore_delay ) - checker.get_permission_visitor().pause_delay_tracking(); - EOS_ASSERT(checker.satisfied(declared_auth), tx_missing_sigs, - "transaction declares authority '${auth}', but does not have signatures for it.", - ("auth", declared_auth)); - if( ignore_delay ) - checker.get_permission_visitor().resume_delay_tracking(); - } - } - } - - if( !allow_unused_signatures && should_check_signatures() ) { - EOS_ASSERT( checker.all_keys_used(), tx_irrelevant_sig, - "transaction bears irrelevant signatures from these keys: ${keys}", - ("keys", checker.unused_keys()) ); - } - - const auto checker_max_delay = checker.get_permission_visitor().get_max_delay(); - - return std::max(max_delay, checker_max_delay); -} - -bool chain_controller::check_authorization( account_name account, permission_name permission, - flat_set provided_keys, - bool allow_unused_signatures)const -{ - auto checker = make_auth_checker( [&](const permission_level& p){ return get_permission(p).auth; }, - noop_permission_visitor(), - get_global_properties().configuration.max_authority_depth, - provided_keys); - - auto satisfied = checker.satisfied({account, permission}); - - if( satisfied && !allow_unused_signatures ) { - EOS_ASSERT(checker.all_keys_used(), tx_irrelevant_sig, - "irrelevant signatures from these keys: ${keys}", - ("keys", checker.unused_keys())); - } - - return satisfied; -} - -fc::microseconds chain_controller::check_transaction_authorization(const transaction& trx, - const vector& signatures, - const vector& cfd, - bool allow_unused_signatures)const -{ - if( should_check_signatures() ) { - return check_authorization( trx.actions, - trx.get_signature_keys( signatures, chain_id_type{}, cfd, allow_unused_signatures ), - allow_unused_signatures ); - } else { - return check_authorization( trx.actions, flat_set(), true ); - } -} - -optional chain_controller::lookup_minimum_permission(account_name authorizer_account, - account_name scope, - action_name act_name) const { -#warning TODO: this comment sounds like it is expecting a check ("may") somewhere else, but I have not found anything else - // Special case native actions cannot be linked to a minimum permission, so there is no need to check. - if( scope == config::system_account_name ) { - FC_ASSERT( act_name != contracts::updateauth::get_name() && - act_name != contracts::deleteauth::get_name() && - act_name != contracts::linkauth::get_name() && - act_name != contracts::unlinkauth::get_name() && - act_name != contracts::canceldelay::get_name(), - "cannot call lookup_minimum_permission on native actions that are not allowed to be linked to minimum permissions" ); - } - - try { - optional linked_permission = lookup_linked_permission(authorizer_account, scope, act_name); - if( !linked_permission ) - return config::active_name; - - if( *linked_permission == config::eosio_any_name ) - return optional(); - - return linked_permission; - } FC_CAPTURE_AND_RETHROW((authorizer_account)(scope)(act_name)) -} - -optional chain_controller::lookup_linked_permission(account_name authorizer_account, - account_name scope, - action_name act_name) const { - try { - // First look up a specific link for this message act_name - auto key = boost::make_tuple(authorizer_account, scope, act_name); - auto link = _db.find(key); - // If no specific link found, check for a contract-wide default - if (link == nullptr) { - get<2>(key) = ""; - link = _db.find(key); - } - - // If no specific or default link found, use active permission - if (link != nullptr) { - return link->required_permission; - } - return optional(); - - // return optional(); - } FC_CAPTURE_AND_RETHROW((authorizer_account)(scope)(act_name)) -} - -void chain_controller::validate_uniqueness( const transaction& trx )const { - if( !should_check_for_duplicate_transactions() ) return; - auto transaction = _db.find(trx.id()); - EOS_ASSERT(transaction == nullptr, tx_duplicate, "Transaction is not unique"); -} - -void chain_controller::record_transaction(const transaction& trx) -{ - try { - _db.create([&](transaction_object& transaction) { - transaction.trx_id = trx.id(); - transaction.expiration = trx.expiration; - }); - } catch ( ... ) { - EOS_ASSERT( false, transaction_exception, - "duplicate transaction ${id}", - ("id", trx.id() ) ); - } -} - -static uint32_t calculate_transaction_cpu_usage( const transaction_trace& trace, const transaction_metadata& meta, const chain_config& chain_configuration ) { - // calculate the sum of all actions retired - uint32_t action_cpu_usage = 0; - uint32_t context_free_actual_cpu_usage = 0; - for (const auto &at: trace.action_traces) { - if (at.context_free) { - context_free_actual_cpu_usage += chain_configuration.base_per_action_cpu_usage + at.cpu_usage; - } else { - action_cpu_usage += chain_configuration.base_per_action_cpu_usage + at.cpu_usage; - if (at.receiver == config::system_account_name && - at.act.account == config::system_account_name && - at.act.name == N(setcode)) { - action_cpu_usage += chain_configuration.base_setcode_cpu_usage; - } - } - } - - // charge a system controlled amount for signature verification/recovery - uint32_t signature_cpu_usage = 0; - if( meta.signature_count ) { - signature_cpu_usage = meta.signature_count * chain_configuration.per_signature_cpu_usage; - } - - uint32_t context_free_cpu_usage = (uint32_t)((uint64_t)context_free_actual_cpu_usage * chain_configuration.context_free_discount_cpu_usage_num / chain_configuration.context_free_discount_cpu_usage_den); - - auto actual_cpu_usage = chain_configuration.base_per_transaction_cpu_usage + - action_cpu_usage + - context_free_cpu_usage + - signature_cpu_usage; - actual_cpu_usage = ((actual_cpu_usage + 1023)/1024) * 1024; // Round up to nearest multiple of 1024 - - uint32_t cpu_usage_limit = meta.trx().max_kcpu_usage.value * 1024UL; // overflow checked in validate_transaction_without_state - EOS_ASSERT( cpu_usage_limit == 0 || actual_cpu_usage <= cpu_usage_limit, tx_resource_exhausted, - "declared cpu usage limit of transaction is too low: ${actual_cpu_usage} > ${declared_limit}", - ("actual_cpu_usage", actual_cpu_usage)("declared_limit",cpu_usage_limit) ); - - return actual_cpu_usage; -} - -static uint32_t calculate_transaction_net_usage( const transaction_trace& trace, const transaction_metadata& meta, const chain_config& chain_configuration ) { - // charge a system controlled per-lock overhead to account for shard bloat - uint32_t lock_net_usage = uint32_t(trace.read_locks.size() + trace.write_locks.size()) * chain_configuration.per_lock_net_usage; - - auto actual_net_usage = chain_configuration.base_per_transaction_net_usage + - meta.billable_packed_size + - lock_net_usage; - actual_net_usage = ((actual_net_usage + 7)/8) * 8; // Round up to nearest multiple of 8 - - - uint32_t net_usage_limit = meta.trx().max_net_usage_words.value * 8UL; // overflow checked in validate_transaction_without_state - EOS_ASSERT( net_usage_limit == 0 || actual_net_usage <= net_usage_limit, tx_resource_exhausted, - "declared net usage limit of transaction is too low: ${actual_net_usage} > ${declared_limit}", - ("actual_net_usage", actual_net_usage)("declared_limit",net_usage_limit) ); - - return actual_net_usage; -} - -void chain_controller::update_resource_usage( transaction_trace& trace, const transaction_metadata& meta ) { - const auto& chain_configuration = get_global_properties().configuration; - - trace.cpu_usage = calculate_transaction_cpu_usage(trace, meta, chain_configuration); - trace.net_usage = calculate_transaction_net_usage(trace, meta, chain_configuration); - trace.kcpu_usage = trace.cpu_usage / 1024; - trace.net_usage_words = trace.net_usage / 8; - - // enforce that the system controlled per tx limits are not violated - EOS_ASSERT(trace.cpu_usage <= chain_configuration.max_transaction_cpu_usage, - tx_resource_exhausted, "Transaction exceeds the maximum cpu usage [used: ${used}, max: ${max}]", - ("used", trace.cpu_usage)("max", chain_configuration.max_transaction_cpu_usage)); - - EOS_ASSERT(trace.net_usage <= chain_configuration.max_transaction_net_usage, - tx_resource_exhausted, "Transaction exceeds the maximum net usage [used: ${used}, max: ${max}]", - ("used", trace.net_usage)("max", chain_configuration.max_transaction_net_usage)); - - // determine the accounts to bill - flat_set bill_to_accounts; - for( const auto& act : meta.trx().actions ) - for( const auto& auth : act.authorization ) - bill_to_accounts.insert( auth.actor ); - - // for account usage, the ordinal is based on possible blocks not actual blocks. This means that as blocks are - // skipped account usage will still decay. - uint32_t ordinal = (uint32_t)(head_block_time().time_since_epoch().count() / fc::milliseconds(config::block_interval_ms).count()); - _resource_limits.add_transaction_usage(bill_to_accounts, trace.cpu_usage, trace.net_usage, ordinal); -} - - -void chain_controller::validate_tapos(const transaction& trx)const { - if (!should_check_tapos()) return; - - const auto& tapos_block_summary = _db.get((uint16_t)trx.ref_block_num); - - //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration - EOS_ASSERT(trx.verify_reference_block(tapos_block_summary.block_id), invalid_ref_block_exception, - "Transaction's reference block did not match. Is this transaction from a different fork?", - ("tapos_summary", tapos_block_summary)); -} - -void chain_controller::validate_referenced_accounts( const transaction& trx )const -{ try { - for( const auto& act : trx.actions ) { - require_account(act.account); - for (const auto& auth : act.authorization ) - require_account(auth.actor); - } -} FC_CAPTURE_AND_RETHROW() } - -void chain_controller::validate_not_expired( const transaction& trx )const -{ try { - fc::time_point now = head_block_time(); - - EOS_ASSERT( now < time_point(trx.expiration), - expired_tx_exception, - "Transaction is expired, now is ${now}, expiration is ${trx.exp}", - ("now",now)("trx.expiration",trx.expiration) ); -} FC_CAPTURE_AND_RETHROW((trx)) } - -void chain_controller::validate_expiration_not_too_far( const transaction& trx, fc::time_point reference_time )const -{ try { - const auto& chain_configuration = get_global_properties().configuration; - - EOS_ASSERT( time_point(trx.expiration) <= reference_time + fc::seconds(chain_configuration.max_transaction_lifetime), - tx_exp_too_far_exception, - "Transaction expiration is too far in the future relative to the reference time of ${reference_time}, " - "expiration is ${trx.expiration} and the maximum transaction lifetime is ${max_til_exp} seconds", - ("trx.expiration",trx.expiration)("reference_time",reference_time) - ("max_til_exp",chain_configuration.max_transaction_lifetime) ); -} FC_CAPTURE_AND_RETHROW((trx)) } - - -void chain_controller::validate_transaction_without_state( const transaction& trx )const -{ try { - EOS_ASSERT( !trx.actions.empty(), tx_no_action, "transaction must have at least one action" ); - - // Check for at least one authorization in the context-aware actions - bool has_auth = false; - for( const auto& act : trx.actions ) { - has_auth |= !act.authorization.empty(); - if( has_auth ) break; - } - EOS_ASSERT( has_auth, tx_no_auths, "transaction must have at least one authorization" ); - - // Check that there are no authorizations in any of the context-free actions - for (const auto &act : trx.context_free_actions) { - EOS_ASSERT( act.authorization.empty(), cfa_irrelevant_auth, "context-free actions cannot require authorization" ); - } - - EOS_ASSERT( trx.max_kcpu_usage.value < UINT32_MAX / 1024UL, transaction_exception, "declared max_kcpu_usage overflows when expanded to max cpu usage" ); - EOS_ASSERT( trx.max_net_usage_words.value < UINT32_MAX / 8UL, transaction_exception, "declared max_net_usage_words overflows when expanded to max net usage" ); - -} FC_CAPTURE_AND_RETHROW((trx)) } - -void chain_controller::validate_transaction_with_minimal_state( const transaction& trx, uint32_t min_net_usage )const -{ try { - validate_transaction_without_state(trx); - validate_not_expired(trx); - validate_tapos(trx); - - uint32_t net_usage_limit = trx.max_net_usage_words.value * 8; // overflow checked in validate_transaction_without_state - EOS_ASSERT( net_usage_limit == 0 || min_net_usage <= net_usage_limit, - transaction_exception, - "Packed transaction and associated data does not fit into the space committed to by the transaction's header! [usage=${usage},commitment=${commit}]", - ("usage", min_net_usage)("commit", net_usage_limit)); - -} FC_CAPTURE_AND_RETHROW((trx)) } - -void chain_controller::require_scope( const scope_name& scope )const { - switch( uint64_t(scope) ) { - case config::eosio_all_scope: - case config::eosio_auth_scope: - return; /// built in scopes - default: - require_account(scope); - } -} - -void chain_controller::require_account(const account_name& name) const { - auto account = _db.find(name); - FC_ASSERT(account != nullptr, "Account not found: ${name}", ("name", name)); -} - -const producer_object& chain_controller::validate_block_header(uint32_t skip, const signed_block& next_block)const { try { - EOS_ASSERT(head_block_id() == next_block.previous, block_validate_exception, "", - ("head_block_id",head_block_id())("next.prev",next_block.previous)); - EOS_ASSERT(head_block_time() < (fc::time_point)next_block.timestamp, block_validate_exception, "", - ("head_block_time",head_block_time())("next",next_block.timestamp)("blocknum",next_block.block_num())); - if (((fc::time_point)next_block.timestamp) > head_block_time() + fc::microseconds(config::block_interval_ms*1000)) { - elog("head_block_time ${h}, next_block ${t}, block_interval ${bi}", - ("h", head_block_time())("t", next_block.timestamp)("bi", config::block_interval_ms)); - elog("Did not produce block within block_interval ${bi}ms, took ${t}ms)", - ("bi", config::block_interval_ms)("t", (time_point(next_block.timestamp) - head_block_time()).count() / 1000)); - } - - - if( !is_start_of_round( next_block.block_num() ) ) { - EOS_ASSERT(!next_block.new_producers, block_validate_exception, - "Producer changes may only occur at the end of a round."); - } - - const producer_object& producer = get_producer(get_scheduled_producer(get_slot_at_time(next_block.timestamp))); - - if(!(skip&skip_producer_signature)) - EOS_ASSERT(next_block.validate_signee(producer.signing_key), block_validate_exception, - "Incorrect block producer key: expected ${e} but got ${a}", - ("e", producer.signing_key)("a", public_key_type(next_block.signee()))); - - if(!(skip&skip_producer_schedule_check)) { - EOS_ASSERT(next_block.producer == producer.owner, block_validate_exception, - "Producer produced block at wrong time", - ("block producer",next_block.producer)("scheduled producer",producer.owner)); - } - - auto expected_schedule_version = get_global_properties().active_producers.version; - EOS_ASSERT( next_block.schedule_version == expected_schedule_version , block_validate_exception,"wrong producer schedule version specified ${x} expected ${y}", - ("x", next_block.schedule_version)("y",expected_schedule_version) ); - - return producer; -} FC_CAPTURE_AND_RETHROW( (block_header(next_block))) } - -void chain_controller::create_block_summary(const signed_block& next_block) { - auto sid = next_block.block_num() & 0xffff; - _db.modify( _db.get(sid), [&](block_summary_object& p) { - p.block_id = next_block.id(); - }); -} - -/** - * Takes the top config::producer_count producers by total vote excluding any producer whose - * block_signing_key is null. - */ -producer_schedule_type chain_controller::_calculate_producer_schedule()const { - producer_schedule_type schedule = get_global_properties().new_active_producers; - - const auto& hps = _head_producer_schedule(); - schedule.version = hps.version; - if( hps != schedule ) - ++schedule.version; - return schedule; -} - -/** - * Returns the most recent and/or pending producer schedule - */ -const shared_producer_schedule_type& chain_controller::_head_producer_schedule()const { - const auto& gpo = get_global_properties(); - if( gpo.pending_active_producers.size() ) - return gpo.pending_active_producers.back().second; - return gpo.active_producers; -} - -void chain_controller::update_global_properties(const signed_block& b) { try { - // If we're at the end of a round, update the BlockchainConfiguration, producer schedule - // and "producers" special account authority - if( is_start_of_round( b.block_num() ) ) { - auto schedule = _calculate_producer_schedule(); - if( b.new_producers ) - { - FC_ASSERT( schedule == *b.new_producers, "pending producer set different than expected" ); - } - const auto& gpo = get_global_properties(); - - if( _head_producer_schedule() != schedule ) { - //wlog( "change in producer schedule pending irreversible: ${s}", ("s", b.new_producers ) ); - FC_ASSERT( b.new_producers, "pending producer set changed but block didn't indicate it" ); - } - _db.modify( gpo, [&]( auto& props ) { - if( props.pending_active_producers.size() && props.pending_active_producers.back().first == b.block_num() ) - props.pending_active_producers.back().second = schedule; - else - { - props.pending_active_producers.emplace_back( props.pending_active_producers.get_allocator() ); - // props.pending_active_producers.size()+1, props.pending_active_producers.get_allocator() ); - auto& back = props.pending_active_producers.back(); - back.first = b.block_num(); - back.second = schedule; - - } - }); - - _update_producers_authority(); - } -} FC_CAPTURE_AND_RETHROW() } - -void chain_controller::_update_producers_authority() { - const auto& gpo = get_global_properties(); - uint32_t authority_threshold = EOS_PERCENT_CEIL(gpo.active_producers.producers.size(), config::producers_authority_threshold_pct); - auto active_producers_authority = authority(authority_threshold, {}, {}); - for(auto& name : gpo.active_producers.producers ) { - active_producers_authority.accounts.push_back({{name.producer_name, config::active_name}, 1}); - } - - auto& po = get_permission({config::producers_account_name, config::active_name}); - _db.modify(po,[active_producers_authority] (permission_object& po) { - po.auth = active_producers_authority; - }); -} - -void chain_controller::add_checkpoints( const flat_map& checkpts ) { - for (const auto& i : checkpts) - _checkpoints[i.first] = i.second; -} - -bool chain_controller::before_last_checkpoint()const { - return (_checkpoints.size() > 0) && (_checkpoints.rbegin()->first >= head_block_num()); -} - -const global_property_object& chain_controller::get_global_properties()const { - return _db.get(); -} - -const dynamic_global_property_object&chain_controller::get_dynamic_global_properties() const { - return _db.get(); -} - -time_point chain_controller::head_block_time()const { - return get_dynamic_global_properties().time; -} - -uint32_t chain_controller::head_block_num()const { - return get_dynamic_global_properties().head_block_number; -} - -block_id_type chain_controller::head_block_id()const { - return get_dynamic_global_properties().head_block_id; -} - -account_name chain_controller::head_block_producer() const { - auto b = _fork_db.fetch_block(head_block_id()); - if( b ) return b->data.producer; - - if (auto head_block = fetch_block_by_id(head_block_id())) - return head_block->producer; - return {}; -} - -const producer_object& chain_controller::get_producer(const account_name& owner_name) const -{ try { - return _db.get(owner_name); -} FC_CAPTURE_AND_RETHROW( (owner_name) ) } - -const permission_object* chain_controller::find_permission( const permission_level& level )const -{ try { - FC_ASSERT( !level.actor.empty() && !level.permission.empty(), "Invalid permission" ); - return _db.find( boost::make_tuple(level.actor,level.permission) ); -} EOS_RETHROW_EXCEPTIONS( chain::permission_query_exception, "Failed to retrieve permission: ${level}", ("level", level) ) } - -const permission_object& chain_controller::get_permission( const permission_level& level )const -{ try { - FC_ASSERT( !level.actor.empty() && !level.permission.empty(), "Invalid permission" ); - return _db.get( boost::make_tuple(level.actor,level.permission) ); -} EOS_RETHROW_EXCEPTIONS( chain::permission_query_exception, "Failed to retrieve permission: ${level}", ("level", level) ) } - -uint32_t chain_controller::last_irreversible_block_num() const { - return get_dynamic_global_properties().last_irreversible_block_num; -} - -void chain_controller::_initialize_indexes() { - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - - - - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); - _db.add_index(); -} - -void chain_controller::_initialize_chain(contracts::chain_initializer& starter) -{ try { - if (!_db.find()) { - _db.with_write_lock([this, &starter] { - auto initial_timestamp = starter.get_chain_start_time(); - FC_ASSERT(initial_timestamp != time_point(), "Must initialize genesis timestamp." ); - FC_ASSERT( block_timestamp_type(initial_timestamp) == initial_timestamp, - "Genesis timestamp must be divisible by config::block_interval_ms" ); - - // Create global properties - const auto& gp = _db.create([&starter](global_property_object& p) { - p.configuration = starter.get_chain_start_configuration(); - p.active_producers = starter.get_chain_start_producers(); - p.new_active_producers = starter.get_chain_start_producers(); - }); - - _db.create([&](dynamic_global_property_object& p) { - p.time = initial_timestamp; - //p.recent_slots_filled = uint64_t(-1); - }); - - _resource_limits.initialize_chain(); - - // Initialize block summary index - for (int i = 0; i < 0x10000; i++) - _db.create([&](block_summary_object&) {}); - - starter.prepare_database(*this, _db); - _update_producers_authority(); - }); - } -} FC_CAPTURE_AND_RETHROW() } - - -void chain_controller::replay() { - ilog("Replaying blockchain"); - auto start = fc::time_point::now(); - - auto on_exit = fc::make_scoped_exit([&_currently_replaying_blocks = _currently_replaying_blocks](){ - _currently_replaying_blocks = false; - }); - _currently_replaying_blocks = true; - - auto last_block = _block_log.read_head(); - if (!last_block) { - elog("No blocks in block log; skipping replay"); - return; - } - - const auto last_block_num = last_block->block_num(); - - ilog("Replaying ${n} blocks...", ("n", last_block_num) ); - for (uint32_t i = 1; i <= last_block_num; ++i) { - if (i % 5000 == 0) - std::cerr << " " << double(i*100)/last_block_num << "% "< block = _block_log.read_block_by_num(i); - FC_ASSERT(block, "Could not find block #${n} in block_log!", ("n", i)); - _apply_block(*block, skip_producer_signature | - skip_transaction_signatures | - skip_transaction_dupe_check | - skip_tapos_check | - skip_producer_schedule_check | - skip_authority_check | - received_block); - } - auto end = fc::time_point::now(); - ilog("Done replaying ${n} blocks, elapsed time: ${t} sec", - ("n", head_block_num())("t",double((end-start).count())/1000000.0)); - - _db.set_revision(head_block_num()); -} - -void chain_controller::_spinup_db() { - // Rewind the database to the last irreversible block - _db.with_write_lock([&] { - _db.undo_all(); - FC_ASSERT(_db.revision() == head_block_num(), "Chainbase revision does not match head block num", - ("rev", _db.revision())("head_block", head_block_num())); - - }); -} - -void chain_controller::_spinup_fork_db() -{ - fc::optional last_block = _block_log.read_head(); - if(last_block.valid()) { - _fork_db.start_block(*last_block); - if (last_block->id() != head_block_id()) { - FC_ASSERT(head_block_num() == 0, "last block ID does not match current chain state", - ("last_block->id", last_block->id())("head_block_num",head_block_num())); - } - } -} - -/* -ProducerRound chain_controller::calculate_next_round(const signed_block& next_block) { - auto schedule = _admin->get_next_round(_db); - auto changes = get_global_properties().active_producers - schedule; - EOS_ASSERT(boost::range::equal(next_block.producer_changes, changes), block_validate_exception, - "Unexpected round changes in new block header", - ("expected changes", changes)("block changes", next_block.producer_changes)); - - fc::time_point tp = (fc::time_point)next_block.timestamp; - utilities::rand::random rng(tp.sec_since_epoch()); - rng.shuffle(schedule); - return schedule; -}*/ - -void chain_controller::update_global_dynamic_data(const signed_block& b) { - const dynamic_global_property_object& _dgp = _db.get(); - - const auto& bmroot = _dgp.block_merkle_root.get_root(); - FC_ASSERT( bmroot == b.block_mroot, "block merkle root does not match expected value" ); - - uint32_t missed_blocks = head_block_num() == 0? 1 : get_slot_at_time((fc::time_point)b.timestamp); - assert(missed_blocks != 0); - missed_blocks--; - -// if (missed_blocks) -// wlog("Blockchain continuing after gap of ${b} missed blocks", ("b", missed_blocks)); - - if (!(_skip_flags & skip_missed_block_penalty)) { - for (uint32_t i = 0; i < missed_blocks; ++i) { - const auto &producer_missed = get_producer(get_scheduled_producer(i + 1)); - if (producer_missed.owner != b.producer) { - /* - const auto& producer_account = producer_missed.producer_account(*this); - if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) ) - wlog( "Producer ${name} missed block ${n} around ${t}", ("name",producer_account.name)("n",b.block_num())("t",b.timestamp) ); - */ - - _db.modify(producer_missed, [&](producer_object &w) { - w.total_missed++; - }); - } - } - } - - const auto& props = get_global_properties(); - - // dynamic global properties updating - _db.modify( _dgp, [&]( dynamic_global_property_object& dgp ){ - dgp.head_block_number = b.block_num(); - dgp.head_block_id = b.id(); - dgp.time = b.timestamp; - dgp.current_producer = b.producer; - dgp.current_absolute_slot += missed_blocks+1; - - /* - // If we've missed more blocks than the bitmap stores, skip calculations and simply reset the bitmap - if (missed_blocks < sizeof(dgp.recent_slots_filled) * 8) { - dgp.recent_slots_filled <<= 1; - dgp.recent_slots_filled += 1; - dgp.recent_slots_filled <<= missed_blocks; - } else - if(config::percent_100 * get_global_properties().active_producers.producers.size() / blocks_per_round() > config::required_producer_participation) - dgp.recent_slots_filled = uint64_t(-1); - else - dgp.recent_slots_filled = 0; - */ - dgp.block_merkle_root.append( head_block_id() ); - }); - - _fork_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + 1 ); -} - -void chain_controller::update_signing_producer(const producer_object& signing_producer, const signed_block& new_block) -{ - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - uint64_t new_block_aslot = dpo.current_absolute_slot + get_slot_at_time( (fc::time_point)new_block.timestamp ); - - _db.modify( signing_producer, [&]( producer_object& _wit ) - { - _wit.last_aslot = new_block_aslot; - _wit.last_confirmed_block_num = new_block.block_num(); - } ); -} - -void chain_controller::update_permission_usage( const transaction_metadata& meta ) { - // for any transaction not sent by code, update the affirmative last time a given permission was used - if (!meta.sender) { - for( const auto& act : meta.trx().actions ) { - for( const auto& auth : act.authorization ) { - const auto *puo = _db.find(boost::make_tuple(auth.actor, auth.permission)); - if (puo) { - _db.modify(*puo, [this](permission_usage_object &pu) { - pu.last_used = head_block_time(); - }); - } else { - _db.create([this, &auth](permission_usage_object &pu){ - pu.account = auth.actor; - pu.permission = auth.permission; - pu.last_used = head_block_time(); - }); - } - } - } - } -} - -void chain_controller::update_or_create_producers( const producer_schedule_type& producers ) { - for ( auto prod : producers.producers ) { - if ( _db.find(prod.producer_name) == nullptr ) { - _db.create( [&]( auto& pro ) { - pro.owner = prod.producer_name; - pro.signing_key = prod.block_signing_key; - }); - } - } -} - -void chain_controller::update_last_irreversible_block() -{ - const global_property_object& gpo = get_global_properties(); - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - - vector producer_objs; - producer_objs.reserve(gpo.active_producers.producers.size()); - - std::transform(gpo.active_producers.producers.begin(), - gpo.active_producers.producers.end(), std::back_inserter(producer_objs), - [this](const producer_key& pk) { return &get_producer(pk.producer_name); }); - - static_assert(config::irreversible_threshold_percent > 0, "irreversible threshold must be nonzero"); - - size_t offset = EOS_PERCENT(producer_objs.size(), config::percent_100- config::irreversible_threshold_percent); - std::nth_element(producer_objs.begin(), producer_objs.begin() + offset, producer_objs.end(), - [](const producer_object* a, const producer_object* b) { - return a->last_confirmed_block_num < b->last_confirmed_block_num; - }); - - uint32_t new_last_irreversible_block_num = producer_objs[offset]->last_confirmed_block_num; - // TODO: right now the code cannot handle the head block being irreversible for reasons that are purely - // implementation details. We can and should remove this special case once the rest of the logic is fixed. - if (producer_objs.size() == 1) { - new_last_irreversible_block_num -= 1; - } - - - if (new_last_irreversible_block_num > dpo.last_irreversible_block_num) { - _db.modify(dpo, [&](dynamic_global_property_object& _dpo) { - _dpo.last_irreversible_block_num = new_last_irreversible_block_num; - }); - } - - // Write newly irreversible blocks to disk. First, get the number of the last block on disk... - auto old_last_irreversible_block = _block_log.head(); - unsigned last_block_on_disk = 0; - // If this is null, there are no blocks on disk, so the zero is correct - if (old_last_irreversible_block) - last_block_on_disk = old_last_irreversible_block->block_num(); - - if (last_block_on_disk < new_last_irreversible_block_num) { - for (auto block_to_write = last_block_on_disk + 1; - block_to_write <= new_last_irreversible_block_num; - ++block_to_write) { - auto block = fetch_block_by_number(block_to_write); - FC_ASSERT( block, "unable to find last irreversible block to write" ); - _block_log.append(*block); - applied_irreversible_block(*block); - } - } - - /// TODO: use upper / lower bound to find - optional new_producer_schedule; - for( const auto& item : gpo.pending_active_producers ) { - if( item.first < new_last_irreversible_block_num ) { - new_producer_schedule = item.second; - } - } - if( new_producer_schedule ) { - update_or_create_producers( *new_producer_schedule ); - _db.modify( gpo, [&]( auto& props ){ - boost::range::remove_erase_if(props.pending_active_producers, - [new_last_irreversible_block_num](const typename decltype(props.pending_active_producers)::value_type& v) -> bool { - return v.first <= new_last_irreversible_block_num; - }); - if( props.active_producers.version != new_producer_schedule->version ) { - props.active_producers = *new_producer_schedule; - } - }); - } - - // Trim fork_database and undo histories - _fork_db.set_max_size(head_block_num() - new_last_irreversible_block_num + 1); - _db.commit(new_last_irreversible_block_num); -} - -void chain_controller::clear_expired_transactions() -{ try { - //Look for expired transactions in the deduplication list, and remove them. - //transactions must have expired by at least two forking windows in order to be removed. - auto& transaction_idx = _db.get_mutable_index(); - const auto& dedupe_index = transaction_idx.indices().get(); - while( (!dedupe_index.empty()) && (head_block_time() > fc::time_point(dedupe_index.begin()->expiration) ) ) { - transaction_idx.remove(*dedupe_index.begin()); - } - - //Look for expired transactions in the pending generated list, and remove them. - //transactions must have expired by at least two forking windows in order to be removed. - auto& generated_transaction_idx = _db.get_mutable_index(); - const auto& generated_index = generated_transaction_idx.indices().get(); - while( (!generated_index.empty()) && (head_block_time() > generated_index.begin()->expiration) ) { - _destroy_generated_transaction(*generated_index.begin()); - } - -} FC_CAPTURE_AND_RETHROW() } - -using boost::container::flat_set; - -account_name chain_controller::get_scheduled_producer(uint32_t slot_num)const -{ - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - uint64_t current_aslot = dpo.current_absolute_slot + slot_num; - const auto& gpo = _db.get(); - auto number_of_active_producers = gpo.active_producers.producers.size(); - auto index = current_aslot % (number_of_active_producers * config::producer_repetitions); - index /= config::producer_repetitions; - FC_ASSERT( gpo.active_producers.producers.size() > 0, "no producers defined" ); - - return gpo.active_producers.producers[index].producer_name; -} - -block_timestamp_type chain_controller::get_slot_time(uint32_t slot_num)const -{ - if( slot_num == 0) - return block_timestamp_type(); - - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - - if( dpo.head_block_number == 0 ) - { - // n.b. first block is at genesis_time plus one block interval - auto genesis_time = block_timestamp_type(dpo.time); - genesis_time.slot += slot_num; - return (fc::time_point)genesis_time; - } - - auto head_block_abs_slot = block_timestamp_type(dpo.time); - head_block_abs_slot.slot += slot_num; - return head_block_abs_slot; -} - -uint32_t chain_controller::get_slot_at_time( block_timestamp_type when )const -{ - auto first_slot_time = get_slot_time(1); - if( when < first_slot_time ) - return 0; - return when.slot - first_slot_time.slot + 1; -} - -uint32_t chain_controller::producer_participation_rate()const -{ - //const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - //return uint64_t(config::percent_100) * __builtin_popcountll(dpo.recent_slots_filled) / 64; - return static_cast(config::percent_100); // Ignore participation rate for now until we construct a better metric. -} - -void chain_controller::_set_apply_handler( account_name contract, scope_name scope, action_name action, apply_handler v ) { - _apply_handlers[contract][make_pair(scope,action)] = v; -} - -static void log_handled_exceptions(const transaction& trx) { - try { - throw; - } catch (const checktime_exceeded&) { - throw; - } FC_CAPTURE_AND_LOG((trx)); -} - -transaction_trace chain_controller::__apply_transaction( transaction_metadata& meta ) -{ try { - transaction_trace result(meta.id); - - for (const auto &act : meta.trx().context_free_actions) { - apply_context context(*this, _db, act, meta); - context.context_free = true; - context.exec(); - fc::move_append(result.action_traces, std::move(context.results.applied_actions)); - FC_ASSERT( result.deferred_transaction_requests.size() == 0 ); - } - - for (const auto &act : meta.trx().actions) { - apply_context context(*this, _db, act, meta); - context.exec(); - context.results.applied_actions.back().auths_used = act.authorization.size() - context.unused_authorizations().size(); - fc::move_append(result.action_traces, std::move(context.results.applied_actions)); - fc::move_append(result.deferred_transaction_requests, std::move(context.results.deferred_transaction_requests)); - } - - update_resource_usage(result, meta); - - update_permission_usage(meta); - record_transaction(meta.trx()); - return result; -} FC_CAPTURE_AND_RETHROW() } - -transaction_trace chain_controller::_apply_transaction( transaction_metadata& meta ) { try { - auto execute = [this](transaction_metadata& meta) -> transaction_trace { - try { - auto temp_session = _db.start_undo_session(true); - auto result = __apply_transaction(meta); - temp_session.squash(); - return result; - } catch (...) { - if (meta.is_implicit) { - try { - throw; - } FC_CAPTURE_AND_LOG((meta.id)); - transaction_trace result(meta.id); - result.status = transaction_trace::hard_fail; - return result; - } - - // if there is no sender, there is no error handling possible, rethrow - if (!meta.sender) { - throw; - } - - // log exceptions we can handle with the error handle, throws otherwise - log_handled_exceptions(meta.trx()); - - return _apply_error(meta); - } - }; - - auto start = fc::time_point::now(); - auto result = execute(meta); - result._profiling_us = fc::time_point::now() - start; - return result; -} FC_CAPTURE_AND_RETHROW( (transaction_header(meta.trx())) ) } - -transaction_trace chain_controller::_apply_error( transaction_metadata& meta ) { - transaction_trace result(meta.id); - result.status = transaction_trace::soft_fail; - - transaction etrx; - etrx.actions.emplace_back(vector{{*meta.sender,config::active_name}}, - contracts::onerror(meta.raw_data, meta.raw_data + meta.raw_size) ); - - try { - auto temp_session = _db.start_undo_session(true); - - apply_context context(*this, _db, etrx.actions.front(), meta); - context.exec(); - fc::move_append(result.action_traces, std::move(context.results.applied_actions)); - fc::move_append(result.deferred_transaction_requests, std::move(context.results.deferred_transaction_requests)); - - uint32_t act_usage = result.action_traces.size(); - - update_resource_usage(result, meta); - record_transaction(meta.trx()); - - temp_session.squash(); - return result; - - } catch (...) { - // log exceptions we can handle with the error handle, throws otherwise - log_handled_exceptions(etrx); - - // fall through to marking this tx as hard-failing - } - - // if we have an objective error, on an error handler, we return hard fail for the trx - result.status = transaction_trace::hard_fail; - return result; -} - -void chain_controller::_destroy_generated_transaction( const generated_transaction_object& gto ) { - auto& generated_transaction_idx = _db.get_mutable_index(); - _resource_limits.add_pending_account_ram_usage(gto.payer, -( config::billable_size_v + gto.packed_trx.size())); - generated_transaction_idx.remove(gto); - -} - -void chain_controller::_create_generated_transaction( const deferred_transaction& dto ) { - size_t trx_size = fc::raw::pack_size(dto); - _resource_limits.add_pending_account_ram_usage( - dto.payer, - (config::billable_size_v + (int64_t)trx_size) - ); - - _db.create([&](generated_transaction_object &obj) { - obj.trx_id = dto.id(); - obj.sender = dto.sender; - obj.sender_id = dto.sender_id; - obj.payer = dto.payer; - obj.expiration = dto.expiration; - obj.delay_until = dto.execute_after; - obj.published = head_block_time(); - obj.packed_trx.resize(trx_size); - fc::datastream ds(obj.packed_trx.data(), obj.packed_trx.size()); - fc::raw::pack(ds, dto); - }); -} - -vector chain_controller::push_deferred_transactions( bool flush, uint32_t skip ) -{ try { - if( !_pending_block ) { - _start_pending_block( true ); - } - - return with_skip_flags(skip, [&]() { - return _db.with_write_lock([&]() { - return _push_deferred_transactions( flush ); - }); - }); -} FC_CAPTURE_AND_RETHROW() } - -vector chain_controller::_push_deferred_transactions( bool flush ) -{ - FC_ASSERT( _pending_block, " block not started" ); - - if (flush && _pending_cycle_trace && _pending_cycle_trace->shard_traces.size() > 0) { - // TODO: when we go multithreaded this will need a better way to see if there are flushable - // deferred transactions in the shards - auto maybe_start_new_cycle = [&]() { - for (const auto &st: _pending_cycle_trace->shard_traces) { - for (const auto &tr: st.transaction_traces) { - for (const auto &req: tr.deferred_transaction_requests) { - if ( req.contains() ) { - const auto& dt = req.get(); - if ( fc::time_point(dt.execute_after) <= head_block_time() ) { - // force a new cycle and break out - _finalize_pending_cycle(); - _start_pending_cycle(); - return; - } - } - } - } - } - }; - - maybe_start_new_cycle(); - } - - const auto& generated_transaction_idx = _db.get_index(); - auto& generated_index = generated_transaction_idx.indices().get(); - vector candidates; - - for( auto itr = generated_index.begin(); itr != generated_index.end() && (head_block_time() >= itr->delay_until); ++itr) { - const auto >rx = *itr; - candidates.emplace_back(>rx); - } - - optional processing_deadline; - if (_limits.max_deferred_transactions_us.count() > 0) { - processing_deadline = fc::time_point::now() + _limits.max_deferred_transactions_us; - } - - vector res; - for (const auto* trx_p: candidates) { - if (!is_known_transaction(trx_p->trx_id)) { - try { - auto trx = fc::raw::unpack(trx_p->packed_trx.data(), trx_p->packed_trx.size()); - transaction_metadata mtrx (trx, trx_p->published, trx.sender, trx.sender_id, trx_p->packed_trx.data(), trx_p->packed_trx.size(), processing_deadline); - res.push_back( _push_transaction(std::move(mtrx)) ); - } FC_CAPTURE_AND_LOG((trx_p->trx_id)(trx_p->sender)); - } - - _destroy_generated_transaction(*trx_p); - - if ( !processing_deadline || *processing_deadline <= fc::time_point::now() ) { - break; - } - } - return res; -} - -const apply_handler* chain_controller::find_apply_handler( account_name receiver, account_name scope, action_name act ) const -{ - auto native_handler_scope = _apply_handlers.find( receiver ); - if( native_handler_scope != _apply_handlers.end() ) { - auto handler = native_handler_scope->second.find( make_pair( scope, act ) ); - if( handler != native_handler_scope->second.end() ) - return &handler->second; - } - return nullptr; -} - -template -transaction_trace chain_controller::wrap_transaction_processing( transaction_metadata&& data, TransactionProcessing trx_processing ) -{ try { - FC_ASSERT( _pending_block, " block not started" ); - - if (_limits.max_push_transaction_us.count() > 0) { - auto newval = fc::time_point::now() + _limits.max_push_transaction_us; - if ( !data.processing_deadline || newval < *data.processing_deadline ) { - data.processing_deadline = newval; - } - } - - const transaction& trx = data.trx(); - - FC_ASSERT( trx.region == 0, "currently only region 0 is supported" ); - - //wdump((transaction_header(trx))); - - auto temp_session = _db.start_undo_session(true); - - // for now apply the transaction serially but schedule it according to those invariants - - auto result = trx_processing(data); - _resource_limits.synchronize_account_ram_usage(); - - auto& bcycle = _pending_block->regions.back().cycles_summary.back(); - auto& bshard = bcycle.front(); - auto& bshard_trace = _pending_cycle_trace->shard_traces.at(0); - - record_locks_for_data_access(result, bshard_trace.read_locks, bshard_trace.write_locks); - - bshard.transactions.emplace_back( result ); - - if( data.raw_trx.valid() && !data.is_implicit ) { // if an input transaction - result.packed_trx_digest = data.packed_digest; - } - - result.region_id = 0; // Currently we only support region 0. - result.cycle_index = _pending_block->regions.back().cycles_summary.size() - 1; - result.shard_index = 0; // Currently we only have one shard per cycle. - - bshard_trace.append(result); - - // The transaction applied successfully. Merge its changes into the pending block session. - temp_session.squash(); - - //wdump((transaction_header(data.trx()))); - _pending_transaction_metas.emplace_back( move(data) ); - - return result; -} FC_CAPTURE_AND_RETHROW( (transaction_header(data.trx())) ) } - -} } /// eosio::chain diff --git a/libraries/chain/contracts/eosio_contract.cpp b/libraries/chain/contracts/eosio_contract.cpp deleted file mode 100644 index cec1672e254..00000000000 --- a/libraries/chain/contracts/eosio_contract.cpp +++ /dev/null @@ -1,568 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace eosio { namespace chain { namespace contracts { - -void validate_authority_precondition( const apply_context& context, const authority& auth ) { - for(const auto& a : auth.accounts) { - context.db.get(a.permission.actor); - context.db.get(boost::make_tuple(a.permission.actor, a.permission.permission)); - } -} - -/** - * This method is called assuming precondition_system_newaccount succeeds a - */ -void apply_eosio_newaccount(apply_context& context) { - auto create = context.act.data_as(); - try { - context.require_authorization(create.creator); - context.require_write_lock( config::eosio_auth_scope ); - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - - EOS_ASSERT( validate(create.owner), action_validate_exception, "Invalid owner authority"); - EOS_ASSERT( validate(create.active), action_validate_exception, "Invalid active authority"); - EOS_ASSERT( validate(create.recovery), action_validate_exception, "Invalid recovery authority"); - - auto& db = context.mutable_db; - - auto name_str = name(create.name).to_string(); - - EOS_ASSERT( !create.name.empty(), action_validate_exception, "account name cannot be empty" ); - EOS_ASSERT( name_str.size() <= 12, action_validate_exception, "account names can only be 12 chars long" ); - - // Check if the creator is privileged - const auto &creator = db.get(create.creator); - if( !creator.privileged ) { - EOS_ASSERT( name_str.find( "eosio." ) != 0, action_validate_exception, - "only privileged accounts can have names that start with 'eosio.'" ); - } - - auto existing_account = db.find(create.name); - EOS_ASSERT(existing_account == nullptr, action_validate_exception, - "Cannot create account named ${name}, as that name is already taken", - ("name", create.name)); - - for( const auto& auth : { create.owner, create.active, create.recovery } ){ - validate_authority_precondition( context, auth ); - } - - const auto& new_account = db.create([&create, &context](account_object& a) { - a.name = create.name; - a.creation_date = context.controller.head_block_time(); - }); - resources.initialize_account(create.name); - resources.add_pending_account_ram_usage( - create.creator, - (int64_t)config::overhead_per_account_ram_bytes - ); - - auto create_permission = [owner=create.name, &db, &context, &resources](const permission_name& name, permission_object::id_type parent, authority &&auth) { - const auto& result = db.create([&](permission_object& p) { - p.name = name; - p.parent = parent; - p.owner = owner; - p.auth = std::move(auth); - }); - - resources.add_pending_account_ram_usage( - owner, - (int64_t)(config::billable_size_v + result.auth.get_billable_size()) - ); - - return result; - }; - - // If a parent_id of 0 is going to be used to indicate the absence of a parent, then we need to make sure that the chain - // initializes permission_index with a dummy object that reserves the id of 0. - const auto& owner_permission = create_permission(config::owner_name, 0, std::move(create.owner)); - create_permission(config::active_name, owner_permission.id, std::move(create.active)); - -} FC_CAPTURE_AND_RETHROW( (create) ) } - -void apply_eosio_setcode(apply_context& context) { - auto& db = context.mutable_db; - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto act = context.act.data_as(); - context.require_authorization(act.account); - context.require_write_lock( config::eosio_auth_scope ); - - FC_ASSERT( act.vmtype == 0 ); - FC_ASSERT( act.vmversion == 0 ); - - auto code_id = fc::sha256::hash( act.code.data(), (uint32_t)act.code.size() ); - - wasm_interface::validate(act.code); - - const auto& account = db.get(act.account); - - int64_t code_size = (int64_t)act.code.size(); - int64_t old_size = (int64_t)account.code.size() * config::setcode_ram_bytes_multiplier; - int64_t new_size = code_size * config::setcode_ram_bytes_multiplier; - - - FC_ASSERT( account.code_version != code_id, "contract is already running this version of code" ); -// wlog( "set code: ${size}", ("size",act.code.size())); - db.modify( account, [&]( auto& a ) { - /** TODO: consider whether a microsecond level local timestamp is sufficient to detect code version changes*/ - #warning TODO: update setcode message to include the hash, then validate it in validate - a.code_version = code_id; - // Added resize(0) here to avoid bug in boost vector container - a.code.resize( 0 ); - a.code.resize( code_size ); - a.last_code_update = context.controller.head_block_time(); - memcpy( a.code.data(), act.code.data(), code_size ); - - }); - - if (new_size != old_size) { - resources.add_pending_account_ram_usage( - act.account, - new_size - old_size - ); - } -} - -void apply_eosio_setabi(apply_context& context) { - auto& db = context.mutable_db; - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto act = context.act.data_as(); - - context.require_authorization(act.account); - - // if system account append native abi - if ( act.account == eosio::chain::config::system_account_name ) { - act.abi = chain_initializer::eos_contract_abi(act.abi); - } - /// if an ABI is specified make sure it is well formed and doesn't - /// reference any undefined types - abi_serializer(act.abi).validate(); - // todo: figure out abi serialization location - - const auto& account = db.get(act.account); - - int64_t old_size = (int64_t)account.abi.size(); - int64_t new_size = (int64_t)fc::raw::pack_size(act.abi); - - db.modify( account, [&]( auto& a ) { - a.set_abi( act.abi ); - }); - - if (new_size != old_size) { - resources.add_pending_account_ram_usage( - act.account, - new_size - old_size - ); - } -} - -void apply_eosio_updateauth(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - - auto update = context.act.data_as(); - context.require_authorization(update.account); // only here to mark the single authority on this action as used - - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto& db = context.mutable_db; - - EOS_ASSERT(!update.permission.empty(), action_validate_exception, "Cannot create authority with empty name"); - EOS_ASSERT( update.permission.to_string().find( "eosio." ) != 0, action_validate_exception, - "Permission names that start with 'eosio.' are reserved" ); - EOS_ASSERT(update.permission != update.parent, action_validate_exception, "Cannot set an authority as its own parent"); - db.get(update.account); - EOS_ASSERT(validate(update.data), action_validate_exception, - "Invalid authority: ${auth}", ("auth", update.data)); - if( update.permission == config::active_name ) - EOS_ASSERT(update.parent == config::owner_name, action_validate_exception, "Cannot change active authority's parent from owner", ("update.parent", update.parent) ); - if (update.permission == config::owner_name) - EOS_ASSERT(update.parent.empty(), action_validate_exception, "Cannot change owner authority's parent"); - else - EOS_ASSERT(!update.parent.empty(), action_validate_exception, "Only owner permission can have empty parent" ); - - auto max_delay = context.controller.get_global_properties().configuration.max_transaction_delay; - EOS_ASSERT( update.delay <= max_delay, action_validate_exception, "Cannot set delay longer than max_transacton_delay, which is ${max_delay} seconds", ("max_delay", max_delay) ); - - validate_authority_precondition(context, update.data); - - auto permission = db.find(boost::make_tuple(update.account, update.permission)); - - // If a parent_id of 0 is going to be used to indicate the absence of a parent, then we need to make sure that the chain - // initializes permission_index with a dummy object that reserves the id of 0. - permission_object::id_type parent_id = 0; - if(update.permission != config::owner_name) { - auto& parent = db.get(boost::make_tuple(update.account, update.parent)); - parent_id = parent.id; - } - - if (permission) { - EOS_ASSERT(parent_id == permission->parent, action_validate_exception, - "Changing parent authority is not currently supported"); - - - int64_t old_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); - - // TODO/QUESTION: If we are updating an existing permission, should we check if the message declared - // permission satisfies the permission we want to modify? - db.modify(*permission, [&update, &parent_id, &context](permission_object& po) { - po.auth = update.data; - po.parent = parent_id; - po.last_updated = context.controller.head_block_time(); - po.delay = fc::seconds(update.delay); - }); - - int64_t new_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); - - resources.add_pending_account_ram_usage( - permission->owner, - new_size - old_size - ); - } else { - // TODO/QUESTION: If we are creating a new permission, should we check if the message declared - // permission satisfies the parent permission? - const auto& p = db.create([&update, &parent_id, &context](permission_object& po) { - po.name = update.permission; - po.owner = update.account; - po.auth = update.data; - po.parent = parent_id; - po.last_updated = context.controller.head_block_time(); - po.delay = fc::seconds(update.delay); - }); - - resources.add_pending_account_ram_usage( - p.owner, - (int64_t)(config::billable_size_v + p.auth.get_billable_size()) - ); - - } -} - -void apply_eosio_deleteauth(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - - auto remove = context.act.data_as(); - context.require_authorization(remove.account); // only here to mark the single authority on this action as used - - EOS_ASSERT(remove.permission != config::active_name, action_validate_exception, "Cannot delete active authority"); - EOS_ASSERT(remove.permission != config::owner_name, action_validate_exception, "Cannot delete owner authority"); - - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto& db = context.mutable_db; - - const auto& permission = db.get(boost::make_tuple(remove.account, remove.permission)); - - { // Check for children - const auto& index = db.get_index(); - auto range = index.equal_range(permission.id); - EOS_ASSERT(range.first == range.second, action_validate_exception, - "Cannot delete an authority which has children. Delete the children first"); - } - - { // Check for links to this permission - const auto& index = db.get_index(); - auto range = index.equal_range(boost::make_tuple(remove.account, remove.permission)); - EOS_ASSERT(range.first == range.second, action_validate_exception, - "Cannot delete a linked authority. Unlink the authority first"); - } - - resources.add_pending_account_ram_usage( - permission.owner, - -(int64_t)(config::billable_size_v + permission.auth.get_billable_size()) - ); - db.remove(permission); -} - -void apply_eosio_linkauth(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto requirement = context.act.data_as(); - try { - EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty"); - - context.require_authorization(requirement.account); // only here to mark the single authority on this action as used - - auto& db = context.mutable_db; - const auto *account = db.find(requirement.account); - EOS_ASSERT(account != nullptr, account_query_exception, - "Failed to retrieve account: ${account}", ("account", requirement.account)); // Redundant? - const auto *code = db.find(requirement.code); - EOS_ASSERT(code != nullptr, account_query_exception, - "Failed to retrieve code for account: ${account}", ("account", requirement.code)); - if( requirement.requirement != config::eosio_any_name ) { - const auto *permission = db.find(requirement.requirement); - EOS_ASSERT(permission != nullptr, permission_query_exception, - "Failed to retrieve permission: ${permission}", ("permission", requirement.requirement)); - } - - auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type); - auto link = db.find(link_key); - - if( link ) { - EOS_ASSERT(link->required_permission != requirement.requirement, action_validate_exception, - "Attempting to update required authority, but new requirement is same as old"); - db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) { - link.required_permission = requirement; - }); - } else { - const auto& l = db.create([&requirement](permission_link_object& link) { - link.account = requirement.account; - link.code = requirement.code; - link.message_type = requirement.type; - link.required_permission = requirement.requirement; - }); - - resources.add_pending_account_ram_usage( - l.account, - (int64_t)(config::billable_size_v) - ); - } - } FC_CAPTURE_AND_RETHROW((requirement)) -} - -void apply_eosio_unlinkauth(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - - auto& resources = context.mutable_controller.get_mutable_resource_limits_manager(); - auto& db = context.mutable_db; - auto unlink = context.act.data_as(); - - context.require_authorization(unlink.account); // only here to mark the single authority on this action as used - - auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type); - auto link = db.find(link_key); - EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found"); - resources.add_pending_account_ram_usage( - link->account, - -(int64_t)(config::billable_size_v) - ); - - db.remove(*link); -} - - -void apply_eosio_onerror(apply_context& context) { - FC_ASSERT(context.trx_meta.sender.valid(), "onerror action cannot be called directly"); - context.require_recipient(*context.trx_meta.sender); -} - -static const abi_serializer& get_abi_serializer() { - static optional _abi_serializer; - if (!_abi_serializer) { - _abi_serializer.emplace(chain_initializer::eos_contract_abi(abi_def())); - } - - return *_abi_serializer; -} - -static optional get_pending_recovery(apply_context& context, account_name account ) { - const uint64_t id = account; - const auto table = N(recovery); - const auto iter = context.db_find_i64(config::system_account_name, account, table, id); - if (iter != -1) { - const auto buffer_size = context.db_get_i64(iter, nullptr, 0); - bytes value(buffer_size); - - const auto written_size = context.db_get_i64(iter, value.data(), buffer_size); - assert(written_size == buffer_size); - - return get_abi_serializer().binary_to_variant("pending_recovery", value); - } - - return optional(); -} - -static auto get_account_creation(const apply_context& context, const account_name& account) { - auto const& accnt = context.db.get(account); - return (time_point)accnt.creation_date; -}; - -static auto get_permission_last_used(const apply_context& context, const account_name& account, const permission_name& permission) { - auto const* perm = context.db.find(boost::make_tuple(account, permission)); - if (perm) { - return optional(perm->last_used); - } - - return optional(); -}; - -void apply_eosio_postrecovery(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - - FC_ASSERT(context.act.authorization.size() == 1, "Recovery Message must have exactly one authorization"); - - auto recover_act = context.act.data_as(); - const auto &auth = context.act.authorization.front(); - const auto& account = recover_act.account; - context.require_write_lock(account); - - FC_ASSERT(!get_pending_recovery(context, account), "Account ${account} already has a pending recovery request!", ("account",account)); - - fc::microseconds delay_lock; - auto now = context.controller.head_block_time(); - if (auth.actor == account && auth.permission == N(active)) { - // process owner recovery from active - delay_lock = fc::days(30); - } else { - // process lost password - - auto owner_last_used = get_permission_last_used(context, account, N(owner)); - auto active_last_used = get_permission_last_used(context, account, N(active)); - - if (!owner_last_used || !active_last_used) { - auto account_creation = get_account_creation(context, account); - if (!owner_last_used) { - owner_last_used.emplace(account_creation); - } - - if (!active_last_used) { - active_last_used.emplace(account_creation); - } - } - - FC_ASSERT(*owner_last_used <= now - fc::days(30), "Account ${account} has had owner key activity recently and cannot be recovered yet!", ("account",account)); - FC_ASSERT(*active_last_used <= now - fc::days(30), "Account ${account} has had active key activity recently and cannot be recovered yet!", ("account",account)); - - delay_lock = fc::days(7); - } - - variant update; - fc::to_variant( updateauth { - .account = account, - .permission = N(owner), - .parent = 0, - .data = recover_act.data - }, update); - - const uint128_t request_id = context.controller.transaction_id_to_sender_id(context.trx_meta.id); - auto record_data = mutable_variant_object() - ("account", account) - ("request_id", request_id) - ("update", update) - ("memo", recover_act.memo); - - deferred_transaction dtrx; - dtrx.sender = config::system_account_name; - dtrx.sender_id = request_id; - dtrx.payer = config::system_account_name; // NOTE: we pre-reserve capacity for this during create account - dtrx.region = 0; - dtrx.execute_after = context.controller.head_block_time() + delay_lock; - dtrx.set_reference_block(context.controller.head_block_id()); - dtrx.expiration = dtrx.execute_after + fc::seconds(60); - dtrx.actions.emplace_back(vector{{account,config::active_name}}, - passrecovery { account }); - - context.execute_deferred(std::move(dtrx)); - - auto data = get_abi_serializer().variant_to_binary("pending_recovery", record_data); - const uint64_t id = account; - const uint64_t table = N(recovery); - const auto payer = account; - - const auto iter = context.db_find_i64(config::system_account_name, account, table, id); - if (iter == -1) { - context.db_store_i64(account, table, payer, id, (const char*)data.data(), data.size()); - } else { - context.db_update_i64(iter, payer, (const char*)data.data(), data.size()); - } - - context.console_append_formatted("Recovery Started for account ${account} : ${memo}\n", mutable_variant_object()("account", account)("memo", recover_act.memo)); -} - -static void remove_pending_recovery(apply_context& context, const account_name& account) { - const auto iter = context.db_find_i64(config::system_account_name, account, N(recovery), account); - if (iter != -1) { - context.db_remove_i64(iter); - } -} - -void apply_eosio_passrecovery(apply_context& context) { - auto pass_act = context.act.data_as(); - const auto& account = pass_act.account; - - // ensure this is only processed if it is a deferred transaction from the system account - FC_ASSERT(context.trx_meta.sender && *context.trx_meta.sender == config::system_account_name); - context.require_authorization(account); - - auto maybe_recovery = get_pending_recovery(context, account); - FC_ASSERT(maybe_recovery, "No pending recovery found for account ${account}", ("account", account)); - auto recovery = *maybe_recovery; - - updateauth update; - fc::from_variant(recovery["update"], update); - - action act(vector{{account,config::owner_name}}, update); - context.execute_inline(move(act)); - - remove_pending_recovery(context, account); - context.console_append_formatted("Account ${account} successfully recovered!\n", mutable_variant_object()("account", account)); -} - -void apply_eosio_vetorecovery(apply_context& context) { - context.require_write_lock( config::eosio_auth_scope ); - auto pass_act = context.act.data_as(); - const auto& account = pass_act.account; - context.require_authorization(account); - - auto maybe_recovery = get_pending_recovery(context, account); - FC_ASSERT(maybe_recovery, "No pending recovery found for account ${account}", ("account", account)); - auto recovery = *maybe_recovery; - - context.cancel_deferred(recovery["request_id"].as()); - - remove_pending_recovery(context, account); - context.console_append_formatted("Recovery for account ${account} vetoed!\n", mutable_variant_object()("account", account)); -} - -void apply_eosio_canceldelay(apply_context& context) { - auto cancel = context.act.data_as(); - context.require_authorization(cancel.canceling_auth.actor); // only here to mark the single authority on this action as used - - const auto& trx_id = cancel.trx_id; - - const auto& generated_transaction_idx = context.controller.get_database().get_index(); - const auto& generated_index = generated_transaction_idx.indices().get(); - const auto& itr = generated_index.lower_bound(trx_id); - FC_ASSERT (itr != generated_index.end() && itr->sender == config::system_account_name && itr->trx_id == trx_id, - "cannot cancel trx_id=${tid}, there is no deferred transaction with that transaction id",("tid", trx_id)); - - auto dtrx = fc::raw::unpack(itr->packed_trx.data(), itr->packed_trx.size()); - bool found = false; - for( const auto& act : dtrx.actions ) { - for( const auto& auth : act.authorization ) { - if( auth == cancel.canceling_auth ) { - found = true; - break; - } - } - if( found ) break; - } - - FC_ASSERT (found, "canceling_auth in canceldelay action was not found as authorization in the original delayed transaction"); - - context.cancel_deferred(context.controller.transaction_id_to_sender_id(trx_id)); -} - -} } } // namespace eosio::chain::contracts diff --git a/libraries/chain/contracts/genesis_state.cpp b/libraries/chain/contracts/genesis_state.cpp deleted file mode 100644 index 92ec2e52663..00000000000 --- a/libraries/chain/contracts/genesis_state.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#include - -// these are required to serialize a genesis_state -#include // required for gcc in release mode - -namespace eosio { namespace chain { namespace contracts { - - -chain::chain_id_type genesis_state_type::compute_chain_id() const { - return initial_chain_id; -} - -} } } // namespace eosio::chain::contracts diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp new file mode 100644 index 00000000000..abd2cc8f053 --- /dev/null +++ b/libraries/chain/controller.cpp @@ -0,0 +1,1367 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace eosio { namespace chain { + +using resource_limits::resource_limits_manager; + + +struct pending_state { + pending_state( database::session&& s ) + :_db_session( move(s) ){} + + database::session _db_session; + + block_state_ptr _pending_block_state; + + vector _actions; + + block_context _block_ctx; + + void push() { + _db_session.push(); + } +}; + +struct controller_impl { + controller& self; + chainbase::database db; + block_log blog; + optional pending; + block_state_ptr head; + fork_database fork_db; + wasm_interface wasmif; + resource_limits_manager resource_limits; + authorization_manager authorization; + controller::config conf; + bool replaying = false; + + typedef pair handler_key; + map< account_name, map > apply_handlers; + + /** + * Transactions that were undone by pop_block or abort_block, transactions + * are removed from this list if they are re-applied in other blocks. Producers + * can query this list when scheduling new transactions into blocks. + */ + map unapplied_transactions; + + void pop_block() { + auto prev = fork_db.get_block( head->header.previous ); + FC_ASSERT( prev, "attempt to pop beyond last irreversible block" ); + for( const auto& t : head->trxs ) + unapplied_transactions[t->signed_id] = t; + head = prev; + db.undo(); + } + + + void set_apply_handler( account_name receiver, account_name contract, action_name action, apply_handler v ) { + apply_handlers[receiver][make_pair(contract,action)] = v; + } + + controller_impl( const controller::config& cfg, controller& s ) + :self(s), + db( cfg.shared_memory_dir, + cfg.read_only ? database::read_only : database::read_write, + cfg.shared_memory_size ), + blog( cfg.block_log_dir ), + fork_db( cfg.shared_memory_dir ), + wasmif( cfg.wasm_runtime ), + resource_limits( db ), + authorization( s, db ), + conf( cfg ) + { + +#define SET_APP_HANDLER( receiver, contract, action) \ + set_apply_handler( #receiver, #contract, #action, &BOOST_PP_CAT(apply_, BOOST_PP_CAT(contract, BOOST_PP_CAT(_,action) ) ) ) + + SET_APP_HANDLER( eosio, eosio, newaccount ); + SET_APP_HANDLER( eosio, eosio, setcode ); + SET_APP_HANDLER( eosio, eosio, setabi ); + SET_APP_HANDLER( eosio, eosio, updateauth ); + SET_APP_HANDLER( eosio, eosio, deleteauth ); + SET_APP_HANDLER( eosio, eosio, linkauth ); + SET_APP_HANDLER( eosio, eosio, unlinkauth ); +/* + SET_APP_HANDLER( eosio, eosio, postrecovery ); + SET_APP_HANDLER( eosio, eosio, passrecovery ); + SET_APP_HANDLER( eosio, eosio, vetorecovery ); +*/ + + SET_APP_HANDLER( eosio, eosio, canceldelay ); + + fork_db.irreversible.connect( [&]( auto b ) { + on_irreversible(b); + }); + + } + + /** + * Plugins / observers listening to signals emited (such as accepted_transaction) might trigger + * errors and throw exceptions. Unless those exceptions are caught it could impact consensus and/or + * cause a node to fork. + * + * If it is ever desirable to let a signal handler bubble an exception out of this method + * a full audit of its uses needs to be undertaken. + * + */ + template + void emit( const Signal& s, Arg&& a ) { + try { + s(std::forward(a)); + } catch ( ... ) { + elog( "signal handler threw exception" ); + } + } + + void on_irreversible( const block_state_ptr& s ) { + if( !blog.head() ) + blog.read_head(); + + const auto& log_head = blog.head(); + FC_ASSERT( log_head ); + auto lh_block_num = log_head->block_num(); + + if( s->block_num - 1 == lh_block_num ) { + FC_ASSERT( s->block->previous == log_head->id(), "irreversible doesn't link to block log head" ); + blog.append( s->block ); + } else if( s->block_num -1 > lh_block_num ) { + wlog( "skipped blocks..." ); + edump((s->block_num)(log_head->block_num())); + if( s->block_num == log_head->block_num() ) { + FC_ASSERT( s->id == log_head->id(), "", ("s->id",s->id)("hid",log_head->id()) ); + } + } + emit( self.irreversible_block, s ); + db.commit( s->block_num ); + } + + void init() { + + /** + * The fork database needs an initial block_state to be set before + * it can accept any new blocks. This initial block state can be found + * in the database (whose head block state should be irreversible) or + * it would be the genesis state. + */ + if( !head ) { + initialize_fork_db(); // set head to genesis state + } + + while( db.revision() > head->block_num ) { + wlog( "warning database revision greater than head block, undoing pending changes" ); + db.undo(); + } + + FC_ASSERT( db.revision() == head->block_num, "fork database is inconsistent with shared memory", + ("db",db.revision())("head",head->block_num) ); + + /** + * The undoable state contains state transitions from blocks + * in the fork database that could be reversed. Because this + * is a new startup and the fork database is empty, we must + * unwind that pending state. This state will be regenerated + * when we catch up to the head block later. + */ + //clear_all_undo(); + } + + ~controller_impl() { + pending.reset(); + fork_db.close(); + + edump((db.revision())(head->block_num)(blog.read_head()->block_num())); + + db.flush(); + } + + void add_indices() { + db.add_index(); + db.add_index(); + + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + db.add_index(); + + authorization.add_indices(); + resource_limits.add_indices(); + } + + void clear_all_undo() { + // Rewind the database to the last irreversible block + db.with_write_lock([&] { + db.undo_all(); + /* + FC_ASSERT(db.revision() == self.head_block_num(), + "Chainbase revision does not match head block num", + ("rev", db.revision())("head_block", self.head_block_num())); + */ + }); + } + + /** + * Sets fork database head to the genesis state. + */ + void initialize_fork_db() { + wlog( " Initializing new blockchain with genesis state " ); + producer_schedule_type initial_schedule{ 0, {{N(eosio), conf.genesis.initial_key}} }; + + block_header_state genheader; + genheader.active_schedule = initial_schedule; + genheader.pending_schedule = initial_schedule; + genheader.pending_schedule_hash = fc::sha256::hash(initial_schedule); + genheader.header.timestamp = conf.genesis.initial_timestamp; + genheader.header.action_mroot = conf.genesis.compute_chain_id(); + genheader.id = genheader.header.id(); + genheader.block_num = genheader.header.block_num(); + + head = std::make_shared( genheader ); + head->block = std::make_shared(genheader.header); + fork_db.set( head ); + db.set_revision( head->block_num ); + + initialize_database(); + + auto end = blog.read_head(); + if( end && end->block_num() > 1 ) { + replaying = true; + ilog( "existing block log, attempting to replay ${n} blocks", ("n",end->block_num()) ); + + auto start = fc::time_point::now(); + while( auto next = blog.read_block_by_num( head->block_num + 1 ) ) { + self.push_block( next ); + if( next->block_num() % 10 == 0 ) { + std::cerr << std::setw(10) << next->block_num() << " of " << end->block_num() <<"\r"; + } + } + std::cerr<< "\n"; + auto end = fc::time_point::now(); + ilog( "replayed blocks in ${n} seconds", ("n", (end-start).count()/1000000.0) ); + replaying = false; + + } else if( !end ) { + blog.append( head->block ); + } + } + + void create_native_account( account_name name, const authority& owner, const authority& active, bool is_privileged = false ) { + db.create([&](auto& a) { + a.name = name; + a.creation_date = conf.genesis.initial_timestamp; + a.privileged = is_privileged; + + if( name == config::system_account_name ) { + a.set_abi(eosio_contract_abi(abi_def())); + } + }); + db.create([&](auto & a) { + a.name = name; + }); + + const auto& owner_permission = authorization.create_permission(name, config::owner_name, 0, + owner, conf.genesis.initial_timestamp ); + const auto& active_permission = authorization.create_permission(name, config::active_name, owner_permission.id, + active, conf.genesis.initial_timestamp ); + + resource_limits.initialize_account(name); + + int64_t ram_delta = config::overhead_per_account_ram_bytes; + ram_delta += 2*config::billable_size_v; + ram_delta += owner_permission.auth.get_billable_size(); + ram_delta += active_permission.auth.get_billable_size(); + + resource_limits.add_pending_ram_usage(name, ram_delta); + resource_limits.verify_account_ram_usage(name); + } + + void initialize_database() { + // Initialize block summary index + for (int i = 0; i < 0x10000; i++) + db.create([&](block_summary_object&) {}); + + const auto& tapos_block_summary = db.get(1); + db.modify( tapos_block_summary, [&]( auto& bs ) { + bs.block_id = head->id; + }); + + db.create([&](auto& gpo ){ + gpo.configuration = conf.genesis.initial_configuration; + }); + db.create([](auto&){}); + + authorization.initialize_database(); + resource_limits.initialize_database(); + + authority system_auth(conf.genesis.initial_key); + create_native_account( config::system_account_name, system_auth, system_auth, true ); + + auto empty_authority = authority(1, {}, {}); + auto active_producers_authority = authority(1, {}, {}); + active_producers_authority.accounts.push_back({{config::system_account_name, config::active_name}, 1}); + + create_native_account( config::null_account_name, empty_authority, empty_authority ); + create_native_account( config::producers_account_name, empty_authority, active_producers_authority ); + const auto& active_permission = authorization.get_permission({config::producers_account_name, config::active_name}); + const auto& majority_permission = authorization.create_permission( config::producers_account_name, + config::majority_producers_permission_name, + active_permission.id, + active_producers_authority, + conf.genesis.initial_timestamp ); + const auto& minority_permission = authorization.create_permission( config::producers_account_name, + config::minority_producers_permission_name, + majority_permission.id, + active_producers_authority, + conf.genesis.initial_timestamp ); + } + + + + void commit_block( bool add_to_fork_db ) { + if( add_to_fork_db ) { + pending->_pending_block_state->validated = true; + auto new_bsp = fork_db.add( pending->_pending_block_state ); + emit( self.accepted_block_header, pending->_pending_block_state ); + head = fork_db.head(); + FC_ASSERT( new_bsp == head, "committed block did not become the new head in fork database" ); + } + + // ilog((fc::json::to_pretty_string(*pending->_pending_block_state->block))); + emit( self.accepted_block, pending->_pending_block_state ); + pending->push(); + pending.reset(); + + self.log_irreversible_blocks(); + } + + // The returned scoped_exit should not exceed the lifetime of the pending which existed when make_block_restore_point was called. + fc::scoped_exit> make_block_restore_point() { + auto orig_block_transactions_size = pending->_pending_block_state->block->transactions.size(); + auto orig_state_transactions_size = pending->_pending_block_state->trxs.size(); + auto orig_state_actions_size = pending->_actions.size(); + + std::function callback = [this, + orig_block_transactions_size, + orig_state_transactions_size, + orig_state_actions_size]() + { + pending->_pending_block_state->block->transactions.resize(orig_block_transactions_size); + pending->_pending_block_state->trxs.resize(orig_state_transactions_size); + pending->_actions.resize(orig_state_actions_size); + }; + + return fc::make_scoped_exit( std::move(callback) ); + } + + transaction_trace_ptr apply_onerror( const generated_transaction_object& gto, + fc::time_point deadline, + fc::time_point start, + uint32_t billed_cpu_time_us) { + signed_transaction etrx; + // Deliver onerror action containing the failed deferred transaction directly back to the sender. + etrx.actions.emplace_back( vector{}, + onerror( gto.sender_id, gto.packed_trx.data(), gto.packed_trx.size() ) ); + etrx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to avoid appearing expired + etrx.set_reference_block( self.head_block_id() ); + + transaction_context trx_context( self, etrx, etrx.id(), start ); + trx_context.deadline = deadline; + trx_context.billed_cpu_time_us = billed_cpu_time_us; + transaction_trace_ptr trace = trx_context.trace; + try { + trx_context.init_for_implicit_trx(); + trx_context.published = gto.published; + trx_context.trace->action_traces.emplace_back(); + trx_context.dispatch_action( trx_context.trace->action_traces.back(), etrx.actions.back(), gto.sender ); + trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful + + auto restore = make_block_restore_point(); + trace->receipt = push_receipt( gto.trx_id, transaction_receipt::soft_fail, + trx_context.billed_cpu_time_us, trace->net_usage ); + fc::move_append( pending->_actions, move(trx_context.executed) ); + + emit( self.applied_transaction, trace ); + + trx_context.squash(); + restore.cancel(); + return trace; + } catch( const fc::exception& e ) { + trace->except = e; + trace->except_ptr = std::current_exception(); + } + return trace; + } + + void remove_scheduled_transaction( const generated_transaction_object& gto ) { + resource_limits.add_pending_ram_usage( + gto.payer, + -(config::billable_size_v + gto.packed_trx.size()) + ); + // No need to verify_account_ram_usage since we are only reducing memory + + db.remove( gto ); + } + + bool failure_is_subjective( const fc::exception& e ) { + auto code = e.code(); + return (code == block_net_usage_exceeded::code_value) || + (code == block_cpu_usage_exceeded::code_value) || + (code == deadline_exception::code_value) || + (code == leeway_deadline_exception::code_value); + } + + transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline, uint32_t billed_cpu_time_us ) { + const auto& idx = db.get_index(); + auto itr = idx.find( trxid ); + FC_ASSERT( itr != idx.end(), "unknown transaction" ); + return push_scheduled_transaction( *itr, deadline, billed_cpu_time_us ); + } + + transaction_trace_ptr push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline, uint32_t billed_cpu_time_us ) + { try { + auto undo_session = db.start_undo_session(true); + fc::datastream ds( gto.packed_trx.data(), gto.packed_trx.size() ); + + auto remove_retained_state = fc::make_scoped_exit([&, this](){ + remove_scheduled_transaction(gto); + }); + + FC_ASSERT( gto.delay_until <= self.pending_block_time(), "this transaction isn't ready", + ("gto.delay_until",gto.delay_until)("pbt",self.pending_block_time()) ); + if( gto.expiration < self.pending_block_time() ) { + auto trace = std::make_shared(); + trace->id = gto.trx_id; + trace->scheduled = false; + trace->receipt = push_receipt( gto.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction + return trace; + } + + signed_transaction dtrx; + fc::raw::unpack(ds,static_cast(dtrx) ); + + transaction_context trx_context( self, dtrx, gto.trx_id ); + trx_context.deadline = deadline; + trx_context.billed_cpu_time_us = billed_cpu_time_us; + transaction_trace_ptr trace = trx_context.trace; + flat_set bill_to_accounts; + try { + trx_context.init_for_deferred_trx( gto.published ); + bill_to_accounts = trx_context.bill_to_accounts; + trx_context.exec(); + trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful + + auto restore = make_block_restore_point(); + + trace->receipt = push_receipt( gto.trx_id, + transaction_receipt::executed, + trx_context.billed_cpu_time_us, + trace->net_usage ); + + fc::move_append( pending->_actions, move(trx_context.executed) ); + + emit( self.applied_transaction, trace ); + + trx_context.squash(); + undo_session.squash(); + restore.cancel(); + return trace; + } catch( const fc::exception& e ) { + trace->except = e; + trace->except_ptr = std::current_exception(); + trace->elapsed = fc::time_point::now() - trx_context.start; + } + trx_context.undo_session.undo(); + + // Only soft or hard failure logic below: + + if( gto.sender != account_name() && !failure_is_subjective(*trace->except)) { + // Attempt error handling for the generated transaction. + edump((trace->except->to_detail_string())); + auto error_trace = apply_onerror( gto, deadline, trx_context.start, trx_context.billed_cpu_time_us ); + error_trace->failed_dtrx_trace = trace; + trace = error_trace; + if( !trace->except_ptr ) { + undo_session.squash(); + return trace; + } + } + + // Only hard failure OR subjective failure logic below: + + trace->elapsed = fc::time_point::now() - trx_context.start; + + resource_limits.add_transaction_usage( bill_to_accounts, trx_context.billed_cpu_time_us, 0, + block_timestamp_type(self.pending_block_time()).slot ); // Should never fail + + if (failure_is_subjective(*trace->except)) { + // this is a subjective failure, don't remove the retained state so it can be + // retried at a later time and don't include any artifact of the transaction in the pending block + remove_retained_state.cancel(); + } else { + trace->receipt = push_receipt(gto.trx_id, transaction_receipt::hard_fail, trx_context.billed_cpu_time_us, 0); + emit( self.applied_transaction, trace ); + undo_session.squash(); + } + + return trace; + } FC_CAPTURE_AND_RETHROW() } /// push_scheduled_transaction + + + /** + * Adds the transaction receipt to the pending block and returns it. + */ + template + const transaction_receipt& push_receipt( const T& trx, transaction_receipt_header::status_enum status, + uint64_t cpu_usage_us, uint64_t net_usage ) { + uint64_t net_usage_words = net_usage / 8; + FC_ASSERT( net_usage_words*8 == net_usage, "net_usage is not divisible by 8" ); + pending->_pending_block_state->block->transactions.emplace_back( trx ); + transaction_receipt& r = pending->_pending_block_state->block->transactions.back(); + r.cpu_usage_us = cpu_usage_us; + r.net_usage_words = net_usage_words; + r.status = status; + return r; + } + + void transaction_trace_notify( const transaction_metadata_ptr& trx, const transaction_trace_ptr& trace ) { + if( trx->on_result ) { + (trx->on_result)(trace); + trx->on_result = decltype(trx->on_result)(); //assign empty std::function + } + + if (!trx->accepted) { + emit( self.accepted_transaction, trx); + trx->accepted = true; + } + } + + /** + * This is the entry point for new transactions to the block state. It will check authorization and + * determine whether to execute it now or to delay it. Lastly it inserts a transaction receipt into + * the pending block. + */ + transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, + fc::time_point deadline, + bool implicit, + uint32_t billed_cpu_time_us ) + { + FC_ASSERT(deadline != fc::time_point(), "deadline cannot be uninitialized"); + + transaction_trace_ptr trace; + try { + transaction_context trx_context(self, trx->trx, trx->id); + trx_context.deadline = deadline; + trx_context.billed_cpu_time_us = billed_cpu_time_us; + trace = trx_context.trace; + try { + if (implicit) { + trx_context.init_for_implicit_trx(); + } else { + trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(), + trx->packed_trx.get_prunable_size(), + trx->trx.signatures.size() ); + } + + trx_context.delay = fc::seconds(trx->trx.delay_sec); + + if (!implicit) { + authorization.check_authorization( + trx->trx.actions, + trx->recover_keys(), + {}, + trx_context.delay, + [](uint32_t){} + /*std::bind(&transaction_context::add_cpu_usage_and_check_time, &trx_context, + std::placeholders::_1)*/, + false + ); + } + + trx_context.exec(); + trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful + + auto restore = make_block_restore_point(); + + if (!implicit) { + transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0)) + ? transaction_receipt::executed + : transaction_receipt::delayed; + trace->receipt = push_receipt(trx->packed_trx, s, trx_context.billed_cpu_time_us, trace->net_usage); + pending->_pending_block_state->trxs.emplace_back(trx); + } else { + transaction_receipt_header r; + r.status = transaction_receipt::executed; + r.cpu_usage_us = trx_context.billed_cpu_time_us; + r.net_usage_words = trace->net_usage / 8; + trace->receipt = r; + } + + fc::move_append(pending->_actions, move(trx_context.executed)); + + transaction_trace_notify(trx, trace); + + emit(self.applied_transaction, trace); + + trx_context.squash(); + restore.cancel(); + + if (!implicit) { + unapplied_transactions.erase( trx->signed_id ); + } + return trace; + } catch (const fc::exception& e) { + trace->except = e; + trace->except_ptr = std::current_exception(); + } + + transaction_trace_notify(trx, trace); + return trace; + } FC_CAPTURE_AND_RETHROW((trace)) + } /// push_transaction + + + void start_block( block_timestamp_type when, uint16_t confirm_block_count ) { + FC_ASSERT( !pending ); + + FC_ASSERT( db.revision() == head->block_num, "", + ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); + + auto guard_pending = fc::make_scoped_exit([this](){ + pending.reset(); + }); + + pending = db.start_undo_session(true); + + pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active + pending->_pending_block_state->in_current_chain = true; + + const auto& gpo = db.get(); + if( gpo.proposed_schedule_block_num.valid() && // if there is a proposed schedule that was proposed in a block ... + ( *gpo.proposed_schedule_block_num <= pending->_pending_block_state->dpos_irreversible_blocknum ) && // ... that has now become irreversible ... + pending->_pending_block_state->pending_schedule.producers.size() == 0 && // ... and there is room for a new pending schedule ... + head->pending_schedule.producers.size() == 0 // ... and not just because it was promoted to active at the start of this block, then: + ) + { + // Promote proposed schedule to pending schedule. + ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", + ("proposed_num", *gpo.proposed_schedule_block_num)("n", pending->_pending_block_state->block_num) + ("lib", pending->_pending_block_state->dpos_irreversible_blocknum) + ("schedule", static_cast(gpo.proposed_schedule) ) ); + pending->_pending_block_state->set_new_producers( gpo.proposed_schedule ); + db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = optional(); + gp.proposed_schedule.clear(); + }); + } + + pending->_pending_block_state->set_confirmed(confirm_block_count); + + try { + auto onbtrx = std::make_shared( get_on_block_transaction() ); + push_transaction( onbtrx, fc::time_point::maximum(), true, config::default_min_transaction_cpu_usage_us); + } catch ( ... ) { + ilog( "on block transaction failed, but shouldn't impact block generation, system contract needs update" ); + } + + clear_expired_input_transactions(); + update_producers_authority(); + guard_pending.cancel(); + } // start_block + + + + void sign_block( const std::function& signer_callback ) { + auto p = pending->_pending_block_state; + try { + p->sign( signer_callback ); + } catch ( ... ) { + edump(( fc::json::to_pretty_string( *p->block ) ) ); + throw; + } + static_cast(*p->block) = p->header; + } /// sign_block + + void apply_block( const signed_block_ptr& b ) { try { + try { + FC_ASSERT( b->block_extensions.size() == 0, "no supported extensions" ); + start_block( b->timestamp, b->confirmed ); + + for( const auto& receipt : b->transactions ) { + if( receipt.trx.contains() ) { + auto& pt = receipt.trx.get(); + auto mtrx = std::make_shared(pt); + push_transaction( mtrx, fc::time_point::maximum(), false, receipt.cpu_usage_us ); + } + else if( receipt.trx.contains() ) { + push_scheduled_transaction( receipt.trx.get(), fc::time_point::maximum(), receipt.cpu_usage_us ); + } + } + + finalize_block(); + sign_block( [&]( const auto& ){ return b->producer_signature; } ); + + // this is implied by the signature passing + //FC_ASSERT( b->id() == pending->_pending_block_state->block->id(), + // "applying block didn't produce expected block id" ); + + commit_block(false); + return; + } catch ( const fc::exception& e ) { + edump((e.to_detail_string())); + abort_block(); + throw; + } + } FC_CAPTURE_AND_RETHROW() } /// apply_block + + + void push_block( const signed_block_ptr& b ) { + // idump((fc::json::to_pretty_string(*b))); + FC_ASSERT(!pending, "it is not valid to push a block when there is a pending block"); + try { + FC_ASSERT( b ); + auto new_header_state = fork_db.add( b ); + emit( self.accepted_block_header, new_header_state ); + maybe_switch_forks(); + } FC_LOG_AND_RETHROW( ) + } + + void push_confirmation( const header_confirmation& c ) { + FC_ASSERT(!pending, "it is not valid to push a confirmation when there is a pending block"); + fork_db.add( c ); + emit( self.accepted_confirmation, c ); + maybe_switch_forks(); + } + + void maybe_switch_forks() { + auto new_head = fork_db.head(); + + if( new_head->header.previous == head->id ) { + try { + apply_block( new_head->block ); + fork_db.mark_in_current_chain( new_head, true ); + fork_db.set_validity( new_head, true ); + head = new_head; + } catch ( const fc::exception& e ) { + fork_db.set_validity( new_head, false ); // Removes new_head from fork_db index, so no need to mark it as not in the current chain. + throw; + } + } else if( new_head->id != head->id ) { + ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})", + ("current_head_id", head->id)("current_head_num", head->block_num)("new_head_id", new_head->id)("new_head_num", new_head->block_num) ); + auto branches = fork_db.fetch_branch_from( new_head->id, head->id ); + + for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { + fork_db.mark_in_current_chain( *itr , false ); + pop_block(); + } + FC_ASSERT( self.head_block_id() == branches.second.back()->header.previous, + "loss of sync between fork_db and chainbase during fork switch" ); // _should_ never fail + + for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr) { + optional except; + try { + apply_block( (*ritr)->block ); + head = *ritr; + fork_db.mark_in_current_chain( *ritr, true ); + } + catch (const fc::exception& e) { except = e; } + if (except) { + elog("exception thrown while switching forks ${e}", ("e",except->to_detail_string())); + + while (ritr != branches.first.rend() ) { + fork_db.set_validity( *ritr, false ); + ++ritr; + } + + // pop all blocks from the bad fork + for( auto itr = (ritr + 1).base(); itr != branches.second.end(); ++itr ) { + fork_db.mark_in_current_chain( *itr , false ); + pop_block(); + } + FC_ASSERT( self.head_block_id() == branches.second.back()->header.previous, + "loss of sync between fork_db and chainbase during fork switch reversal" ); // _should_ never fail + + // re-apply good blocks + for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) { + apply_block( (*ritr)->block ); + head = *ritr; + fork_db.mark_in_current_chain( *ritr, true ); + } + throw *except; + } // end if exception + } /// end for each block in branch + ilog("successfully switched fork to new head ${new_head_id}", ("new_head_id", new_head->id)); + } + } /// push_block + + void abort_block() { + if( pending ) { + for( const auto& t : pending->_pending_block_state->trxs ) + unapplied_transactions[t->signed_id] = t; + pending.reset(); + } + } + + + bool should_enforce_runtime_limits()const { + return false; + } + + void set_action_merkle() { + vector action_digests; + action_digests.reserve( pending->_actions.size() ); + for( const auto& a : pending->_actions ) + action_digests.emplace_back( a.digest() ); + + pending->_pending_block_state->header.action_mroot = merkle( move(action_digests) ); + } + + void set_trx_merkle() { + vector trx_digests; + const auto& trxs = pending->_pending_block_state->block->transactions; + trx_digests.reserve( trxs.size() ); + for( const auto& a : trxs ) + trx_digests.emplace_back( a.digest() ); + + pending->_pending_block_state->header.transaction_mroot = merkle( move(trx_digests) ); + } + + + void finalize_block() + { + FC_ASSERT(pending, "it is not valid to finalize when there is no pending block"); + try { + + + /* + ilog( "finalize block ${n} (${id}) at ${t} by ${p} (${signing_key}); schedule_version: ${v} lib: ${lib} #dtrxs: ${ndtrxs} ${np}", + ("n",pending->_pending_block_state->block_num) + ("id",pending->_pending_block_state->header.id()) + ("t",pending->_pending_block_state->header.timestamp) + ("p",pending->_pending_block_state->header.producer) + ("signing_key", pending->_pending_block_state->block_signing_key) + ("v",pending->_pending_block_state->header.schedule_version) + ("lib",pending->_pending_block_state->dpos_irreversible_blocknum) + ("ndtrxs",db.get_index().size()) + ("np",pending->_pending_block_state->header.new_producers) + ); + */ + + // Update resource limits: + resource_limits.process_account_limit_updates(); + const auto& chain_config = self.get_global_properties().configuration; + uint32_t max_virtual_mult = 1000; + uint64_t CPU_TARGET = EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct); + resource_limits.set_block_parameters( + { CPU_TARGET, chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}}, + {EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}} + ); + resource_limits.process_block_usage(pending->_pending_block_state->block_num); + + set_action_merkle(); + set_trx_merkle(); + + auto p = pending->_pending_block_state; + p->id = p->header.id(); + + create_block_summary(p->id); + + } FC_CAPTURE_AND_RETHROW() } + + void update_producers_authority() { + const auto& producers = pending->_pending_block_state->active_schedule.producers; + + auto update_permission = [&]( auto& permission, auto threshold ) { + auto auth = authority( threshold, {}, {}); + for( auto& p : producers ) { + auth.accounts.push_back({{p.producer_name, config::active_name}, 1}); + } + + if( static_cast(permission.auth) != auth ) { // TODO: use a more efficient way to check that authority has not changed + db.modify(permission, [&]( auto& po ) { + po.auth = auth; + }); + } + }; + + uint32_t num_producers = producers.size(); + auto calculate_threshold = [=]( uint32_t numerator, uint32_t denominator ) { + return ( (num_producers * numerator) / denominator ) + 1; + }; + + update_permission( authorization.get_permission({config::producers_account_name, + config::active_name}), + calculate_threshold( 2, 3 ) /* more than two-thirds */ ); + + update_permission( authorization.get_permission({config::producers_account_name, + config::majority_producers_permission_name}), + calculate_threshold( 1, 2 ) /* more than one-half */ ); + + update_permission( authorization.get_permission({config::producers_account_name, + config::minority_producers_permission_name}), + calculate_threshold( 1, 3 ) /* more than one-third */ ); + + //TODO: Add tests + } + + void create_block_summary(const block_id_type& id) { + auto block_num = block_header::num_from_id(id); + auto sid = block_num & 0xffff; + db.modify( db.get(sid), [&](block_summary_object& bso ) { + bso.block_id = id; + }); + } + + + void clear_expired_input_transactions() { + //Look for expired transactions in the deduplication list, and remove them. + auto& transaction_idx = db.get_mutable_index(); + const auto& dedupe_index = transaction_idx.indices().get(); + auto now = self.pending_block_time(); + while( (!dedupe_index.empty()) && ( now > fc::time_point(dedupe_index.begin()->expiration) ) ) { + transaction_idx.remove(*dedupe_index.begin()); + } + } + + /* + bool should_check_tapos()const { return true; } + + void validate_tapos( const transaction& trx )const { + if( !should_check_tapos() ) return; + + const auto& tapos_block_summary = db.get((uint16_t)trx.ref_block_num); + + //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration + EOS_ASSERT(trx.verify_reference_block(tapos_block_summary.block_id), invalid_ref_block_exception, + "Transaction's reference block did not match. Is this transaction from a different fork?", + ("tapos_summary", tapos_block_summary)); + } + */ + + + /** + * At the start of each block we notify the system contract with a transaction that passes in + * the block header of the prior block (which is currently our head block) + */ + signed_transaction get_on_block_transaction() + { + action on_block_act; + on_block_act.account = config::system_account_name; + on_block_act.name = N(onblock); + on_block_act.authorization = vector{{config::system_account_name, config::active_name}}; + on_block_act.data = fc::raw::pack(self.head_block_header()); + + signed_transaction trx; + trx.actions.emplace_back(std::move(on_block_act)); + trx.set_reference_block(self.head_block_id()); + trx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired + return trx; + } + +}; /// controller_impl + +const resource_limits_manager& controller::get_resource_limits_manager()const +{ + return my->resource_limits; +} +resource_limits_manager& controller::get_mutable_resource_limits_manager() +{ + return my->resource_limits; +} + +const authorization_manager& controller::get_authorization_manager()const +{ + return my->authorization; +} +authorization_manager& controller::get_mutable_authorization_manager() +{ + return my->authorization; +} + +controller::controller( const controller::config& cfg ) +:my( new controller_impl( cfg, *this ) ) +{ +} + +controller::~controller() { + my->abort_block(); +} + + +void controller::startup() { + + // ilog( "${c}", ("c",fc::json::to_pretty_string(cfg)) ); + my->add_indices(); + + my->head = my->fork_db.head(); + if( !my->head ) { + elog( "No head block in fork db, perhaps we need to replay" ); + my->init(); + } else { + // my->db.set_revision( my->head->block_num ); + } +} + +chainbase::database& controller::db()const { return my->db; } + +fork_database& controller::fork_db()const { return my->fork_db; } + + +void controller::start_block( block_timestamp_type when, uint16_t confirm_block_count ) { + my->start_block(when, confirm_block_count); +} + +void controller::finalize_block() { + my->finalize_block(); +} + +void controller::sign_block( const std::function& signer_callback ) { + my->sign_block( signer_callback ); +} + +void controller::commit_block() { + my->commit_block(true); +} + +void controller::abort_block() { + my->abort_block(); +} + +void controller::push_block( const signed_block_ptr& b ) { + my->push_block( b ); + log_irreversible_blocks(); +} + +void controller::push_confirmation( const header_confirmation& c ) { + my->push_confirmation( c ); +} + +transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { + return my->push_transaction(trx, deadline, false, billed_cpu_time_us); +} + +transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline, uint32_t billed_cpu_time_us ) +{ + return my->push_scheduled_transaction( trxid, deadline, billed_cpu_time_us ); +} + +uint32_t controller::head_block_num()const { + return my->head->block_num; +} +time_point controller::head_block_time()const { + return my->head->header.timestamp; +} +block_id_type controller::head_block_id()const { + return my->head->id; +} +account_name controller::head_block_producer()const { + return my->head->header.producer; +} +const block_header& controller::head_block_header()const { + return my->head->header; +} +block_state_ptr controller::head_block_state()const { + return my->head; +} + +block_state_ptr controller::pending_block_state()const { + if( my->pending ) return my->pending->_pending_block_state; + return block_state_ptr(); +} +time_point controller::pending_block_time()const { + FC_ASSERT( my->pending, "no pending block" ); + return my->pending->_pending_block_state->header.timestamp; +} + +uint32_t controller::last_irreversible_block_num() const { + return std::max(my->head->bft_irreversible_blocknum, my->head->dpos_irreversible_blocknum); +} + +block_id_type controller::last_irreversible_block_id() const { + auto lib_num = last_irreversible_block_num(); + const auto& tapos_block_summary = db().get((uint16_t)lib_num); + + if( block_header::num_from_id(tapos_block_summary.block_id) == lib_num ) + return tapos_block_summary.block_id; + + return fetch_block_by_number(lib_num)->id(); + +} + +const dynamic_global_property_object& controller::get_dynamic_global_properties()const { + return my->db.get(); +} +const global_property_object& controller::get_global_properties()const { + return my->db.get(); +} + +/** + * This method reads the current dpos_irreverible block number, if it is higher + * than the last block number of the log, it grabs the next block from the + * fork database, saves it to disk, then removes the block from the fork database. + * + * Any forks built off of a different block with the same number are also pruned. + */ +void controller::log_irreversible_blocks() { + /* + if( !my->blog.head() ) + my->blog.read_head(); + + const auto& log_head = my->blog.head(); + auto lib = my->head->dpos_irreversible_blocknum; + + + if( lib > 2 ) { + if( log_head && log_head->block_num() > lib ) { + auto blk = my->fork_db.get_block_in_current_chain_by_num( lib - 1 ); + FC_ASSERT( blk, "unable to find block state", ("block_num",lib-1)); + my->fork_db.prune( blk ); + my->db.commit( lib -1 ); + return; + } + + while( log_head && (log_head->block_num()+1) < lib ) { + auto lhead = log_head->block_num(); + auto blk = my->fork_db.get_block_in_current_chain_by_num( lhead + 1 ); + FC_ASSERT( blk, "unable to find block state", ("block_num",lhead+1)); + irreversible_block( blk ); + + if( !my->replaying ) { + my->blog.append( blk->block ); + } + + my->fork_db.prune( blk ); + my->db.commit( lhead ); + } + } + */ +} +signed_block_ptr controller::fetch_block_by_id( block_id_type id )const { + auto state = my->fork_db.get_block(id); + if( state ) return state->block; + auto bptr = fetch_block_by_number( block_header::num_from_id(id) ); + if( bptr && bptr->id() == id ) return bptr; + return signed_block_ptr(); +} + +signed_block_ptr controller::fetch_block_by_number( uint32_t block_num )const { try { + auto blk_state = my->fork_db.get_block_in_current_chain_by_num( block_num ); + if( blk_state ) { + return blk_state->block; + } + + return my->blog.read_block_by_num(block_num); +} FC_CAPTURE_AND_RETHROW( (block_num) ) } + +block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try { + auto blk_state = my->fork_db.get_block_in_current_chain_by_num( block_num ); + if( blk_state ) { + return blk_state->id; + } + + auto signed_blk = my->blog.read_block_by_num(block_num); + + EOS_ASSERT( BOOST_LIKELY( signed_blk != nullptr ), unknown_block_exception, + "Could not find block: ${block}", ("block", block_num) ); + + return signed_blk->id(); +} FC_CAPTURE_AND_RETHROW( (block_num) ) } + +void controller::pop_block() { + my->pop_block(); +} + +bool controller::set_proposed_producers( vector producers ) { + const auto& gpo = get_global_properties(); + auto cur_block_num = head_block_num() + 1; + + if( gpo.proposed_schedule_block_num.valid() ) { + if( *gpo.proposed_schedule_block_num != cur_block_num ) + return false; // there is already a proposed schedule set in a previous block, wait for it to become pending + + if( std::equal( producers.begin(), producers.end(), + gpo.proposed_schedule.producers.begin(), gpo.proposed_schedule.producers.end() ) ) + return false; // the proposed producer schedule does not change + } + + producer_schedule_type sch; + + decltype(sch.producers.cend()) end; + decltype(end) begin; + + if( my->pending->_pending_block_state->pending_schedule.producers.size() == 0 ) { + const auto& active_sch = my->pending->_pending_block_state->active_schedule; + begin = active_sch.producers.begin(); + end = active_sch.producers.end(); + sch.version = active_sch.version + 1; + } else { + const auto& pending_sch = my->pending->_pending_block_state->pending_schedule; + begin = pending_sch.producers.begin(); + end = pending_sch.producers.end(); + sch.version = pending_sch.version + 1; + } + + if( std::equal( producers.begin(), producers.end(), begin, end ) ) + return false; // the producer schedule would not change + + sch.producers = std::move(producers); + + my->db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = cur_block_num; + gp.proposed_schedule = std::move(sch); + }); + return true; +} + +const producer_schedule_type& controller::active_producers()const { + if ( !(my->pending) ) + return my->head->active_schedule; + return my->pending->_pending_block_state->active_schedule; +} + +const producer_schedule_type& controller::pending_producers()const { + if ( !(my->pending) ) + return my->head->pending_schedule; + return my->pending->_pending_block_state->pending_schedule; +} + +optional controller::proposed_producers()const { + const auto& gpo = get_global_properties(); + if( !gpo.proposed_schedule_block_num.valid() ) + return optional(); + + return gpo.proposed_schedule; +} + + +const apply_handler* controller::find_apply_handler( account_name receiver, account_name scope, action_name act ) const +{ + auto native_handler_scope = my->apply_handlers.find( receiver ); + if( native_handler_scope != my->apply_handlers.end() ) { + auto handler = native_handler_scope->second.find( make_pair( scope, act ) ); + if( handler != native_handler_scope->second.end() ) + return &handler->second; + } + return nullptr; +} +wasm_interface& controller::get_wasm_interface() { + return my->wasmif; +} + +const account_object& controller::get_account( account_name name )const +{ try { + return my->db.get(name); +} FC_CAPTURE_AND_RETHROW( (name) ) } + +vector controller::get_unapplied_transactions() const { + vector result; + result.reserve(my->unapplied_transactions.size()); + for ( const auto& entry: my->unapplied_transactions ) { + result.emplace_back(entry.second); + } + return result; +} + +void controller::drop_unapplied_transaction(const transaction_metadata_ptr& trx) { + my->unapplied_transactions.erase(trx->signed_id); +} + +vector controller::get_scheduled_transactions() const { + const auto& idx = db().get_index(); + + vector result; + + static const size_t max_reserve = 64; + result.reserve(std::min(idx.size(), max_reserve)); + + auto itr = idx.begin(); + while( itr != idx.end() && itr->delay_until <= pending_block_time() ) { + result.emplace_back(itr->trx_id); + ++itr; + } + return result; +} + +void controller::validate_referenced_accounts( const transaction& trx )const { + for( const auto& a : trx.context_free_actions ) { + auto* code = my->db.find(a.account); + EOS_ASSERT( code != nullptr, transaction_exception, + "action's code account '${account}' does not exist", ("account", a.account) ); + EOS_ASSERT( a.authorization.size() == 0, transaction_exception, + "context-free actions cannot have authorizations" ); + } + bool one_auth = false; + for( const auto& a : trx.actions ) { + auto* code = my->db.find(a.account); + EOS_ASSERT( code != nullptr, transaction_exception, + "action's code account '${account}' does not exist", ("account", a.account) ); + for( const auto& auth : a.authorization ) { + one_auth = true; + auto* actor = my->db.find(auth.actor); + EOS_ASSERT( actor != nullptr, transaction_exception, + "action's authorizing actor '${account}' does not exist", ("account", auth.actor) ); + EOS_ASSERT( my->authorization.find_permission(auth) != nullptr, transaction_exception, + "action's authorizations include a non-existent permission: {permission}", + ("permission", auth) ); + } + } + EOS_ASSERT( one_auth, tx_no_auths, "transaction must have at least one authorization" ); +} + +void controller::validate_expiration( const transaction& trx )const { try { + const auto& chain_configuration = get_global_properties().configuration; + + EOS_ASSERT( time_point(trx.expiration) >= pending_block_time(), + expired_tx_exception, + "transaction has expired, " + "expiration is ${trx.expiration} and pending block time is ${pending_block_time}", + ("trx.expiration",trx.expiration)("pending_block_time",pending_block_time())); + EOS_ASSERT( time_point(trx.expiration) <= pending_block_time() + fc::seconds(chain_configuration.max_transaction_lifetime), + tx_exp_too_far_exception, + "Transaction expiration is too far in the future relative to the reference time of ${reference_time}, " + "expiration is ${trx.expiration} and the maximum transaction lifetime is ${max_til_exp} seconds", + ("trx.expiration",trx.expiration)("reference_time",pending_block_time()) + ("max_til_exp",chain_configuration.max_transaction_lifetime) ); +} FC_CAPTURE_AND_RETHROW((trx)) } + +void controller::validate_tapos( const transaction& trx )const { try { + const auto& tapos_block_summary = db().get((uint16_t)trx.ref_block_num); + + //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration + EOS_ASSERT(trx.verify_reference_block(tapos_block_summary.block_id), invalid_ref_block_exception, + "Transaction's reference block did not match. Is this transaction from a different fork?", + ("tapos_summary", tapos_block_summary)); +} FC_CAPTURE_AND_RETHROW() } + + +} } /// eosio::chain diff --git a/libraries/chain/eosio_contract.cpp b/libraries/chain/eosio_contract.cpp new file mode 100644 index 00000000000..b552eddca48 --- /dev/null +++ b/libraries/chain/eosio_contract.cpp @@ -0,0 +1,380 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace eosio { namespace chain { + + + +uint128_t transaction_id_to_sender_id( const transaction_id_type& tid ) { + fc::uint128_t _id(tid._hash[3], tid._hash[2]); + return (unsigned __int128)_id; +} + +void validate_authority_precondition( const apply_context& context, const authority& auth ) { + for(const auto& a : auth.accounts) { + auto* acct = context.db.find(a.permission.actor); + EOS_ASSERT( acct != nullptr, action_validate_exception, + "account '${account}' does not exist", + ("account", a.permission.actor) + ); + + if( a.permission.permission == config::owner_name || a.permission.permission == config::active_name ) + continue; // account was already checked to exist, so its owner and active permissions should exist + + if( a.permission.permission == config::eosio_code_name ) // virtual eosio.code permission does not really exist but is allowed + continue; + + try { + context.control.get_authorization_manager().get_permission({a.permission.actor, a.permission.permission}); + } catch( const permission_query_exception& ) { + EOS_THROW( action_validate_exception, + "permission '${perm}' does not exist", + ("perm", a.permission) + ); + } + } +} + +/** + * This method is called assuming precondition_system_newaccount succeeds a + */ +void apply_eosio_newaccount(apply_context& context) { + auto create = context.act.data_as(); + try { + context.require_authorization(create.creator); +// context.require_write_lock( config::eosio_auth_scope ); + auto& authorization = context.control.get_mutable_authorization_manager(); + + EOS_ASSERT( validate(create.owner), action_validate_exception, "Invalid owner authority"); + EOS_ASSERT( validate(create.active), action_validate_exception, "Invalid active authority"); + + auto& db = context.db; + + auto name_str = name(create.name).to_string(); + + EOS_ASSERT( !create.name.empty(), action_validate_exception, "account name cannot be empty" ); + EOS_ASSERT( name_str.size() <= 12, action_validate_exception, "account names can only be 12 chars long" ); + + // Check if the creator is privileged + const auto &creator = db.get(create.creator); + if( !creator.privileged ) { + EOS_ASSERT( name_str.find( "eosio." ) != 0, action_validate_exception, + "only privileged accounts can have names that start with 'eosio.'" ); + } + + auto existing_account = db.find(create.name); + EOS_ASSERT(existing_account == nullptr, action_validate_exception, + "Cannot create account named ${name}, as that name is already taken", + ("name", create.name)); + + const auto& new_account = db.create([&](auto& a) { + a.name = create.name; + a.creation_date = context.control.pending_block_time(); + }); + + db.create([&](auto& a) { + a.name = create.name; + }); + + for( const auto& auth : { create.owner, create.active } ){ + validate_authority_precondition( context, auth ); + } + + const auto& owner_permission = authorization.create_permission( create.name, config::owner_name, 0, + std::move(create.owner) ); + const auto& active_permission = authorization.create_permission( create.name, config::active_name, owner_permission.id, + std::move(create.active) ); + + context.control.get_mutable_resource_limits_manager().initialize_account(create.name); + + int64_t ram_delta = config::overhead_per_account_ram_bytes; + ram_delta += 2*config::billable_size_v; + ram_delta += owner_permission.auth.get_billable_size(); + ram_delta += active_permission.auth.get_billable_size(); + + context.trx_context.add_ram_usage(create.name, ram_delta); + +} FC_CAPTURE_AND_RETHROW( (create) ) } + +void apply_eosio_setcode(apply_context& context) { + const auto& cfg = context.control.get_global_properties().configuration; + context.checktime( cfg.base_setcode_cpu_usage ); + + auto& db = context.db; + auto act = context.act.data_as(); + context.require_authorization(act.account); +// context.require_write_lock( config::eosio_auth_scope ); + + FC_ASSERT( act.vmtype == 0 ); + FC_ASSERT( act.vmversion == 0 ); + + context.checktime( act.code.size() * 20 ); + + auto code_id = fc::sha256::hash( act.code.data(), (uint32_t)act.code.size() ); + + wasm_interface::validate(act.code); + + const auto& account = db.get(act.account); + + int64_t code_size = (int64_t)act.code.size(); + int64_t old_size = (int64_t)account.code.size() * config::setcode_ram_bytes_multiplier; + int64_t new_size = code_size * config::setcode_ram_bytes_multiplier; + + FC_ASSERT( account.code_version != code_id, "contract is already running this version of code" ); +// wlog( "set code: ${size}", ("size",act.code.size())); + db.modify( account, [&]( auto& a ) { + /** TODO: consider whether a microsecond level local timestamp is sufficient to detect code version changes*/ + #warning TODO: update setcode message to include the hash, then validate it in validate + a.code_version = code_id; + a.code.resize( code_size ); + a.last_code_update = context.control.pending_block_time(); + memcpy( a.code.data(), act.code.data(), code_size ); + + }); + + if (new_size != old_size) { + context.trx_context.add_ram_usage( act.account, new_size - old_size ); + } +} + +void apply_eosio_setabi(apply_context& context) { + auto& db = context.db; + auto act = context.act.data_as(); + + context.require_authorization(act.account); + + // if system account append native abi + if ( act.account == eosio::chain::config::system_account_name ) { + act.abi = eosio_contract_abi(act.abi); + } + /// if an ABI is specified make sure it is well formed and doesn't + /// reference any undefined types + abi_serializer(act.abi).validate(); + // todo: figure out abi serialization location + + const auto& account = db.get(act.account); + + int64_t old_size = (int64_t)account.abi.size(); + int64_t new_size = (int64_t)fc::raw::pack_size(act.abi); + + context.checktime( new_size * 2 ); + + db.modify( account, [&]( auto& a ) { + a.set_abi( act.abi ); + }); + + if (new_size != old_size) { + context.trx_context.add_ram_usage( act.account, new_size - old_size ); + } +} + +void apply_eosio_updateauth(apply_context& context) { + + auto update = context.act.data_as(); + context.require_authorization(update.account); // only here to mark the single authority on this action as used + + auto& authorization = context.control.get_mutable_authorization_manager(); + auto& db = context.db; + + EOS_ASSERT(!update.permission.empty(), action_validate_exception, "Cannot create authority with empty name"); + EOS_ASSERT( update.permission.to_string().find( "eosio." ) != 0, action_validate_exception, + "Permission names that start with 'eosio.' are reserved" ); + EOS_ASSERT(update.permission != update.parent, action_validate_exception, "Cannot set an authority as its own parent"); + db.get(update.account); + EOS_ASSERT(validate(update.auth), action_validate_exception, + "Invalid authority: ${auth}", ("auth", update.auth)); + if( update.permission == config::active_name ) + EOS_ASSERT(update.parent == config::owner_name, action_validate_exception, "Cannot change active authority's parent from owner", ("update.parent", update.parent) ); + if (update.permission == config::owner_name) + EOS_ASSERT(update.parent.empty(), action_validate_exception, "Cannot change owner authority's parent"); + else + EOS_ASSERT(!update.parent.empty(), action_validate_exception, "Only owner permission can have empty parent" ); + + if( update.auth.waits.size() > 0 ) { + auto max_delay = context.control.get_global_properties().configuration.max_transaction_delay; + EOS_ASSERT( update.auth.waits.back().wait_sec <= max_delay, action_validate_exception, + "Cannot set delay longer than max_transacton_delay, which is ${max_delay} seconds", + ("max_delay", max_delay) ); + } + + validate_authority_precondition(context, update.auth); + + + context.checktime( 5000 ); + + auto permission = authorization.find_permission({update.account, update.permission}); + + // If a parent_id of 0 is going to be used to indicate the absence of a parent, then we need to make sure that the chain + // initializes permission_index with a dummy object that reserves the id of 0. + authorization_manager::permission_id_type parent_id = 0; + if( update.permission != config::owner_name ) { + auto& parent = authorization.get_permission({update.account, update.parent}); + parent_id = parent.id; + } + + if( permission ) { + EOS_ASSERT(parent_id == permission->parent, action_validate_exception, + "Changing parent authority is not currently supported"); + + + int64_t old_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); + + authorization.modify_permission( *permission, update.auth ); + + int64_t new_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); + + context.trx_context.add_ram_usage( permission->owner, new_size - old_size ); + } else { + const auto& p = authorization.create_permission( update.account, update.permission, parent_id, update.auth ); + + int64_t new_size = (int64_t)(config::billable_size_v + p.auth.get_billable_size()); + + context.trx_context.add_ram_usage( update.account, new_size ); + } +} + +void apply_eosio_deleteauth(apply_context& context) { +// context.require_write_lock( config::eosio_auth_scope ); + + auto remove = context.act.data_as(); + context.require_authorization(remove.account); // only here to mark the single authority on this action as used + + EOS_ASSERT(remove.permission != config::active_name, action_validate_exception, "Cannot delete active authority"); + EOS_ASSERT(remove.permission != config::owner_name, action_validate_exception, "Cannot delete owner authority"); + + auto& authorization = context.control.get_mutable_authorization_manager(); + auto& db = context.db; + + + + { // Check for links to this permission + const auto& index = db.get_index(); + auto range = index.equal_range(boost::make_tuple(remove.account, remove.permission)); + EOS_ASSERT(range.first == range.second, action_validate_exception, + "Cannot delete a linked authority. Unlink the authority first"); + } + + const auto& permission = authorization.get_permission({remove.account, remove.permission}); + int64_t old_size = config::billable_size_v + permission.auth.get_billable_size(); + + authorization.remove_permission( permission ); + + context.trx_context.add_ram_usage( remove.account, -old_size ); + + context.checktime( 3000 ); +} + +void apply_eosio_linkauth(apply_context& context) { +// context.require_write_lock( config::eosio_auth_scope ); + + auto requirement = context.act.data_as(); + try { + EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty"); + + context.require_authorization(requirement.account); // only here to mark the single authority on this action as used + + auto& db = context.db; + const auto *account = db.find(requirement.account); + EOS_ASSERT(account != nullptr, account_query_exception, + "Failed to retrieve account: ${account}", ("account", requirement.account)); // Redundant? + const auto *code = db.find(requirement.code); + EOS_ASSERT(code != nullptr, account_query_exception, + "Failed to retrieve code for account: ${account}", ("account", requirement.code)); + if( requirement.requirement != config::eosio_any_name ) { + const auto *permission = db.find(requirement.requirement); + EOS_ASSERT(permission != nullptr, permission_query_exception, + "Failed to retrieve permission: ${permission}", ("permission", requirement.requirement)); + } + + auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type); + auto link = db.find(link_key); + + if( link ) { + EOS_ASSERT(link->required_permission != requirement.requirement, action_validate_exception, + "Attempting to update required authority, but new requirement is same as old"); + db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) { + link.required_permission = requirement; + }); + } else { + const auto& l = db.create([&requirement](permission_link_object& link) { + link.account = requirement.account; + link.code = requirement.code; + link.message_type = requirement.type; + link.required_permission = requirement.requirement; + }); + + context.trx_context.add_ram_usage( + l.account, + (int64_t)(config::billable_size_v) + ); + } + + context.checktime( 3000 ); + } FC_CAPTURE_AND_RETHROW((requirement)) +} + +void apply_eosio_unlinkauth(apply_context& context) { +// context.require_write_lock( config::eosio_auth_scope ); + + auto& db = context.db; + auto unlink = context.act.data_as(); + + context.require_authorization(unlink.account); // only here to mark the single authority on this action as used + + auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type); + auto link = db.find(link_key); + EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found"); + context.trx_context.add_ram_usage( + link->account, + -(int64_t)(config::billable_size_v) + ); + + db.remove(*link); + context.checktime( 3000 ); +} + +static const abi_serializer& get_abi_serializer() { + static optional _abi_serializer; + if (!_abi_serializer) { + _abi_serializer.emplace(eosio_contract_abi(abi_def())); + } + + return *_abi_serializer; +} + + +void apply_eosio_canceldelay(apply_context& context) { + auto cancel = context.act.data_as(); + context.require_authorization(cancel.canceling_auth.actor); // only here to mark the single authority on this action as used + + const auto& trx_id = cancel.trx_id; + + context.cancel_deferred_transaction(transaction_id_to_sender_id(trx_id), account_name()); + + context.checktime( 1000 ); +} + +} } // namespace eosio::chain diff --git a/libraries/chain/contracts/chain_initializer.cpp b/libraries/chain/eosio_contract_abi.cpp similarity index 59% rename from libraries/chain/contracts/chain_initializer.cpp rename to libraries/chain/eosio_contract_abi.cpp index bf03b3681c5..185395dd17e 100644 --- a/libraries/chain/contracts/chain_initializer.cpp +++ b/libraries/chain/eosio_contract_abi.cpp @@ -1,61 +1,13 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include +#include -#include -#include +namespace eosio { namespace chain { -#include - -#include -#include - -namespace eosio { namespace chain { namespace contracts { - -time_point chain_initializer::get_chain_start_time() { - return genesis.initial_timestamp; -} - -chain_config chain_initializer::get_chain_start_configuration() { - return genesis.initial_configuration; -} - -producer_schedule_type chain_initializer::get_chain_start_producers() { - producer_schedule_type result; - result.producers.push_back( {config::system_account_name, genesis.initial_key} ); - return result; -} - -void chain_initializer::register_types(chain_controller& chain, chainbase::database& db) { - -#define SET_APP_HANDLER( contract, scope, action, nspace ) \ - chain._set_apply_handler( #contract, #scope, #action, &BOOST_PP_CAT(contracts::apply_, BOOST_PP_CAT(contract, BOOST_PP_CAT(_,action) ) ) ) - SET_APP_HANDLER( eosio, eosio, newaccount, eosio ); - SET_APP_HANDLER( eosio, eosio, setcode, eosio ); - SET_APP_HANDLER( eosio, eosio, setabi, eosio ); - SET_APP_HANDLER( eosio, eosio, updateauth, eosio ); - SET_APP_HANDLER( eosio, eosio, deleteauth, eosio ); - SET_APP_HANDLER( eosio, eosio, linkauth, eosio ); - SET_APP_HANDLER( eosio, eosio, unlinkauth, eosio ); - SET_APP_HANDLER( eosio, eosio, onerror, eosio ); - SET_APP_HANDLER( eosio, eosio, postrecovery, eosio ); - SET_APP_HANDLER( eosio, eosio, passrecovery, eosio ); - SET_APP_HANDLER( eosio, eosio, vetorecovery, eosio ); - SET_APP_HANDLER( eosio, eosio, canceldelay, eosio ); -} - - -abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) +abi_def eosio_contract_abi(const abi_def& eosio_system_abi) { abi_def eos_abi(eosio_system_abi); eos_abi.types.push_back( type_def{"account_name","name"} ); eos_abi.types.push_back( type_def{"table_name","name"} ); eos_abi.types.push_back( type_def{"share_type","int64"} ); - eos_abi.types.push_back( type_def{"onerror","bytes"} ); eos_abi.types.push_back( type_def{"context_free_type","bytes"} ); eos_abi.types.push_back( type_def{"weight_type","uint16"} ); eos_abi.types.push_back( type_def{"fields","field[]"} ); @@ -102,8 +54,7 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) {"account", "account_name"}, {"permission", "permission_name"}, {"parent", "permission_name"}, - {"data", "authority"}, - {"delay", "uint32"} + {"auth", "authority"} } }); @@ -144,7 +95,7 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) eos_abi.structs.emplace_back( struct_def { "postrecovery", "", { {"account", "account_name"}, - {"data", "authority"}, + {"auth", "authority"}, {"memo", "string"}, } }); @@ -221,21 +172,13 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) } }); - eos_abi.structs.emplace_back( struct_def { - "permission_level_weight", "", { - {"permission", "permission_level"}, - {"weight", "weight_type"} - } - }); - eos_abi.structs.emplace_back( struct_def { "transaction_header", "", { {"expiration", "time_point_sec"}, - {"region", "uint16"}, {"ref_block_num", "uint16"}, {"ref_block_prefix", "uint32"}, {"max_net_usage_words", "varuint32"}, - {"max_kcpu_usage", "varuint32"}, + {"max_cpu_usage_ms", "uint8"}, {"delay_sec", "varuint32"} } }); @@ -261,13 +204,29 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) } }); + eos_abi.structs.emplace_back( struct_def { + "permission_level_weight", "", { + {"permission", "permission_level"}, + {"weight", "weight_type"} + } + }); + + eos_abi.structs.emplace_back( struct_def { + "wait_weight", "", { + {"wait_sec", "uint32"}, + {"weight", "weight_type"} + } + }); + eos_abi.structs.emplace_back( struct_def { "authority", "", { {"threshold", "uint32"}, {"keys", "key_weight[]"}, - {"accounts", "permission_level_weight[]"} + {"accounts", "permission_level_weight[]"}, + {"waits", "wait_weight[]"} } }); + eos_abi.structs.emplace_back( struct_def { "clause_pair", "", { {"id", "string"}, @@ -328,75 +287,13 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi) } }); - return eos_abi; -} - -void chain_initializer::prepare_database( chain_controller& chain, - chainbase::database& db) { - /// Reserve id of 0 in permission_index by creating dummy permission_object as the very first object in the index: - db.create([&](permission_object& p) { + eos_abi.structs.emplace_back( struct_def { + "onerror", "", { + {"sender_id", "uint128"}, + {"sent_trx", "bytes"} + } }); - /// Create the native contract accounts manually; sadly, we can't run their contracts to make them create themselves - auto create_native_account = [this, &chain, &db](account_name name) { - db.create([this, &name](account_object& a) { - a.name = name; - a.creation_date = genesis.initial_timestamp; - a.privileged = true; - - if( name == config::system_account_name ) { - a.set_abi(eos_contract_abi(abi_def())); - } - }); - const auto& owner = db.create([&](permission_object& p) { - p.owner = name; - p.name = "owner"; - p.auth.threshold = 1; - p.auth.keys.push_back( key_weight{ .key = genesis.initial_key, .weight = 1 } ); - }); - db.create([&](permission_object& p) { - p.owner = name; - p.parent = owner.id; - p.name = "active"; - p.auth.threshold = 1; - p.auth.keys.push_back( key_weight{ .key = genesis.initial_key, .weight = 1 } ); - }); - - chain.get_mutable_resource_limits_manager().initialize_account(name); - - db.create( [&]( auto& pro ) { - pro.owner = config::system_account_name; - pro.signing_key = genesis.initial_key; - }); - }; - create_native_account(config::system_account_name); - - // Create special accounts - auto create_special_account = [this, &db](account_name name, const auto& owner, const auto& active) { - db.create([this, &name](account_object& a) { - a.name = name; - a.creation_date = genesis.initial_timestamp; - }); - const auto& owner_permission = db.create([&owner, &name](permission_object& p) { - p.name = config::owner_name; - p.parent = 0; - p.owner = name; - p.auth = move(owner); - }); - db.create([&active, &owner_permission](permission_object& p) { - p.name = config::active_name; - p.parent = owner_permission.id; - p.owner = owner_permission.owner; - p.auth = move(active); - }); - }; - - auto empty_authority = authority(0, {}, {}); - auto active_producers_authority = authority(0, {}, {}); - active_producers_authority.accounts.push_back({{config::system_account_name, config::active_name}, 1}); - - create_special_account(config::nobody_account_name, empty_authority, empty_authority); - create_special_account(config::producers_account_name, empty_authority, active_producers_authority); + return eos_abi; } - -} } } // namespace eosio::chain::contracts +} } /// eosio::chain diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index fcabf13492e..dca3bea885e 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -1,236 +1,349 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ #include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace eosio { namespace chain { -fork_database::fork_database() -{ -} -void fork_database::reset() -{ - _head.reset(); - _index.clear(); -} - -void fork_database::pop_block() -{ - FC_ASSERT( _head, "no blocks to pop" ); - auto prev = _head->prev.lock(); - _head = prev; -} - -void fork_database::start_block(signed_block b) -{ - auto item = std::make_shared(std::move(b)); - _index.insert(item); - _head = item; -} - -/** - * Pushes the block into the fork database and caches it if it doesn't link - * - */ -shared_ptr fork_database::push_block(const signed_block& b) -{ - auto item = std::make_shared(b); - try { - _push_block(item); + using boost::multi_index_container; + using namespace boost::multi_index; + + + struct by_block_id; + struct by_block_num; + struct by_lib_block_num; + struct by_prev; + typedef multi_index_container< + block_state_ptr, + indexed_by< + hashed_unique< tag, member, std::hash>, + ordered_non_unique< tag, const_mem_fun >, + ordered_non_unique< tag, + composite_key< block_state, + member, + member + >, + composite_key_compare< std::less, std::greater > + >, + ordered_non_unique< tag, + composite_key< block_header_state, + member, + member, + member + >, + composite_key_compare< std::greater, std::greater, std::greater > + > + > + > fork_multi_index_type; + + + struct fork_database_impl { + fork_multi_index_type index; + block_state_ptr head; + fc::path datadir; + }; + + + fork_database::fork_database( const fc::path& data_dir ):my( new fork_database_impl() ) { + my->datadir = data_dir; + + if (!fc::is_directory(my->datadir)) + fc::create_directories(my->datadir); + + auto fork_db_dat = my->datadir / "forkdb.dat"; + if( fc::exists( fork_db_dat ) ) { + string content; + fc::read_file_contents( fork_db_dat, content ); + + fc::datastream ds( content.data(), content.size() ); + vector states; + fc::raw::unpack( ds, states ); + + for( auto& s : states ) { + set( std::make_shared( move( s ) ) ); + } + block_id_type head_id; + fc::raw::unpack( ds, head_id ); + + my->head = get_block( head_id ); + } } - catch ( const unlinkable_block_exception& e ) - { - wlog( "Pushing block to fork database that failed to link: ${id}, ${num}", ("id",b.id())("num",b.block_num()) ); - wlog( "Head: ${num}, ${id}", ("num",_head->data.block_num())("id",_head->data.id()) ); - throw; - _unlinked_index.insert( item ); + + void fork_database::close() { + if( my->index.size() == 0 ) return; + + fc::datastream ps; + vector states; + states.reserve( my->index.size() ); + for( const auto& s : my->index ) { + states.push_back( *s ); + } + + auto fork_db_dat = my->datadir / "forkdb.dat"; + std::ofstream out( fork_db_dat.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); + fc::raw::pack( out, states ); + if( my->head ) + fc::raw::pack( out, my->head->id ); + else + fc::raw::pack( out, block_id_type() ); + idump((states.size())); + + + /// we don't normally indicate the head block as irreversible + /// we cannot normally prune the lib if it is the head block because + /// the next block needs to build off of the head block. We are exiting + /// now so we can prune this block as irreversible before exiting. + auto lib = my->head->dpos_irreversible_blocknum; + auto oldest = *my->index.get().begin(); + if( oldest->block_num <= lib ) { + prune( oldest ); + } + + my->index.clear(); + } + + fork_database::~fork_database() { + close(); } - return _head; -} - -void fork_database::_push_block(const item_ptr& item) -{ - if( _head ) // make sure the block is within the range that we are caching - { - EOS_ASSERT( item->num > std::max( 0, int64_t(_head->num) - (_max_size) ), - block_too_old_exception, - "attempting to push a block that is too old", - ("item->num",item->num)("head",_head->num)("max_size",_max_size)); + + void fork_database::set( block_state_ptr s ) { + auto result = my->index.insert( s ); + FC_ASSERT( s->id == s->header.id() ); + + //FC_ASSERT( s->block_num == s->header.block_num() ); + + FC_ASSERT( result.second, "unable to insert block state, duplicate state detected" ); + if( !my->head ) { + my->head = s; + } else if( my->head->block_num < s->block_num ) { + my->head = s; + } } - if( _head && item->previous_id() != block_id_type() ) - { - auto& index = _index.get(); - auto itr = index.find(item->previous_id()); - EOS_ASSERT(itr != index.end(), unlinkable_block_exception, "block does not link to known chain"); - FC_ASSERT(!(*itr)->invalid); - item->prev = *itr; + block_state_ptr fork_database::add( block_state_ptr n ) { + auto inserted = my->index.insert(n); + FC_ASSERT( inserted.second, "duplicate block added?" ); + + my->head = *my->index.get().begin(); + + auto lib = my->head->dpos_irreversible_blocknum; + auto oldest = *my->index.get().begin(); + + if( oldest->block_num < lib ) { + prune( oldest ); + } + + return n; } - _index.insert(item); - if( !_head ) _head = item; - else if( item->num > _head->num ) - { - uint32_t delta = item->data.timestamp.slot - _head->data.timestamp.slot; - if (delta > 1) - wlog("Number of missed blocks: ${num}", ("num", delta-1)); - _head = item; - uint32_t min_num = _head->num - std::min( _max_size, _head->num ); -// ilog( "min block in fork DB ${n}, max_size: ${m}", ("n",min_num)("m",_max_size) ); - auto& num_idx = _index.get(); - while( num_idx.size() && (*num_idx.begin())->num < min_num ) - num_idx.erase( num_idx.begin() ); - - _unlinked_index.get().erase(_head->num - _max_size); + block_state_ptr fork_database::add( signed_block_ptr b ) { + FC_ASSERT( b, "attempt to add null block" ); + FC_ASSERT( my->head, "no head block set" ); + + const auto& by_id_idx = my->index.get(); + auto existing = by_id_idx.find( b->id() ); + FC_ASSERT( existing == by_id_idx.end(), "we already know about this block" ); + + auto prior = by_id_idx.find( b->previous ); + FC_ASSERT( prior != by_id_idx.end(), "unlinkable block", ("id", b->id())("previous", b->previous) ); + + auto result = std::make_shared( **prior, move(b) ); + FC_ASSERT( result ); + return add(result); } - //_push_next( item ); -} - -/** - * Iterate through the unlinked cache and insert anything that - * links to the newly inserted item. This will start a recursive - * set of calls performing a depth-first insertion of pending blocks as - * _push_next(..) calls _push_block(...) which will in turn call _push_next - */ -void fork_database::_push_next( const item_ptr& new_item ) -{ - auto& prev_idx = _unlinked_index.get(); - - auto itr = prev_idx.find( new_item->id ); - while( itr != prev_idx.end() ) - { - auto tmp = *itr; - prev_idx.erase( itr ); - _push_block( tmp ); - - itr = prev_idx.find( new_item->id ); - } -} - -void fork_database::set_max_size( uint32_t s ) -{ - _max_size = s; - if( !_head ) return; - - { /// index - auto& by_num_idx = _index.get(); - auto itr = by_num_idx.begin(); - while( itr != by_num_idx.end() ) + + const block_state_ptr& fork_database::head()const { return my->head; } + + /** + * Given two head blocks, return two branches of the fork graph that + * end with a common ancestor (same prior block) + */ + pair< branch_type, branch_type > fork_database::fetch_branch_from( const block_id_type& first, + const block_id_type& second )const { + pair result; + auto first_branch = get_block(first); + auto second_branch = get_block(second); + + while( first_branch->block_num > second_branch->block_num ) { - if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) - by_num_idx.erase(itr); - else - break; - itr = by_num_idx.begin(); + result.first.push_back(first_branch); + first_branch = get_block( first_branch->header.previous ); + FC_ASSERT( first_branch ); } - } - { /// unlinked_index - auto& by_num_idx = _unlinked_index.get(); - auto itr = by_num_idx.begin(); - while( itr != by_num_idx.end() ) + + while( second_branch->block_num > first_branch->block_num ) + { + result.second.push_back( second_branch ); + second_branch = get_block( second_branch->header.previous ); + FC_ASSERT( second_branch ); + } + + while( first_branch->header.previous != second_branch->header.previous ) + { + result.first.push_back(first_branch); + result.second.push_back(second_branch); + first_branch = get_block( first_branch->header.previous ); + second_branch = get_block( second_branch->header.previous ); + FC_ASSERT( first_branch && second_branch ); + } + + if( first_branch && second_branch ) { - if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) - by_num_idx.erase(itr); - else - break; - itr = by_num_idx.begin(); + result.first.push_back(first_branch); + result.second.push_back(second_branch); } + return result; + } /// fetch_branch_from + + /// remove all of the invalid forks built of this id including this id + void fork_database::remove( const block_id_type& id ) { + vector remove_queue{id}; + + for( uint32_t i = 0; i < remove_queue.size(); ++i ) { + auto itr = my->index.find( remove_queue[i] ); + if( itr != my->index.end() ) + my->index.erase(itr); + + auto& previdx = my->index.get(); + auto previtr = previdx.find(id); + while( previtr != previdx.end() ) { + remove_queue.push_back( (*previtr)->id ); + previdx.erase(previtr); + previtr = previdx.find(id); + } + } + wdump((my->index.size())); } -} - -bool fork_database::is_known_block(const block_id_type& id)const -{ - auto& index = _index.get(); - auto itr = index.find(id); - if( itr != index.end() ) - return true; - auto& unlinked_index = _unlinked_index.get(); - auto unlinked_itr = unlinked_index.find(id); - return unlinked_itr != unlinked_index.end(); -} - -item_ptr fork_database::fetch_block(const block_id_type& id)const -{ - auto& index = _index.get(); - auto itr = index.find(id); - if( itr != index.end() ) - return *itr; - auto& unlinked_index = _unlinked_index.get(); - auto unlinked_itr = unlinked_index.find(id); - if( unlinked_itr != unlinked_index.end() ) - return *unlinked_itr; - return item_ptr(); -} - -vector fork_database::fetch_block_by_number(uint32_t num)const -{ - vector result; - auto itr = _index.get().find(num); - while( itr != _index.get().end() ) - { - if( (*itr)->num == num ) - result.push_back( *itr ); - else - break; - ++itr; + + void fork_database::set_validity( const block_state_ptr& h, bool valid ) { + if( !valid ) { + remove( h->id ); + } else { + /// remove older than irreversible and mark block as valid + h->validated = true; + } } - return result; -} - -pair - fork_database::fetch_branch_from(block_id_type first, block_id_type second)const -{ try { - // This function gets a branch (i.e. vector) leading - // back to the most recent common ancestor. - pair result; - auto first_branch_itr = _index.get().find(first); - FC_ASSERT(first_branch_itr != _index.get().end()); - auto first_branch = *first_branch_itr; - - auto second_branch_itr = _index.get().find(second); - FC_ASSERT(second_branch_itr != _index.get().end()); - auto second_branch = *second_branch_itr; - - - while( first_branch->data.block_num() > second_branch->data.block_num() ) - { - result.first.push_back(first_branch); - first_branch = first_branch->prev.lock(); - FC_ASSERT(first_branch); + + void fork_database::mark_in_current_chain( const block_state_ptr& h, bool in_current_chain ) { + if( h->in_current_chain == in_current_chain ) + return; + + auto& by_id_idx = my->index.get(); + auto itr = by_id_idx.find( h->id ); + FC_ASSERT( itr != by_id_idx.end(), "could not find block in fork database" ); + + by_id_idx.modify( itr, [&]( auto& bsp ) { // Need to modify this way rather than directly so that Boost MultiIndex can re-sort + bsp->in_current_chain = in_current_chain; + }); + } + + void fork_database::prune( const block_state_ptr& h ) { + auto num = h->block_num; + + auto& by_bn = my->index.get(); + auto bni = by_bn.begin(); + while( bni != by_bn.end() && (*bni)->block_num < num ) { + prune( *bni ); + bni = by_bn.begin(); + } + + auto itr = my->index.find( h->id ); + if( itr != my->index.end() ) { + irreversible(*itr); + my->index.erase(itr); + } + + auto& numidx = my->index.get(); + auto nitr = numidx.lower_bound( num ); + while( nitr != numidx.end() && (*nitr)->block_num == num ) { + auto itr_to_remove = nitr; + ++nitr; + auto id = (*itr_to_remove)->id; + remove( id ); + } } - while( second_branch->data.block_num() > first_branch->data.block_num() ) - { - result.second.push_back( second_branch ); - second_branch = second_branch->prev.lock(); - FC_ASSERT(second_branch); + + block_state_ptr fork_database::get_block(const block_id_type& id)const { + auto itr = my->index.find( id ); + if( itr != my->index.end() ) + return *itr; + return block_state_ptr(); } - while( first_branch->data.previous != second_branch->data.previous ) - { - result.first.push_back(first_branch); - result.second.push_back(second_branch); - first_branch = first_branch->prev.lock(); - FC_ASSERT(first_branch); - second_branch = second_branch->prev.lock(); - FC_ASSERT(second_branch); + + block_state_ptr fork_database::get_block_in_current_chain_by_num( uint32_t n )const { + const auto& numidx = my->index.get(); + auto nitr = numidx.lower_bound( n ); + // following asserts removed so null can be returned + //FC_ASSERT( nitr != numidx.end() && (*nitr)->block_num == n, + // "could not find block in fork database with block number ${block_num}", ("block_num", n) ); + //FC_ASSERT( (*nitr)->in_current_chain == true, + // "block (with block number ${block_num}) found in fork database is not in the current chain", ("block_num", n) ); + if( nitr == numidx.end() || (*nitr)->block_num != n || (*nitr)->in_current_chain != true ) + return block_state_ptr(); + return *nitr; } - if( first_branch && second_branch ) - { - result.first.push_back(first_branch); - result.second.push_back(second_branch); + + void fork_database::add( const header_confirmation& c ) { + auto b = get_block( c.block_id ); + FC_ASSERT( b, "unable to find block id ${id}", ("id",c.block_id)); + b->add_confirmation( c ); + + if( b->bft_irreversible_blocknum < b->block_num && + b->confirmations.size() > ((b->active_schedule.producers.size() * 2) / 3) ) { + set_bft_irreversible( c.block_id ); + } } - return result; -} FC_CAPTURE_AND_RETHROW( (first)(second) ) } -void fork_database::set_head(shared_ptr h) -{ - _head = h; -} + /** + * This method will set this block as being BFT irreversible and will update + * all blocks which build off of it to have the same bft_irb if their existing + * bft irb is less than this block num. + * + * This will require a search over all forks + */ + void fork_database::set_bft_irreversible( block_id_type id ) { + auto& idx = my->index.get(); + auto itr = idx.find(id); + uint32_t block_num = (*itr)->block_num; + idx.modify( itr, [&]( auto& bsp ) { + bsp->bft_irreversible_blocknum = bsp->block_num; + }); -void fork_database::remove(block_id_type id) -{ - _index.get().erase(id); -} + /** to prevent stack-overflow, we perform a bredth-first traversal of the + * fork database. At each stage we iterate over the leafs from the prior stage + * and find all nodes that link their previous. If we update the bft lib then we + * add it to a queue for the next layer. This lambda takes one layer and returns + * all block ids that need to be iterated over for next layer. + */ + auto update = [&]( const vector& in ) { + vector updated; + + for( const auto& i : in ) { + auto& pidx = my->index.get(); + auto pitr = pidx.lower_bound( i ); + auto epitr = pidx.upper_bound( i ); + while( pitr != epitr ) { + pidx.modify( pitr, [&]( auto& bsp ) { + if( bsp->bft_irreversible_blocknum < block_num ) { + bsp->bft_irreversible_blocknum = block_num; + updated.push_back( bsp->id ); + } + }); + ++pitr; + } + } + return updated; + }; + + vector queue{id}; + while( queue.size() ) { + queue = update( queue ); + } + } -} } // eosio::chain +} } /// eosio::chain diff --git a/libraries/chain/genesis_state.cpp b/libraries/chain/genesis_state.cpp new file mode 100644 index 00000000000..f17a5ce55f8 --- /dev/null +++ b/libraries/chain/genesis_state.cpp @@ -0,0 +1,18 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +#include + +// these are required to serialize a genesis_state +#include // required for gcc in release mode + +namespace eosio { namespace chain { + + +chain::chain_id_type genesis_state::compute_chain_id() const { + return initial_chain_id; +} + +} } // namespace eosio::chain diff --git a/libraries/chain/get_config.cpp b/libraries/chain/get_config.cpp deleted file mode 100644 index 54fcd85350a..00000000000 --- a/libraries/chain/get_config.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#include -#include -#include - -namespace eosio { namespace chain { - -fc::variant_object get_config() -{ - fc::mutable_variant_object result; - -// result["block_interval_ms"] = config::block_interval_ms; -// result["producer_count"] = config::producer_count; - /// TODO: add extra config parms - return result; -} - -} } // eosio::chain diff --git a/libraries/chain/global_property_object.cpp b/libraries/chain/global_property_object.cpp deleted file mode 100644 index bc5270d2772..00000000000 --- a/libraries/chain/global_property_object.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -namespace eosio { namespace chain { - -} } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/contracts/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp similarity index 89% rename from libraries/chain/include/eosio/chain/contracts/abi_serializer.hpp rename to libraries/chain/include/eosio/chain/abi_serializer.hpp index dc45a37c669..2ddb0c98bd6 100644 --- a/libraries/chain/include/eosio/chain/contracts/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -3,12 +3,12 @@ * @copyright defined in eos/LICENSE.txt */ #pragma once -#include -#include +#include +#include #include #include -namespace eosio { namespace chain { namespace contracts { +namespace eosio { namespace chain { using std::map; using std::string; @@ -96,12 +96,10 @@ namespace impl { constexpr bool single_type_requires_abi_v() { return std::is_base_of::value || std::is_same::value || - std::is_same::value || std::is_same::value || std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_base_of:: value; + std::is_same::value || + std::is_same::value; } /** @@ -176,6 +174,19 @@ namespace impl { mvo(name, std::move(array)); } + /** + * template which overloads add for shared_ptr of types which contain ABI information in their trees + * for these members we call ::add in order to trigger further processing + */ + template = 1> + static void add( mutable_variant_object &mvo, const char* name, const std::shared_ptr& v, Resolver resolver ) + { + if( !v ) return; + mutable_variant_object obj_mvo; + add(obj_mvo, "_", *v, resolver); + mvo(name, std::move(obj_mvo["_"])); + } + template static void add( mutable_variant_object &mvo, const char* name, const fc::static_variant& v, Resolver resolver ) { @@ -207,6 +218,26 @@ namespace impl { out(name, std::move(mvo)); } + /** + * overload of to_variant_object for actions + * @tparam Resolver + * @param act + * @param resolver + * @return + template + static void add(mutable_variant_object &out, const char* name, const action_trace& act, Resolver resolver) { + mutable_variant_object mvo; + mvo("receipt", act.receipt); + mvo("elapsed", act.elapsed); + mvo("cpu_usage", act.cpu_usage); + mvo("console", act.console); + mvo("total_cpu_usage", act.total_inline_cpu_usage); + mvo("inline_traces", act.inline_traces); + out(name, std::move(mvo)); + } + */ + + /** * overload of to_variant_object for packed_transaction * @tparam Resolver @@ -299,6 +330,19 @@ namespace impl { } } + /** + * template which overloads extract for shared_ptr of types which contain ABI information in their trees + * for these members we call ::extract in order to trigger further processing + */ + template = 1> + static void extract( const variant& v, std::shared_ptr& o, Resolver resolver ) + { + const variant_object& vo = v.get_object(); + M obj; + extract(vo, obj, resolver); + o = std::make_shared(obj); + } + /** * Non templated overload that has priority for the action structure * this type has members which must be directly translated by the ABI so it is @@ -346,7 +390,6 @@ namespace impl { template static void extract( const variant& v, packed_transaction& ptrx, Resolver resolver ) { const variant_object& vo = v.get_object(); - wdump((vo)); EOS_ASSERT(vo.contains("signatures"), packed_transaction_type_exception, "Missing signatures"); EOS_ASSERT(vo.contains("compression"), packed_transaction_type_exception, "Missing compression"); from_variant(vo["signatures"], ptrx.signatures); @@ -444,4 +487,4 @@ void abi_serializer::from_variant( const variant& v, T& o, Resolver resolver ) t } FC_RETHROW_EXCEPTIONS(error, "Failed to deserialize variant", ("variant",v)) -} } } // eosio::chain::contracts +} } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/account_object.hpp b/libraries/chain/include/eosio/chain/account_object.hpp index a4958eff7f4..f7d0dc2e42b 100644 --- a/libraries/chain/include/eosio/chain/account_object.hpp +++ b/libraries/chain/include/eosio/chain/account_object.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include "multi_index_includes.hpp" @@ -21,23 +21,23 @@ namespace eosio { namespace chain { uint8_t vm_version = 0; bool privileged = false; - time_point_sec last_code_update; + time_point last_code_update; digest_type code_version; block_timestamp_type creation_date; - shared_vector code; - shared_vector abi; + shared_string code; + shared_string abi; - void set_abi( const eosio::chain::contracts::abi_def& a ) { - // Added resize(0) here to avoid bug in boost vector container - abi.resize( 0 ); + void set_abi( const eosio::chain::abi_def& a ) { abi.resize( fc::raw::pack_size( a ) ); fc::datastream ds( abi.data(), abi.size() ); fc::raw::pack( ds, a ); } - eosio::chain::contracts::abi_def get_abi()const { - eosio::chain::contracts::abi_def a; + eosio::chain::abi_def get_abi()const { + eosio::chain::abi_def a; + FC_ASSERT( abi.size() != 0, "No ABI set on account ${n}", ("n",name) ); + fc::datastream ds( abi.data(), abi.size() ); fc::raw::unpack( ds, a ); return a; @@ -54,9 +54,30 @@ namespace eosio { namespace chain { > >; + + class account_sequence_object : public chainbase::object + { + OBJECT_CTOR(account_sequence_object); + + id_type id; + account_name name; + uint64_t recv_sequence = 0; + uint64_t auth_sequence = 0; + }; + + struct by_name; + using account_sequence_index = chainbase::shared_multi_index_container< + account_sequence_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member> + > + >; + } } // eosio::chain CHAINBASE_SET_INDEX_TYPE(eosio::chain::account_object, eosio::chain::account_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::account_sequence_object, eosio::chain::account_sequence_index) FC_REFLECT(eosio::chain::account_object, (name)(vm_type)(vm_version)(code_version)(code)(creation_date)) diff --git a/libraries/chain/include/eosio/chain/action.hpp b/libraries/chain/include/eosio/chain/action.hpp new file mode 100644 index 00000000000..15cff8dcf54 --- /dev/null +++ b/libraries/chain/include/eosio/chain/action.hpp @@ -0,0 +1,102 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include + +namespace eosio { namespace chain { + + struct permission_level { + account_name actor; + permission_name permission; + }; + + inline bool operator== (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) == std::tie(rhs.actor, rhs.permission); + } + + inline bool operator!= (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) != std::tie(rhs.actor, rhs.permission); + } + + inline bool operator< (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) < std::tie(rhs.actor, rhs.permission); + } + + inline bool operator<= (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) <= std::tie(rhs.actor, rhs.permission); + } + + inline bool operator> (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) > std::tie(rhs.actor, rhs.permission); + } + + inline bool operator>= (const permission_level& lhs, const permission_level& rhs) { + return std::tie(lhs.actor, lhs.permission) >= std::tie(rhs.actor, rhs.permission); + } + + /** + * An action is performed by an actor, aka an account. It may + * be created explicitly and authorized by signatures or might be + * generated implicitly by executing application code. + * + * This follows the design pattern of React Flux where actions are + * named and then dispatched to one or more action handlers (aka stores). + * In the context of eosio, every action is dispatched to the handler defined + * by account 'scope' and function 'name', but the default handler may also + * forward the action to any number of additional handlers. Any application + * can write a handler for "scope::name" that will get executed if and only if + * this action is forwarded to that application. + * + * Each action may require the permission of specific actors. Actors can define + * any number of permission levels. The actors and their respective permission + * levels are declared on the action and validated independently of the executing + * application code. An application code will check to see if the required authorization + * were properly declared when it executes. + */ + struct action { + account_name account; + action_name name; + vector authorization; + bytes data; + + action(){} + + template::value, int> = 1> + action( vector auth, const T& value ) { + account = T::get_account(); + name = T::get_name(); + authorization = move(auth); + data.assign(value.data(), value.data() + value.size()); + } + + template::value, int> = 1> + action( vector auth, const T& value ) { + account = T::get_account(); + name = T::get_name(); + authorization = move(auth); + data = fc::raw::pack(value); + } + + action( vector auth, account_name account, action_name name, const bytes& data ) + : account(account), name(name), authorization(move(auth)), data(data) { + } + + template + T data_as()const { + FC_ASSERT( account == T::get_account() ); + FC_ASSERT( name == T::get_name() ); + return fc::raw::unpack(data); + } + }; + + struct action_notice : public action { + account_name receiver; + }; + +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::permission_level, (actor)(permission) ) +FC_REFLECT( eosio::chain::action, (account)(name)(authorization)(data) ) diff --git a/libraries/chain/include/eosio/chain/action_objects.hpp b/libraries/chain/include/eosio/chain/action_objects.hpp deleted file mode 100644 index 4a0e9e2f5c2..00000000000 --- a/libraries/chain/include/eosio/chain/action_objects.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include -#include - -#include "multi_index_includes.hpp" - -namespace eosio { namespace chain { - - /** - * Maps the permission level on the code to the permission level specififed by owner, when specifying a contract the - * contract will specify 1 permission_object per action, and by default the parent of that permission object will be - * the active permission of the contract; however, the contract owner could group their actions any way they like. - * - * When it comes time to evaluate whether User can call Action on Contract with UserPermissionLevel the algorithm - * operates as follows: - * - * let scope_permission = action_code.permission - * while( ! mapping for (scope_permission / owner ) - * scope_permission = scope_permission.parent - * if( !scope_permission ) - * user permission => active - * break; - * - * Now that we know the required user permission... - * - * while( ! transaction.has( user_permission ) ) - * user_permission = user_permission.parent - * if( !user_permission ) - * throw invalid permission - * pass - */ - class action_permission_object : public chainbase::object - { - OBJECT_CTOR(action_permission_object) - - id_type id; - account_name owner; ///< the account whose permission we seek - permission_object::id_type scope_permission; ///< the scope permission defined by the contract for the action - permission_object::id_type owner_permission; ///< the owner permission that is required - }; - - struct by_owner_scope; - using action_permission_index = chainbase::shared_multi_index_container< - action_permission_object, - indexed_by< - ordered_unique, member>, - ordered_unique, - composite_key< action_permission_object, - member, - member - > - > - > - >; - -} } // eosio::chain - -CHAINBASE_SET_INDEX_TYPE(eosio::chain::action_permission_object, eosio::chain::action_permission_index) - -FC_REFLECT(eosio::chain::action_permission_object, (id)(owner)(owner_permission)(scope_permission) ) diff --git a/libraries/chain/include/eosio/chain/action_receipt.hpp b/libraries/chain/include/eosio/chain/action_receipt.hpp new file mode 100644 index 00000000000..424520634d6 --- /dev/null +++ b/libraries/chain/include/eosio/chain/action_receipt.hpp @@ -0,0 +1,26 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include + +namespace eosio { namespace chain { + + /** + * For each action dispatched this receipt is generated + */ + struct action_receipt { + account_name receiver; + digest_type act_digest; + uint64_t global_sequence = 0; ///< total number of actions dispatched since genesis + uint64_t recv_sequence = 0; ///< total number of actions with this receiver since genesis + flat_map auth_sequence; + + digest_type digest()const { return digest_type::hash(*this); } + }; + +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::action_receipt, (receiver)(act_digest)(global_sequence)(recv_sequence)(auth_sequence) ) diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 34afaf2f13f..0577ae4faa4 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -3,10 +3,9 @@ * @copyright defined in eos/LICENSE.txt */ #pragma once -#include +#include #include -#include -#include +#include #include #include #include @@ -16,17 +15,14 @@ namespace chainbase { class database; } namespace eosio { namespace chain { -using contracts::key_value_object; - -class chain_controller; +class controller; +class transaction_context; class apply_context { private: template class iterator_cache { public: - typedef contracts::table_id_object table_id_object; - iterator_cache(){ _end_iterator_to_table.reserve(8); _iterator_to_object.reserve(32); @@ -104,7 +100,7 @@ class apply_context { inline size_t end_iterator_to_index( int ei )const { return (-ei - 2); } /// Precondition: indx < _end_iterator_to_table.size() <= std::numeric_limits::max() inline int index_to_end_iterator( size_t indx )const { return -(indx + 2); } - }; + }; /// class iterator_cache template struct array_size; @@ -132,7 +128,7 @@ class apply_context { sk_from_wasm = sk_in_table; } - static auto create_tuple(const contracts::table_id_object& tab, const secondary_key_type& secondary) { + static auto create_tuple(const table_id_object& tab, const secondary_key_type& secondary) { return boost::make_tuple( tab.id, secondary ); } }; @@ -157,7 +153,7 @@ class apply_context { std::copy(sk_in_table.begin(), sk_in_table.end(), sk_from_wasm); } - static auto create_tuple(const contracts::table_id_object& tab, secondary_key_proxy_const_type sk_from_wasm) { + static auto create_tuple(const table_id_object& tab, secondary_key_proxy_const_type sk_from_wasm) { secondary_key_type secondary; std::copy(sk_from_wasm, sk_from_wasm + N, secondary.begin()); return boost::make_tuple( tab.id, secondary ); @@ -185,18 +181,18 @@ class apply_context { { FC_ASSERT( payer != account_name(), "must specify a valid account to pay for new record" ); - context.require_write_lock( scope ); +// context.require_write_lock( scope ); const auto& tab = context.find_or_create_table( context.receiver, scope, table, payer ); - const auto& obj = context.mutable_db.create( [&]( auto& o ){ + const auto& obj = context.db.create( [&]( auto& o ){ o.t_id = tab.id; o.primary_key = id; secondary_key_helper_t::set(o.secondary_key, value); o.payer = payer; }); - context.mutable_db.modify( tab, [&]( auto& t ) { + context.db.modify( tab, [&]( auto& t ) { ++t.count; }); @@ -213,12 +209,12 @@ class apply_context { const auto& table_obj = itr_cache.get_table( obj.t_id ); FC_ASSERT( table_obj.code == context.receiver, "db access violation" ); - context.require_write_lock( table_obj.scope ); +// context.require_write_lock( table_obj.scope ); - context.mutable_db.modify( table_obj, [&]( auto& t ) { + context.db.modify( table_obj, [&]( auto& t ) { --t.count; }); - context.mutable_db.remove( obj ); + context.db.remove( obj ); if (table_obj.count == 0) { context.remove_table(table_obj); @@ -233,7 +229,7 @@ class apply_context { const auto& table_obj = itr_cache.get_table( obj.t_id ); FC_ASSERT( table_obj.code == context.receiver, "db access violation" ); - context.require_write_lock( table_obj.scope ); +// context.require_write_lock( table_obj.scope ); if( payer == account_name() ) payer = obj.payer; @@ -244,7 +240,7 @@ class apply_context { context.update_db_usage( payer, +(billing_size) ); } - context.mutable_db.modify( obj, [&]( auto& o ) { + context.db.modify( obj, [&]( auto& o ) { secondary_key_helper_t::set(o.secondary_key, secondary); o.payer = payer; }); @@ -256,7 +252,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto* obj = context.db.find( secondary_key_helper_t::create_tuple( *tab, secondary ) ); + const auto* obj = context.db.find( secondary_key_helper_t::create_tuple( *tab, secondary ) ); if( !obj ) return table_end_itr; primary = obj->primary_key; @@ -270,7 +266,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, contracts::by_secondary >(); + const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, by_secondary >(); auto itr = idx.lower_bound( secondary_key_helper_t::create_tuple( *tab, secondary ) ); if( itr == idx.end() ) return table_end_itr; if( itr->t_id != tab->id ) return table_end_itr; @@ -287,7 +283,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, contracts::by_secondary >(); + const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, by_secondary >(); auto itr = idx.upper_bound( secondary_key_helper_t::create_tuple( *tab, secondary ) ); if( itr == idx.end() ) return table_end_itr; if( itr->t_id != tab->id ) return table_end_itr; @@ -309,7 +305,7 @@ class apply_context { if( iterator < -1 ) return -1; // cannot increment past end iterator of index const auto& obj = itr_cache.get(iterator); // Check for iterator != -1 happens in this call - const auto& idx = context.db.get_index::type, contracts::by_secondary>(); + const auto& idx = context.db.get_index::type, by_secondary>(); auto itr = idx.iterator_to(obj); ++itr; @@ -321,7 +317,7 @@ class apply_context { } int previous_secondary( int iterator, uint64_t& primary ) { - const auto& idx = context.db.get_index::type, contracts::by_secondary>(); + const auto& idx = context.db.get_index::type, by_secondary>(); if( iterator < -1 ) // is end iterator { @@ -358,7 +354,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto* obj = context.db.find( boost::make_tuple( tab->id, primary ) ); + const auto* obj = context.db.find( boost::make_tuple( tab->id, primary ) ); if( !obj ) return table_end_itr; secondary_key_helper_t::get(secondary, obj->secondary_key); @@ -371,7 +367,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto& idx = context.db.get_index::type, contracts::by_primary>(); + const auto& idx = context.db.get_index::type, by_primary>(); auto itr = idx.lower_bound(boost::make_tuple(tab->id, primary)); if (itr == idx.end()) return table_end_itr; if (itr->t_id != tab->id) return table_end_itr; @@ -385,7 +381,7 @@ class apply_context { auto table_end_itr = itr_cache.cache_table( *tab ); - const auto& idx = context.db.get_index::type, contracts::by_primary>(); + const auto& idx = context.db.get_index::type, by_primary>(); auto itr = idx.upper_bound(boost::make_tuple(tab->id, primary)); if (itr == idx.end()) return table_end_itr; if (itr->t_id != tab->id) return table_end_itr; @@ -398,7 +394,7 @@ class apply_context { if( iterator < -1 ) return -1; // cannot increment past end iterator of table const auto& obj = itr_cache.get(iterator); // Check for iterator != -1 happens in this call - const auto& idx = context.db.get_index::type, contracts::by_primary>(); + const auto& idx = context.db.get_index::type, by_primary>(); auto itr = idx.iterator_to(obj); ++itr; @@ -410,7 +406,7 @@ class apply_context { } int previous_primary( int iterator, uint64_t& primary ) { - const auto& idx = context.db.get_index::type, contracts::by_primary>(); + const auto& idx = context.db.get_index::type, by_primary>(); if( iterator < -1 ) // is end iterator { @@ -450,36 +446,43 @@ class apply_context { private: apply_context& context; iterator_cache itr_cache; - }; - + }; /// class generic_index - - apply_context(chain_controller& con, chainbase::database& db, const action& a, const transaction_metadata& trx_meta, uint32_t depth=0) - - :controller(con), - db(db), - act(a), - mutable_controller(con), - mutable_db(db), - used_authorizations(act.authorization.size(), false), - trx_meta(trx_meta), - idx64(*this), - idx128(*this), - idx256(*this), - idx_double(*this), - idx_long_double(*this), - recurse_depth(depth) + /// Constructor + public: + apply_context(controller& con, transaction_context& trx_ctx, const action& a, uint32_t depth=0) + :control(con) + ,db(con.db()) + ,trx_context(trx_ctx) + ,act(a) + ,receiver(act.account) + ,used_authorizations(act.authorization.size(), false) + ,recurse_depth(depth) + ,idx64(*this) + ,idx128(*this) + ,idx256(*this) + ,idx_double(*this) + ,idx_long_double(*this) { reset_console(); } + + /// Execution methods: + public: + + action_trace exec_one(); void exec(); + void execute_inline( action&& a ); + void execute_context_free_inline( action&& a ); + void schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx ); + void cancel_deferred_transaction( const uint128_t& sender_id, account_name sender ); + void cancel_deferred_transaction( const uint128_t& sender_id ) { cancel_deferred_transaction(sender_id, receiver); } - void execute_inline( action &&a ); - void execute_context_free_inline( action &&a ); - void execute_deferred( deferred_transaction &&trx ); - void cancel_deferred( const uint128_t& sender_id ); + + /// Authorization methods: + public: /** * @brief Require @ref account to have approved of this message @@ -488,13 +491,11 @@ class apply_context { * This method will check that @ref account is listed in the message's declared authorizations, and marks the * authorization as used. Note that all authorizations on a message must be used, or the message is invalid. * - * @throws tx_missing_auth If no sufficient permission was found + * @throws missing_auth_exception If no sufficient permission was found */ void require_authorization(const account_name& account); bool has_authorization(const account_name& account) const; void require_authorization(const account_name& account, const permission_name& permission); - void require_write_lock(const scope_name& scope); - void require_read_lock(const account_name& account, const scope_name& scope); /** * @return true if account exists, false if it does not @@ -512,39 +513,12 @@ class apply_context { */ bool has_recipient(account_name account)const; - bool all_authorizations_used()const; - vector unused_authorizations()const; - - vector get_active_producers() const; - - const bytes& get_packed_transaction(); - - const chain_controller& controller; - const chainbase::database& db; ///< database where state is stored - const action& act; ///< message being applied - account_name receiver; ///< the code that is currently running - bool privileged = false; - bool context_free = false; - bool used_context_free_api = false; - - chain_controller& mutable_controller; - chainbase::database& mutable_db; - - - ///< Parallel to act.authorization; tracks which permissions have been used while processing the message - vector used_authorizations; - - const transaction_metadata& trx_meta; - - struct apply_results { - vector applied_actions; - vector> deferred_transaction_requests; - size_t deferred_transactions_count = 0; - }; - - apply_results results; + /// Console methods: + public: - std::ostringstream& get_console_stream() { return _pending_console_output; } + void reset_console(); + std::ostringstream& get_console_stream() { return _pending_console_output; } + const std::ostringstream& get_console_stream()const { return _pending_console_output; } template void console_append(T val) { @@ -561,68 +535,86 @@ class apply_context { console_append(fc::format_string(fmt, vo)); } - void checktime(uint32_t instruction_count); - - int get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const; - int get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const; + /// Database methods: + public: void update_db_usage( const account_name& payer, int64_t delta ); - void check_auth( const transaction& trx, const vector& perm ); int db_store_i64( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ); void db_update_i64( int iterator, account_name payer, const char* buffer, size_t buffer_size ); void db_remove_i64( int iterator ); - int db_get_i64( int iterator, char* buffer, size_t buffer_size ); - int db_next_i64( int iterator, uint64_t& primary ); - int db_previous_i64( int iterator, uint64_t& primary ); - int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); - int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); - int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); - int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ); - - generic_index idx64; - generic_index idx128; - generic_index idx256; - generic_index idx_double; - generic_index idx_long_double; - - uint32_t recurse_depth; // how deep inline actions can recurse - - void add_cpu_usage( const uint64_t usage ); + int db_get_i64( int iterator, char* buffer, size_t buffer_size ); + int db_next_i64( int iterator, uint64_t& primary ); + int db_previous_i64( int iterator, uint64_t& primary ); + int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); + int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); + int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); + int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ); private: - iterator_cache keyval_cache; - void append_results(apply_results &&other) { - fc::move_append(results.applied_actions, std::move(other.applied_actions)); - fc::move_append(results.deferred_transaction_requests, std::move(other.deferred_transaction_requests)); - results.deferred_transactions_count += other.deferred_transactions_count; - } - - void reset_console(); - - void exec_one(); - - using table_id_object = contracts::table_id_object; const table_id_object* find_table( name code, name scope, name table ); const table_id_object& find_or_create_table( name code, name scope, name table, const account_name &payer ); - void remove_table( const table_id_object& tid ); + void remove_table( const table_id_object& tid ); int db_store_i64( uint64_t code, uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ); + + /// Misc methods: + public: + + int get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const; + int get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const; + vector get_active_producers() const; + bytes get_packed_transaction(); + + void checktime(uint32_t instruction_count); + + uint64_t next_global_sequence(); + uint64_t next_recv_sequence( account_name receiver ); + uint64_t next_auth_sequence( account_name actor ); + + private: + + void validate_referenced_accounts( const transaction& t )const; + void validate_expiration( const transaction& t )const; + + + /// Fields: + public: + + controller& control; + chainbase::database& db; ///< database where state is stored + transaction_context& trx_context; ///< transaction context in which the action is running + const action& act; ///< message being applied + account_name receiver; ///< the code that is currently running + vector used_authorizations; ///< Parallel to act.authorization; tracks which permissions have been used while processing the message + uint32_t recurse_depth; ///< how deep inline actions can recurse + bool privileged = false; + bool context_free = false; + bool used_context_free_api = false; + + generic_index idx64; + generic_index idx128; + generic_index idx256; + generic_index idx_double; + generic_index idx_long_double; + + action_trace trace; + + private: + + iterator_cache keyval_cache; vector _notified; ///< keeps track of new accounts to be notifed of current message vector _inline_actions; ///< queued inline messages vector _cfa_inline_actions; ///< queued inline messages std::ostringstream _pending_console_output; - vector _read_locks; - vector _write_scopes; - bytes _cached_trx; - uint64_t _cpu_usage; + //bytes _cached_trx; }; using apply_handler = std::function; } } // namespace eosio::chain -FC_REFLECT(eosio::chain::apply_context::apply_results, (applied_actions)(deferred_transaction_requests)(deferred_transactions_count)) +//FC_REFLECT(eosio::chain::apply_context::apply_results, (applied_actions)(deferred_transaction_requests)(deferred_transactions_count)) diff --git a/libraries/chain/include/eosio/chain/asset.hpp b/libraries/chain/include/eosio/chain/asset.hpp index a244c55ea53..757c8d98c4c 100644 --- a/libraries/chain/include/eosio/chain/asset.hpp +++ b/libraries/chain/include/eosio/chain/asset.hpp @@ -3,7 +3,7 @@ * @copyright defined in eos/LICENSE.txt */ #pragma once -#include +#include #include #include @@ -25,15 +25,21 @@ with amount = 10 and symbol(4,"CUR") */ - struct asset { - explicit asset(share_type a = 0, symbol id = EOS_SYMBOL) - :amount(a), sym(id){} + static constexpr int64_t max_amount = (1LL << 62) - 1; + + explicit asset(share_type a = 0, symbol id = EOS_SYMBOL) :amount(a), sym(id) { + EOS_ASSERT( is_amount_within_range(), asset_type_exception, "magnitude of asset amount must be less than 2^62" ); + EOS_ASSERT( sym.valid(), asset_type_exception, "invalid symbol" ); + } share_type amount; symbol sym; + bool is_amount_within_range()const { return -max_amount <= amount && amount <= max_amount; } + bool is_valid()const { return is_amount_within_range() && sym.valid(); } + double to_real()const { return static_cast(amount) / precision(); } uint8_t decimals()const; diff --git a/libraries/chain/include/eosio/chain/authority.hpp b/libraries/chain/include/eosio/chain/authority.hpp index 4520a8736ab..d59382de2c9 100644 --- a/libraries/chain/include/eosio/chain/authority.hpp +++ b/libraries/chain/include/eosio/chain/authority.hpp @@ -15,11 +15,28 @@ namespace eosio { namespace chain { struct permission_level_weight { permission_level permission; weight_type weight; + + friend bool operator == ( const permission_level_weight& lhs, const permission_level_weight& rhs ) { + return tie( lhs.permission, lhs.weight ) == tie( rhs.permission, rhs.weight ); + } }; struct key_weight { public_key_type key; weight_type weight; + + friend bool operator == ( const key_weight& lhs, const key_weight& rhs ) { + return tie( lhs.key, lhs.weight ) == tie( rhs.key, rhs.weight ); + } +}; + +struct wait_weight { + uint32_t wait_sec; + weight_type weight; + + friend bool operator == ( const wait_weight& lhs, const wait_weight& rhs ) { + return tie( lhs.wait_sec, lhs.weight ) == tie( rhs.wait_sec, rhs.weight ); + } }; namespace config { @@ -32,35 +49,58 @@ namespace config { struct billable_size { static const uint64_t value = 8; ///< over value of weight for safety, dynamically sizing key }; + + template<> + struct billable_size { + static const uint64_t value = 16; ///< over value of weight and wait_sec for safety + }; } struct authority { - authority( public_key_type k ):threshold(1),keys({{k,1}}){} - authority( uint32_t t, vector k, vector p = {} ) - :threshold(t),keys(move(k)),accounts(move(p)){} - authority(){} + authority( public_key_type k, uint32_t delay_sec = 0 ) + :threshold(1),keys({{k,1}}) + { + if( delay_sec > 0 ) { + threshold = 2; + waits.push_back(wait_weight{delay_sec, 1}); + } + } + authority( uint32_t t, vector k, vector p = {}, vector w = {} ) + :threshold(t),keys(move(k)),accounts(move(p)),waits(move(w)){} + authority(){} - uint32_t threshold = 0; - vector keys; - vector accounts; + uint32_t threshold = 0; + vector keys; + vector accounts; + vector waits; + + friend bool operator == ( const authority& lhs, const authority& rhs ) { + return tie( lhs.threshold, lhs.keys, lhs.accounts, lhs.waits ) == tie( rhs.threshold, rhs.keys, rhs.accounts, rhs.waits ); + } + + friend bool operator != ( const authority& lhs, const authority& rhs ) { + return tie( lhs.threshold, lhs.keys, lhs.accounts, lhs.waits ) != tie( rhs.threshold, rhs.keys, rhs.accounts, rhs.waits ); + } }; struct shared_authority { shared_authority( chainbase::allocator alloc ) - :keys(alloc),accounts(alloc){} + :keys(alloc),accounts(alloc),waits(alloc){} shared_authority& operator=(const authority& a) { threshold = a.threshold; keys = decltype(keys)(a.keys.begin(), a.keys.end(), keys.get_allocator()); accounts = decltype(accounts)(a.accounts.begin(), a.accounts.end(), accounts.get_allocator()); + waits = decltype(waits)(a.waits.begin(), a.waits.end(), waits.get_allocator()); return *this; } uint32_t threshold = 0; shared_vector keys; shared_vector accounts; + shared_vector waits; operator authority()const { return to_authority(); } authority to_authority()const { @@ -68,25 +108,31 @@ struct shared_authority { auth.threshold = threshold; auth.keys.reserve(keys.size()); auth.accounts.reserve(accounts.size()); + auth.waits.reserve(waits.size()); for( const auto& k : keys ) { auth.keys.emplace_back( k ); } for( const auto& a : accounts ) { auth.accounts.emplace_back( a ); } + for( const auto& w : waits ) { auth.waits.emplace_back( w ); } return auth; } size_t get_billable_size() const { size_t accounts_size = accounts.size() * config::billable_size_v; + size_t waits_size = waits.size() * config::billable_size_v; size_t keys_size = 0; for (const auto& k: keys) { keys_size += config::billable_size_v; keys_size += fc::raw::pack_size(k.key); ///< serialized size of the key } - return accounts_size + keys_size; + return accounts_size + waits_size + keys_size; } }; -inline bool operator< (const permission_level& a, const permission_level& b) { - return std::tie(a.actor, a.permission) < std::tie(b.actor, b.permission); +namespace config { + template<> + struct billable_size { + static const uint64_t value = (3 * config::fixed_overhead_shared_vector_ram_bytes) + 4; + }; } /** @@ -100,25 +146,44 @@ inline bool validate( const Authority& auth ) { static_assert( std::is_same::value && std::is_same::value && std::is_same::value && - std::is_same::value, + std::is_same::value && + std::is_same::value, "unexpected type for threshold and/or weight in authority" ); - if( ( auth.keys.size() + auth.accounts.size() ) > (1 << 16) ) + if( ( auth.keys.size() + auth.accounts.size() + auth.waits.size() ) > (1 << 16) ) return false; // overflow protection (assumes weight_type is uint16_t and threshold is of type uint32_t) - const key_weight* prev = nullptr; - for( const auto& k : auth.keys ) { - if( prev && ( prev->key < k.key || prev->key == k.key ) ) return false; - total_weight += k.weight; - prev = &k; + if( auth.threshold == 0 ) + return false; + + { + const key_weight* prev = nullptr; + for( const auto& k : auth.keys ) { + if( prev && !(prev->key < k.key) ) return false; // TODO: require keys to be sorted in ascending order rather than descending (requires modifying many tests) + total_weight += k.weight; + prev = &k; + } + } + { + const permission_level_weight* prev = nullptr; + for( const auto& a : auth.accounts ) { + if( prev && ( prev->permission >= a.permission ) ) return false; // TODO: require permission_levels to be sorted in ascending order rather than descending (requires modifying many tests) + total_weight += a.weight; + prev = &a; + } } - const permission_level_weight* pa = nullptr; - for( const auto& a : auth.accounts ) { - if(pa && ( pa->permission < a.permission || pa->permission == a.permission ) ) return false; - total_weight += a.weight; - pa = &a; + { + const wait_weight* prev = nullptr; + if( auth.waits.size() > 0 && auth.waits.front().wait_sec == 0 ) + return false; + for( const auto& w : auth.waits ) { + if( prev && ( prev->wait_sec >= w.wait_sec ) ) return false; + total_weight += w.weight; + prev = &w; + } } - return auth.threshold > 0 && total_weight >= auth.threshold; + + return total_weight >= auth.threshold; } } } // namespace eosio::chain @@ -126,5 +191,6 @@ inline bool validate( const Authority& auth ) { FC_REFLECT(eosio::chain::permission_level_weight, (permission)(weight) ) FC_REFLECT(eosio::chain::key_weight, (key)(weight) ) -FC_REFLECT(eosio::chain::authority, (threshold)(keys)(accounts)) -FC_REFLECT(eosio::chain::shared_authority, (threshold)(keys)(accounts)) +FC_REFLECT(eosio::chain::wait_weight, (wait_sec)(weight) ) +FC_REFLECT(eosio::chain::authority, (threshold)(keys)(accounts)(waits) ) +FC_REFLECT(eosio::chain::shared_authority, (threshold)(keys)(accounts)(waits) ) diff --git a/libraries/chain/include/eosio/chain/authority_checker.hpp b/libraries/chain/include/eosio/chain/authority_checker.hpp index 1ed307a09d1..3076c8ad2d6 100644 --- a/libraries/chain/include/eosio/chain/authority_checker.hpp +++ b/libraries/chain/include/eosio/chain/authority_checker.hpp @@ -15,25 +15,32 @@ #include #include +#include + namespace eosio { namespace chain { namespace detail { - using meta_permission = static_variant; + // Order of the template types in the static_variant matters to meta_permission_comparator. + using meta_permission = static_variant; struct get_weight_visitor { using result_type = uint32_t; template - uint32_t operator()(const Permission& permission) { return permission.weight; } + uint32_t operator()( const Permission& permission ) { return permission.weight; } }; - // Orders permissions descending by weight, and breaks ties with Key permissions being less than Account permissions + // Orders permissions descending by weight, and breaks ties with Wait permissions being less than + // Key permissions which are in turn less than Account permissions struct meta_permission_comparator { - bool operator()(const meta_permission& a, const meta_permission& b) const { + bool operator()( const meta_permission& lhs, const meta_permission& rhs ) const { get_weight_visitor scale; - if (a.visit(scale) > b.visit(scale)) return true; - return a.contains() && b.contains(); + auto lhs_weight = lhs.visit(scale); + auto lhs_type = lhs.which(); + auto rhs_weight = rhs.visit(scale); + auto rhs_type = rhs.which(); + return std::tie( lhs_weight, lhs_type ) > std::tie( rhs_weight, rhs_type ); } }; @@ -51,92 +58,129 @@ namespace detail { * @tparam F A callable which takes a single argument of type @ref AccountPermission and returns the corresponding * authority */ - template + template class authority_checker { private: - PermissionToAuthorityFunc permission_to_authority; - PermissionVisitorFunc permission_visitor; - uint16_t recursion_depth_limit; - vector signing_keys; - flat_set _provided_auths; /// accounts which have authorized the transaction at owner level - flat_set _provided_levels; - vector _used_keys; + PermissionToAuthorityFunc permission_to_authority; + const std::function& checktime; + vector provided_keys; // Making this a flat_set causes runtime problems with utilities::filter_data_by_marker for some reason. TODO: Figure out why. + flat_set provided_permissions; + vector _used_keys; + fc::microseconds provided_delay; + uint16_t recursion_depth_limit; - struct weight_tally_visitor { - using result_type = uint32_t; + public: + authority_checker( PermissionToAuthorityFunc permission_to_authority, + uint16_t recursion_depth_limit, + const flat_set& provided_keys, + const flat_set& provided_permissions, + fc::microseconds provided_delay, + const std::function& checktime + ) + :permission_to_authority(permission_to_authority) + ,checktime( checktime ) + ,provided_keys(provided_keys.begin(), provided_keys.end()) + ,provided_permissions(provided_permissions) + ,_used_keys(provided_keys.size(), false) + ,provided_delay(provided_delay) + ,recursion_depth_limit(recursion_depth_limit) + { + FC_ASSERT( static_cast(checktime), "checktime cannot be empty" ); + } - authority_checker& checker; - uint16_t recursion_depth; - uint32_t total_weight = 0; + enum permission_cache_status { + being_evaluated, + permission_unsatisfied, + permission_satisfied + }; - weight_tally_visitor(authority_checker& checker, uint16_t recursion_depth) - : checker(checker), recursion_depth(recursion_depth) {} + typedef map permission_cache_type; - uint32_t operator()(const key_weight& permission) { - auto itr = boost::find(checker.signing_keys, permission.key); - if (itr != checker.signing_keys.end()) { - checker._used_keys[itr - checker.signing_keys.begin()] = true; - total_weight += permission.weight; - } - return total_weight; - } - uint32_t operator()(const permission_level_weight& permission) { - if (recursion_depth < checker.recursion_depth_limit) { - checker.permission_visitor.push_undo(); - checker.permission_visitor(permission.permission); - - if( checker.has_permission( permission.permission ) ) { - total_weight += permission.weight; - checker.permission_visitor.squash_undo(); - // Satisfied by owner may throw off visitor expectations... - // TODO: Figure out what a permission_visitor actually needs and should expect. - } else if( checker.satisfied(permission.permission, recursion_depth + 1) ) { - total_weight += permission.weight; - checker.permission_visitor.squash_undo(); - } else { - checker.permission_visitor.pop_undo(); - } - } - return total_weight; - } - }; + bool satisfied( const permission_level& permission, + fc::microseconds override_provided_delay, + permission_cache_type* cached_perms = nullptr + ) + { + auto delay_reverter = fc::make_scoped_exit( [this, delay = provided_delay] () mutable { + provided_delay = delay; + }); + + provided_delay = override_provided_delay; - bool has_permission( const permission_level& level )const { - return _provided_auths.find( level.actor ) != _provided_auths.end() - || _provided_levels.find( level ) != _provided_levels.end(); + return satisfied( permission, cached_perms ); } - public: - authority_checker( PermissionToAuthorityFunc permission_to_authority, - PermissionVisitorFunc permission_visitor, - uint16_t recursion_depth_limit, const flat_set& signing_keys, - const flat_set& provided_auths = flat_set(), - const flat_set& provided_levels = flat_set()) - : permission_to_authority(permission_to_authority), - permission_visitor(permission_visitor), - recursion_depth_limit(recursion_depth_limit), - signing_keys(signing_keys.begin(), signing_keys.end()), - _provided_auths(provided_auths.begin(), provided_auths.end()), - _provided_levels(provided_levels.begin(), provided_levels.end()), - _used_keys(signing_keys.size(), false) - {} - - bool satisfied(const permission_level& permission, uint16_t depth = 0) { - if( has_permission( permission ) ) - return true; - try { - return satisfied(permission_to_authority(permission), depth); - } catch( const permission_query_exception& e ) { - return false; - } + bool satisfied( const permission_level& permission, permission_cache_type* cached_perms = nullptr ) { + permission_cache_type cached_permissions; + + if( cached_perms == nullptr ) + cached_perms = initialize_permission_cache( cached_permissions ); + + weight_tally_visitor visitor(*this, *cached_perms, 0); + return ( visitor(permission_level_weight{permission, 1}) > 0 ); + } + + template + bool satisfied( const AuthorityType& authority, + fc::microseconds override_provided_delay, + permission_cache_type* cached_perms = nullptr + ) + { + auto delay_reverter = fc::make_scoped_exit( [this, delay = provided_delay] () mutable { + provided_delay = delay; + }); + + provided_delay = override_provided_delay; + + return satisfied( authority, cached_perms ); } template - bool satisfied(const AuthorityType& authority, uint16_t depth = 0) { - // This check is redundant, since weight_tally_visitor did it too, but I'll leave it here for future-proofing - if (depth > recursion_depth_limit) - return false; + bool satisfied( const AuthorityType& authority, permission_cache_type* cached_perms = nullptr ) { + permission_cache_type cached_permissions; + + if( cached_perms == nullptr ) + cached_perms = initialize_permission_cache( cached_permissions ); + + return satisfied( authority, *cached_perms, 0 ); + } + + bool all_keys_used() const { return boost::algorithm::all_of_equal(_used_keys, true); } + + flat_set used_keys() const { + auto range = utilities::filter_data_by_marker(provided_keys, _used_keys, true); + return {range.begin(), range.end()}; + } + flat_set unused_keys() const { + auto range = utilities::filter_data_by_marker(provided_keys, _used_keys, false); + return {range.begin(), range.end()}; + } + static optional + permission_status_in_cache( const permission_cache_type& permissions, + const permission_level& level ) + { + auto itr = permissions.find( level ); + if( itr != permissions.end() ) + return itr->second; + + itr = permissions.find( {level.actor, permission_name()} ); + if( itr != permissions.end() ) + return itr->second; + + return optional(); + } + + private: + permission_cache_type* initialize_permission_cache( permission_cache_type& cached_permissions ) { + for( const auto& p : provided_permissions ) { + cached_permissions.emplace_hint( cached_permissions.end(), p, permission_satisfied ); + } + return &cached_permissions; + } + + template + bool satisfied( const AuthorityType& authority, permission_cache_type& cached_permissions, uint16_t depth ) { // Save the current used keys; if we do not satisfy this authority, the newly used keys aren't actually used auto KeyReverter = fc::make_scoped_exit([this, keys = _used_keys] () mutable { _used_keys = keys; @@ -145,11 +189,12 @@ namespace detail { // Sort key permissions and account permissions together into a single set of meta_permissions detail::meta_permission_set permissions; + permissions.insert(authority.waits.begin(), authority.waits.end()); permissions.insert(authority.keys.begin(), authority.keys.end()); permissions.insert(authority.accounts.begin(), authority.accounts.end()); - // Check all permissions, from highest weight to lowest, seeing if signing_keys satisfies them or not - weight_tally_visitor visitor(*this, depth); + // Check all permissions, from highest weight to lowest, seeing if provided authorization factors satisfies them or not + weight_tally_visitor visitor(*this, cached_permissions, depth); for( const auto& permission : permissions ) // If we've got enough weight, to satisfy the authority, return! if( permission.visit(visitor) >= authority.threshold ) { @@ -159,39 +204,90 @@ namespace detail { return false; } - bool all_keys_used() const { return boost::algorithm::all_of_equal(_used_keys, true); } + struct weight_tally_visitor { + using result_type = uint32_t; - flat_set used_keys() const { - auto range = utilities::filter_data_by_marker(signing_keys, _used_keys, true); - return {range.begin(), range.end()}; - } - flat_set unused_keys() const { - auto range = utilities::filter_data_by_marker(signing_keys, _used_keys, false); - return {range.begin(), range.end()}; - } + authority_checker& checker; + permission_cache_type& cached_permissions; + uint16_t recursion_depth; + uint32_t total_weight = 0; - PermissionVisitorFunc& get_permission_visitor() { - return permission_visitor; - } + weight_tally_visitor(authority_checker& checker, permission_cache_type& cached_permissions, uint16_t recursion_depth) + :checker(checker) + ,cached_permissions(cached_permissions) + ,recursion_depth(recursion_depth) + {} + + uint32_t operator()(const wait_weight& permission) { + if( checker.provided_delay >= fc::seconds(permission.wait_sec) ) { + total_weight += permission.weight; + } + return total_weight; + } + + uint32_t operator()(const key_weight& permission) { + auto itr = boost::find( checker.provided_keys, permission.key ); + if( itr != checker.provided_keys.end() ) { + checker._used_keys[itr - checker.provided_keys.begin()] = true; + total_weight += permission.weight; + } + return total_weight; + } + + uint32_t operator()(const permission_level_weight& permission) { + auto status = authority_checker::permission_status_in_cache( cached_permissions, permission.permission ); + if( !status ) { + if( recursion_depth < checker.recursion_depth_limit ) { + bool r = false; + typename permission_cache_type::iterator itr = cached_permissions.end(); + + bool propagate_error = false; + try { + auto&& auth = checker.permission_to_authority( permission.permission ); + propagate_error = true; + auto res = cached_permissions.emplace( permission.permission, being_evaluated ); + itr = res.first; + r = checker.satisfied( std::forward(auth), cached_permissions, recursion_depth + 1 ); + } catch( const permission_query_exception& ) { + if( propagate_error ) + throw; + else + return total_weight; // if the permission doesn't exist, continue without it + } + + if( r ) { + total_weight += permission.weight; + itr->second = permission_satisfied; + } else { + itr->second = permission_unsatisfied; + } + } + } else if( *status == permission_satisfied ) { + total_weight += permission.weight; + } + return total_weight; + } + }; }; /// authority_checker - template - auto make_auth_checker(PermissionToAuthorityFunc&& pta, - PermissionVisitorFunc&& permission_visitor, - uint16_t recursion_depth_limit, - const flat_set& signing_keys, - const flat_set& accounts = flat_set(), - const flat_set& levels = flat_set() ) { - return authority_checker(std::forward(pta), std::forward(permission_visitor), recursion_depth_limit, signing_keys, accounts, levels); + template + auto make_auth_checker( PermissionToAuthorityFunc&& pta, + uint16_t recursion_depth_limit, + const flat_set& provided_keys, + const flat_set& provided_permissions = flat_set(), + fc::microseconds provided_delay = fc::microseconds(0), + const std::function& _checktime = std::function() + ) + { + auto noop_checktime = []( uint32_t ) {}; + const auto& checktime = ( static_cast(_checktime) ? _checktime : noop_checktime ); + return authority_checker< PermissionToAuthorityFunc>( std::forward(pta), + recursion_depth_limit, + provided_keys, + provided_permissions, + provided_delay, + checktime ); } - class noop_permission_visitor { - public: - void push_undo() {} - void pop_undo() {} - void squash_undo() {} - void operator()(const permission_level& perm_level) {} - }; - } } // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/authorization_manager.hpp b/libraries/chain/include/eosio/chain/authorization_manager.hpp new file mode 100644 index 00000000000..ef87af18d36 --- /dev/null +++ b/libraries/chain/include/eosio/chain/authorization_manager.hpp @@ -0,0 +1,133 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include + +#include +#include + +namespace eosio { namespace chain { + + class controller; + struct updateauth; + struct deleteauth; + struct linkauth; + struct unlinkauth; + struct canceldelay; + + class authorization_manager { + public: + using permission_id_type = permission_object::id_type; + + explicit authorization_manager(controller& c, chainbase::database& d); + + void add_indices(); + void initialize_database(); + + const permission_object& create_permission( account_name account, + permission_name name, + permission_id_type parent, + const authority& auth, + time_point initial_creation_time = time_point() + ); + + const permission_object& create_permission( account_name account, + permission_name name, + permission_id_type parent, + authority&& auth, + time_point initial_creation_time = time_point() + ); + + void modify_permission( const permission_object& permission, const authority& auth ); + + void remove_permission( const permission_object& permission ); + + void update_permission_usage( const permission_object& permission ); + + fc::time_point get_permission_last_used( const permission_object& permission )const; + + const permission_object* find_permission( const permission_level& level )const; + const permission_object& get_permission( const permission_level& level )const; + + /** + * @brief Find the lowest authority level required for @ref authorizer_account to authorize a message of the + * specified type + * @param authorizer_account The account authorizing the message + * @param code_account The account which publishes the contract that handles the message + * @param type The type of message + */ + optional lookup_minimum_permission( account_name authorizer_account, + scope_name code_account, + action_name type + )const; + + /** + * @brief Check authorizations of a vector of actions with provided keys, permission levels, and delay + * + * @param actions - the actions to check authorization across + * @param provided_keys - the set of public keys which have authorized the transaction + * @param provided_permissions - the set of permissions which have authorized the transaction (empty permission name acts as wildcard) + * @param provided_delay - the delay satisfied by the transaction + * @param checktime - the function that can be called to track CPU usage and time during the process of checking authorization + * @param allow_unused_keys - true if method should not assert on unused keys + */ + void + check_authorization( const vector& actions, + const flat_set& provided_keys, + const flat_set& provided_permissions = flat_set(), + fc::microseconds provided_delay = fc::microseconds(0), + const std::function& checktime = std::function(), + bool allow_unused_keys = false + )const; + + + /** + * @brief Check authorizations of a permission with provided keys, permission levels, and delay + * + * @param account - the account owner of the permission + * @param permission - the permission name to check for authorization + * @param provided_keys - a set of public keys + * @param provided_permissions - the set of permissions which can be considered satisfied (empty permission name acts as wildcard) + * @param provided_delay - the delay considered to be satisfied for the authorization check + * @param checktime - the function that can be called to track CPU usage and time during the process of checking authorization + * @param allow_unused_keys - true if method does not require all keys to be used + */ + void + check_authorization( account_name account, + permission_name permission, + const flat_set& provided_keys, + const flat_set& provided_permissions = flat_set(), + fc::microseconds provided_delay = fc::microseconds(0), + const std::function& checktime = std::function(), + bool allow_unused_keys = false + )const; + + flat_set get_required_keys( const transaction& trx, + const flat_set& candidate_keys, + fc::microseconds provided_delay = fc::microseconds(0) + )const; + + + static std::function _noop_checktime; + + private: + const controller& _control; + chainbase::database& _db; + + void check_updateauth_authorization( const updateauth& update, const vector& auths )const; + void check_deleteauth_authorization( const deleteauth& del, const vector& auths )const; + void check_linkauth_authorization( const linkauth& link, const vector& auths )const; + void check_unlinkauth_authorization( const unlinkauth& unlink, const vector& auths )const; + fc::microseconds check_canceldelay_authorization( const canceldelay& cancel, const vector& auths )const; + + optional lookup_linked_permission( account_name authorizer_account, + scope_name code_account, + action_name type + )const; + }; + +} } /// namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/base.hpp b/libraries/chain/include/eosio/chain/base.hpp deleted file mode 100644 index 586b940e156..00000000000 --- a/libraries/chain/include/eosio/chain/base.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -namespace eosio { namespace chain { - - /** - * @defgroup operations Operations - * @ingroup transactions Transactions - * @brief A set of valid comands for mutating the globally shared state. - * - * An operation can be thought of like a function that will modify the global - * shared state of the blockchain. The members of each struct are like function - * arguments and each operation can potentially generate a return value. - * - * Operations can be grouped into transactions (@ref transaction) to ensure that they occur - * in a particular order and that all operations apply successfully or - * no operations apply. - * - * Each operation is a fully defined state transition and can exist in a transaction on its own. - * - * @section operation_design_principles Design Principles - * - * Operations have been carefully designed to include all of the information necessary to - * interpret them outside the context of the blockchain. This means that information about - * current chain state is included in the operation even though it could be inferred from - * a subset of the data. This makes the expected outcome of each operation well defined and - * easily understood without access to chain state. - * - * @subsection balance_calculation Balance Calculation Principle - * - * We have stipulated that the current account balance may be entirely calculated from - * just the subset of operations that are relevant to that account. There should be - * no need to process the entire blockchain inorder to know your account's balance. - * - * @subsection fee_calculation Explicit Fee Principle - * - * Blockchain fees can change from time to time and it is important that a signed - * transaction explicitly agree to the fees it will be paying. This aids with account - * balance updates and ensures that the sender agreed to the fee prior to making the - * transaction. - * - * @subsection defined_authority Explicit Authority - * - * Each operation shall contain enough information to know which accounts must authorize - * the operation. This principle enables authority verification to occur in a centralized, - * optimized, and parallel manner. - * - * @subsection relevancy_principle Explicit Relevant Accounts - * - * Each operation contains enough information to enumerate all accounts for which the - * operation should apear in its account history. This principle enables us to easily - * define and enforce the @balance_calculation. This is superset of the @ref defined_authority - * - * @{ - */ - - struct base_operation - { - void validate()const{} - }; - - ///@} - -} } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/block.hpp b/libraries/chain/include/eosio/chain/block.hpp index 099a79daaf8..2cd4b261700 100644 --- a/libraries/chain/include/eosio/chain/block.hpp +++ b/libraries/chain/include/eosio/chain/block.hpp @@ -1,134 +1,77 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ #pragma once -#include +#include #include -#include namespace eosio { namespace chain { - struct block_header - { - digest_type digest() const; - uint32_t block_num() const { return num_from_id(previous) + 1; } - static uint32_t num_from_id(const block_id_type& id); - - block_id_type previous; - block_timestamp_type timestamp; - - checksum256_type transaction_mroot; /// mroot of cycles_summary - checksum256_type action_mroot; - checksum256_type block_mroot; - - account_name producer; - - /** The producer schedule version that should validate this block, this is used to - * indicate that the prior block which included new_producers->version has been marked - * irreversible and that it the new producer schedule takes effect this block. - */ - uint32_t schedule_version = 0; - optional new_producers; - }; - - struct signed_block_header : public block_header - { - block_id_type id() const; - public_key_type signee() const; - void sign(const private_key_type& signer); - bool validate_signee(const public_key_type& expected_signee) const; + /** + * When a transaction is referenced by a block it could imply one of several outcomes which + * describe the state-transition undertaken by the block producer. + */ - signature_type producer_signature; + struct transaction_receipt_header { + enum status_enum { + executed = 0, ///< succeed, no error handler executed + soft_fail = 1, ///< objectively failed (not executed), error handler executed + hard_fail = 2, ///< objectively failed and error handler objectively failed thus no state change + delayed = 3, ///< transaction delayed/deferred/scheduled for future execution + expired = 4 ///< transaction expired and storage space refuned to user + }; + + transaction_receipt_header():status(hard_fail){} + transaction_receipt_header( status_enum s ):status(s){} + + fc::enum_type status; + uint32_t cpu_usage_us; ///< total billed CPU usage (microseconds) + fc::unsigned_int net_usage_words; ///< total billed NET usage, so we can reconstruct resource state when skipping context free data... hard failures... }; - struct shard_lock { - account_name account; - scope_name scope; + struct transaction_receipt : public transaction_receipt_header { - friend bool operator < ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) < std::tie(b.account, b.scope); } - friend bool operator <= ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) <= std::tie(b.account, b.scope); } - friend bool operator > ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) > std::tie(b.account, b.scope); } - friend bool operator >= ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) >= std::tie(b.account, b.scope); } - friend bool operator == ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) == std::tie(b.account, b.scope); } - friend bool operator != ( const shard_lock& a, const shard_lock& b ) { return std::tie(a.account, a.scope) != std::tie(b.account, b.scope); } - }; + transaction_receipt():transaction_receipt_header(){} + transaction_receipt( transaction_id_type tid ):transaction_receipt_header(executed),trx(tid){} + transaction_receipt( packed_transaction ptrx ):transaction_receipt_header(executed),trx(ptrx){} - struct shard_summary { - vector read_locks; - vector write_locks; - vector transactions; /// new or generated transactions + fc::static_variant trx; - bool empty() const { - return read_locks.empty() && write_locks.empty() && transactions.empty(); + digest_type digest()const { + digest_type::encoder enc; + fc::raw::pack( enc, status ); + fc::raw::pack( enc, cpu_usage_us ); + fc::raw::pack( enc, net_usage_words ); + if( trx.contains() ) + fc::raw::pack( enc, trx.get() ); + else + fc::raw::pack( enc, trx.get().packed_digest() ); + return enc.result(); } }; - typedef vector cycle; - - struct region_summary { - uint16_t region = 0; - vector cycles_summary; - }; /** - * The block_summary defines the set of transactions that were successfully applied as they - * are organized into cycles and shards. A shard contains the set of transactions IDs which - * are either user generated transactions or code-generated transactions. - * - * - * The primary purpose of a block is to define the order in which messages are processed. - * - * The secodnary purpose of a block is certify that the messages are valid according to - * a group of 3rd party validators (producers). - * - * The next purpose of a block is to enable light-weight proofs that a transaction occured - * and was considered valid. - * - * The next purpose is to enable code to generate messages that are certified by the - * producers to be authorized. - * - * A block is therefore defined by the ordered set of executed and generated transactions, - * and the merkle proof is over set of messages delivered as a result of executing the - * transactions. - * - * A message is defined by { receiver, code, function, permission, data }, the merkle - * tree of a block should be generated over a set of message IDs rather than a set of - * transaction ids. */ - struct signed_block_summary : public signed_block_header { - vector regions; + struct signed_block : public signed_block_header { + using signed_block_header::signed_block_header; + signed_block() = default; + signed_block( const signed_block_header& h ):signed_block_header(h){} + + vector transactions; /// new or generated transactions + extensions_type block_extensions; }; + using signed_block_ptr = std::shared_ptr; - /** - * This structure contains the set of signed transactions referenced by the - * block summary. This inherits from block_summary/signed_block_header and is - * what would be logged to disk to enable the regeneration of blockchain state. - * - * The transactions are grouped to mirror the cycles in block_summary, generated - * transactions are not included. - */ - struct signed_block : public signed_block_summary { - signed_block () = default; - signed_block (const signed_block& ) = default; - signed_block (const signed_block_summary& base) - :signed_block_summary (base), - input_transactions() - {} - - /// this is loaded and indexed into map that is referenced by summary; order doesn't matter - vector input_transactions; + struct producer_confirmation { + block_id_type block_id; + digest_type block_digest; + account_name producer; + signature_type sig; }; -} } // eosio::chain +} } /// eosio::chain -FC_REFLECT(eosio::chain::block_header, (previous)(timestamp) - (transaction_mroot)(action_mroot)(block_mroot) - (producer)(schedule_version)(new_producers)) +FC_REFLECT_ENUM( eosio::chain::transaction_receipt::status_enum, + (executed)(soft_fail)(hard_fail)(delayed)(expired) ) -FC_REFLECT_DERIVED(eosio::chain::signed_block_header, (eosio::chain::block_header), (producer_signature)) -FC_REFLECT( eosio::chain::shard_lock, (account)(scope)) -FC_REFLECT( eosio::chain::shard_summary, (read_locks)(write_locks)(transactions)) -FC_REFLECT( eosio::chain::region_summary, (region)(cycles_summary) ) -FC_REFLECT_DERIVED(eosio::chain::signed_block_summary, (eosio::chain::signed_block_header), (regions)) -FC_REFLECT_DERIVED(eosio::chain::signed_block, (eosio::chain::signed_block_summary), (input_transactions)) +FC_REFLECT(eosio::chain::transaction_receipt_header, (status)(cpu_usage_us)(net_usage_words) ) +FC_REFLECT_DERIVED(eosio::chain::transaction_receipt, (eosio::chain::transaction_receipt_header), (trx) ) +FC_REFLECT_DERIVED(eosio::chain::signed_block, (eosio::chain::signed_block_header), (transactions)(block_extensions) ) diff --git a/libraries/chain/include/eosio/chain/block_context.hpp b/libraries/chain/include/eosio/chain/block_context.hpp new file mode 100644 index 00000000000..ea65f6769fd --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_context.hpp @@ -0,0 +1,27 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +namespace eosio { namespace chain { + + + class block_context { + public: + + enum block_status { + dpos_irreversible = 0, ///< this block has already been applied before by this node and is considered irreversible by DPOS standards + bft_irreversible = 1, ///< this block has already been applied before by this node and is considered irreversible by BFT standards (but not yet DPOS standards) + validated_block = 2, ///< this is a complete block signed by a valid producer and has been previously applied by this node and therefore validated but it is not yet irreversible + completed_block = 3, ///< this is a complete block signed by a valid producer but is not yet irreversible nor has it yet been applied by this node + producing_block = 4, ///< this is an incomplete block that is being produced by a valid producer for their time slot and will be signed by them after the block is completed + speculative_block = 5 ///< this is an incomplete block that is only being speculatively produced by the node (whether it is the node of an active producer or not) + }; + + block_status status = speculative_block; + bool is_active_producer = false; ///< whether the node applying the block is an active producer (this further modulates behavior when the block status is completed_block) + + }; + +} } diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp new file mode 100644 index 00000000000..dbc75ad5777 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include + +namespace eosio { namespace chain { + + struct block_header + { + block_timestamp_type timestamp; + account_name producer; + + /** + * By signing this block this producer is confirming blocks [block_num() - confirmed, blocknum()) + * as being the best blocks for that range and that he has not signed any other + * statements that would contradict. + * + * No producer should sign a block with overlapping ranges or it is proof of byzantine + * behavior. When producing a block a producer is always confirming at least the block he + * is building off of. A producer cannot confirm "this" block, only prior blocks. + */ + uint16_t confirmed = 1; + + block_id_type previous; + + checksum256_type transaction_mroot; /// mroot of cycles_summary + checksum256_type action_mroot; /// mroot of all delivered action receipts + + + /** The producer schedule version that should validate this block, this is used to + * indicate that the prior block which included new_producers->version has been marked + * irreversible and that it the new producer schedule takes effect this block. + */ + uint32_t schedule_version = 0; + optional new_producers; + extensions_type header_extensions; + + + digest_type digest()const; + block_id_type id() const; + uint32_t block_num() const { return num_from_id(previous) + 1; } + static uint32_t num_from_id(const block_id_type& id); + }; + + + struct signed_block_header : public block_header + { + signature_type producer_signature; + }; + + struct header_confirmation { + block_id_type block_id; + account_name producer; + signature_type producer_signature; + }; + +} } /// namespace eosio::chain + +FC_REFLECT(eosio::chain::block_header, + (timestamp)(producer)(confirmed)(previous) + (transaction_mroot)(action_mroot) + (producer)(schedule_version)(new_producers)(header_extensions)) + +FC_REFLECT_DERIVED(eosio::chain::signed_block_header, (eosio::chain::block_header), (producer_signature)) +FC_REFLECT(eosio::chain::header_confirmation, (block_id)(producer)(producer_signature) ) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp new file mode 100644 index 00000000000..1b6cf824330 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include + +namespace eosio { namespace chain { + +/** + * @struct block_header_state + * @brief defines the minimum state necessary to validate transaction headers + */ +struct block_header_state { + block_id_type id; + uint32_t block_num = 0; + signed_block_header header; + uint32_t dpos_irreversible_blocknum = 0; + uint32_t bft_irreversible_blocknum = 0; + uint32_t pending_schedule_lib_num = 0; /// last irr block num + digest_type pending_schedule_hash; + producer_schedule_type pending_schedule; + producer_schedule_type active_schedule; + incremental_merkle blockroot_merkle; + flat_map producer_to_last_produced; + public_key_type block_signing_key; + vector confirm_count; + vector confirmations; + + block_header_state next( const signed_block_header& h )const; + block_header_state generate_next( block_timestamp_type when )const; + + void set_new_producers( producer_schedule_type next_pending ); + void set_confirmed( uint16_t num_prev_blocks ); + void add_confirmation( const header_confirmation& c ); + + + bool has_pending_producers()const { return pending_schedule.producers.size(); } + //uint32_t calc_dpos_last_irreversible()const; + bool is_active_producer( account_name n )const; + + /* + block_timestamp_type get_slot_time( uint32_t slot_num )const; + uint32_t get_slot_at_time( block_timestamp_type t )const; + producer_key get_scheduled_producer( uint32_t slot_num )const; + uint32_t producer_participation_rate()const; + */ + + producer_key get_scheduled_producer( block_timestamp_type t )const; + const block_id_type& prev()const { return header.previous; } + digest_type sig_digest()const; + void sign( const std::function& signer ); + public_key_type signee()const; +}; + + + +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::block_header_state, + (id)(block_num)(header)(dpos_irreversible_blocknum)(bft_irreversible_blocknum) + (pending_schedule_lib_num)(pending_schedule_hash) + (pending_schedule)(active_schedule)(blockroot_merkle) + (producer_to_last_produced)(block_signing_key) + (confirm_count)(confirmations) ) diff --git a/libraries/chain/include/eosio/chain/block_log.hpp b/libraries/chain/include/eosio/chain/block_log.hpp index 50322104009..0ebca3e536b 100644 --- a/libraries/chain/include/eosio/chain/block_log.hpp +++ b/libraries/chain/include/eosio/chain/block_log.hpp @@ -41,11 +41,11 @@ namespace eosio { namespace chain { block_log(block_log&& other); ~block_log(); - uint64_t append(const signed_block& b); + uint64_t append(const signed_block_ptr& b); void flush(); - std::pair read_block(uint64_t file_pos)const; - optional read_block_by_num(uint32_t block_num)const; - optional read_block_by_id(const block_id_type& id)const { + std::pair read_block(uint64_t file_pos)const; + signed_block_ptr read_block_by_num(uint32_t block_num)const; + signed_block_ptr read_block_by_id(const block_id_type& id)const { return read_block_by_num(block_header::num_from_id(id)); } @@ -53,8 +53,8 @@ namespace eosio { namespace chain { * Return offset of block in file, or block_log::npos if it does not exist. */ uint64_t get_block_pos(uint32_t block_num) const; - optional read_head()const; - const optional& head()const; + signed_block_ptr read_head()const; + const signed_block_ptr& head()const; static const uint64_t npos = std::numeric_limits::max(); diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp new file mode 100644 index 00000000000..9b5bdd1f660 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -0,0 +1,34 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include +#include +#include + +namespace eosio { namespace chain { + + struct block_state : public block_header_state { + block_state( const block_header_state& cur ):block_header_state(cur){} + block_state( const block_header_state& prev, signed_block_ptr b ); + block_state( const block_header_state& prev, block_timestamp_type when ); + block_state() = default; + + /// weak_ptr prev_block_state.... + signed_block_ptr block; + bool validated = false; + bool in_current_chain = false; + + /// this data is redundant with the data stored in block, but facilitates + /// recapturing transactions when we pop a block + vector trxs; + }; + + using block_state_ptr = std::shared_ptr; + +} } /// namespace eosio::chain + +FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(validated) ) diff --git a/libraries/chain/include/eosio/chain/block_timestamp.hpp b/libraries/chain/include/eosio/chain/block_timestamp.hpp index 45d1ecf1f13..0b4028704bb 100644 --- a/libraries/chain/include/eosio/chain/block_timestamp.hpp +++ b/libraries/chain/include/eosio/chain/block_timestamp.hpp @@ -30,6 +30,17 @@ namespace eosio { namespace chain { static block_timestamp maximum() { return block_timestamp( 0xffff ); } static block_timestamp min() { return block_timestamp(0); } + block_timestamp next() const { + FC_ASSERT( std::numeric_limits::max() - slot >= 1, "block timestamp overflow" ); + auto result = block_timestamp(*this); + result.slot += 1; + return result; + } + + fc::time_point to_time_point() const { + return (fc::time_point)(*this); + } + operator fc::time_point() const { int64_t msec = slot * (int64_t)IntervalMs; msec += EpochMs; diff --git a/libraries/chain/include/eosio/chain/block_trace.hpp b/libraries/chain/include/eosio/chain/block_trace.hpp deleted file mode 100644 index 0e9703bcbd4..00000000000 --- a/libraries/chain/include/eosio/chain/block_trace.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include -#include - -namespace eosio { namespace chain { - - struct shard_trace { - digest_type shard_action_root; - digest_type shard_transaction_root; - vector transaction_traces; - uint64_t cpu_usage; - - flat_set read_locks; - flat_set write_locks; - - void append( transaction_trace&& res ) { - transaction_traces.emplace_back(move(res)); - } - - void append( const transaction_trace& res ) { - transaction_traces.emplace_back(res); - } - - void finalize_shard(); - }; - - struct cycle_trace { - vector shard_traces; - }; - - struct region_trace { - vector cycle_traces; - }; - - struct block_trace { - explicit block_trace(const signed_block& s) - :block(s) - {} - - const signed_block& block; - vector region_traces; - vector implicit_transactions; - digest_type calculate_action_merkle_root()const; - digest_type calculate_transaction_merkle_root()const; - uint64_t calculate_cpu_usage() const; - }; - - -} } // eosio::chain - -FC_REFLECT( eosio::chain::shard_trace, (shard_action_root)(shard_transaction_root)(transaction_traces)(cpu_usage)(read_locks)(write_locks)) -FC_REFLECT( eosio::chain::cycle_trace, (shard_traces)) -FC_REFLECT( eosio::chain::region_trace, (cycle_traces)) -FC_REFLECT( eosio::chain::block_trace, (region_traces)) diff --git a/libraries/chain/include/eosio/chain/chain_config.hpp b/libraries/chain/include/eosio/chain/chain_config.hpp index f0769136a3d..8d234c68253 100644 --- a/libraries/chain/include/eosio/chain/chain_config.hpp +++ b/libraries/chain/include/eosio/chain/chain_config.hpp @@ -17,56 +17,59 @@ namespace eosio { namespace chain { * values specified by the producers. */ struct chain_config { + uint64_t max_block_net_usage; ///< the maxiumum net usage in instructions for a block + uint32_t target_block_net_usage_pct; ///< the target percent (1% == 100, 100%= 10,000) of maximum net usage; exceeding this triggers congestion handling + uint32_t max_transaction_net_usage; ///< the maximum objectively measured net usage that the chain will allow regardless of account limits + uint32_t base_per_transaction_net_usage; ///< the base amount of net usage billed for a transaction to cover incidentals + uint32_t net_usage_leeway; + uint32_t context_free_discount_net_usage_num; ///< the numerator for the discount on net usage of context-free data + uint32_t context_free_discount_net_usage_den; ///< the denominator for the discount on net usage of context-free data - uint32_t base_per_transaction_net_usage; ///< the base amount of net usage billed for a transaction to cover incidentals - uint32_t base_per_transaction_cpu_usage; ///< the base amount of cpu usage billed for a transaction to cover incidentals - uint32_t base_per_action_cpu_usage; ///< the base amount of cpu usage billed for an action to cover incidentals - uint32_t base_setcode_cpu_usage; ///< the base amount of cpu usage billed for a setcode action to cover compilation/etc - uint32_t per_signature_cpu_usage; ///< the cpu usage billed for every signature on a transaction - uint32_t per_lock_net_usage; ///< the net usage billed for every lock on a transaction to cover overhead in the block shards + uint32_t max_block_cpu_usage; ///< the maxiumum cpu usage in instructions for a block + uint32_t target_block_cpu_usage_pct; ///< the target percent (1% == 100, 100%= 10,000) of maximum cpu usage; exceeding this triggers congestion handling + uint32_t max_transaction_cpu_usage; ///< the maximum objectively measured cpu usage that the chain will allow regardless of account limits + uint32_t base_per_transaction_cpu_usage; ///< the base amount of cpu usage billed for a transaction to cover incidentals + uint32_t base_per_action_cpu_usage; ///< the base amount of cpu usage billed for an action to cover incidentals + uint32_t base_setcode_cpu_usage; ///< the base amount of cpu usage billed for a setcode action to cover compilation/etc + uint32_t per_signature_cpu_usage; ///< the cpu usage billed for every signature on a transaction + uint32_t cpu_usage_leeway; + uint32_t context_free_discount_cpu_usage_num; ///< the numerator for the discount on cpu usage of context-free actions + uint32_t context_free_discount_cpu_usage_den; ///< the denominator for the discount on cpu usage of context-free actions - uint64_t context_free_discount_cpu_usage_num; ///< the numerator for the discount on cpu usage for CFA's - uint64_t context_free_discount_cpu_usage_den; ///< the denominator for the discount on cpu usage for CFA's - - uint32_t max_transaction_cpu_usage; ///< the maximum objectively measured cpu usage that the chain will allow regardless of account limits - uint32_t max_transaction_net_usage; ///< the maximum objectively measured net usage that the chain will allow regardless of account limits - - uint64_t max_block_cpu_usage; ///< the maxiumum cpu usage in instructions for a block - uint32_t target_block_cpu_usage_pct; ///< the target percent (1% == 100, 100%= 10,000) of maximum cpu usage; exceeding this triggers congestion handling - uint64_t max_block_net_usage; ///< the maxiumum net usage in instructions for a block - uint32_t target_block_net_usage_pct; ///< the target percent (1% == 100, 100%= 10,000) of maximum net usage; exceeding this triggers congestion handling - - uint32_t max_transaction_lifetime; - uint32_t max_transaction_exec_time; - uint16_t max_authority_depth; - uint16_t max_inline_depth; - uint32_t max_inline_action_size; - uint32_t max_generated_transaction_count; - uint32_t max_transaction_delay; - - static chain_config get_median_values( vector votes ); + uint32_t max_transaction_lifetime; ///< the maximum number of seconds that an input transaction's expiration can be ahead of the time of the block in which it is first included + uint32_t deferred_trx_expiration_window; ///< the number of seconds after the time a deferred transaction can first execute until it expires + uint32_t max_transaction_delay; ///< the maximum number of seconds that can be imposed as a delay requirement by authorization checks + uint32_t max_inline_action_size; ///< maximum allowed size (in bytes) of an inline action + uint16_t max_inline_action_depth; ///< recursion depth limit on sending inline actions + uint16_t max_authority_depth; ///< recursion depth limit for checking if an authority is satisfied + uint32_t max_generated_transaction_count; ///< the number of generated transactions per action (TODO: implement?) template friend Stream& operator << ( Stream& out, const chain_config& c ) { - return out << "Base Per-Transaction Net Usage: " << c.base_per_transaction_net_usage << ", " + return out << "Max Block Net Usage: " << c.max_block_net_usage << ", " + << "Target Block Net Usage Percent: " << ((double)c.target_block_net_usage_pct / (double)config::percent_1) << "%, " + << "Max Transaction Net Usage: " << c.max_transaction_net_usage << ", " + << "Base Per-Transaction Net Usage: " << c.base_per_transaction_net_usage << ", " + << "Net Usage Leeway: " << c.net_usage_leeway << ", " + << "Context-Free Data Net Usage Discount: " << (double)c.context_free_discount_net_usage_num * 100.0 / (double)c.context_free_discount_net_usage_den << "% , " + + << "Max Block CPU Usage: " << c.max_block_cpu_usage << ", " + << "Target Block CPU Usage Percent: " << ((double)c.target_block_cpu_usage_pct / (double)config::percent_1) << "%, " + << "Max Transaction CPU Usage: " << c.max_transaction_cpu_usage << ", " << "Base Per-Transaction CPU Usage: " << c.base_per_transaction_cpu_usage << ", " << "Base Per-Action CPU Usage: " << c.base_per_action_cpu_usage << ", " << "Base Setcode CPU Usage: " << c.base_setcode_cpu_usage << ", " << "Per-Signature CPU Usage: " << c.per_signature_cpu_usage << ", " - << "Per-Lock NET Usage: " << c.per_lock_net_usage << ", " + << "CPU Usage Leeway: " << c.cpu_usage_leeway << ", " << "Context-Free Action CPU Usage Discount: " << (double)c.context_free_discount_cpu_usage_num * 100.0 / (double)c.context_free_discount_cpu_usage_den << "% , " - << "Max Transaction CPU Usage: " << c.max_transaction_cpu_usage << ", " - << "Max Transaction Net Usage: " << c.max_transaction_net_usage << ", " - << "Max Block CPU Usage: " << c.max_block_cpu_usage << ", " - << "Target Block CPU Usage Percent: " << c.target_block_cpu_usage_pct << ", " - << "Max Block NET Usage: " << c.max_block_net_usage << ", " - << "Target Block NET Usage Percent: " << ((double)c.target_block_net_usage_pct / (double)config::percent_1) << "%, " - << "Max Transaction Lifetime: " << ((double)c.max_transaction_lifetime / (double)config::percent_1) << "%, " - << "Max Authority Depth: " << c.max_authority_depth << ", " - << "Max Inline Depth: " << c.max_inline_depth << ", " + + << "Max Transaction Lifetime: " << c.max_transaction_lifetime << ", " + << "Deferred Transaction Expiration Window: " << c.deferred_trx_expiration_window << ", " + << "Max Transaction Delay: " << c.max_transaction_delay << ", " << "Max Inline Action Size: " << c.max_inline_action_size << ", " - << "Max Generated Transaction Count: " << c.max_generated_transaction_count << "\n" - << "Max Transaction Delay: " << c.max_transaction_delay << "\n"; + << "Max Inline Action Depth: " << c.max_inline_action_depth << ", " + << "Max Authority Depth: " << c.max_authority_depth << ", " + << "Max Generated Transaction Count: " << c.max_generated_transaction_count << "\n"; } }; @@ -76,16 +79,17 @@ inline bool operator!=(const chain_config& a, const chain_config& b) { return !( } } // namespace eosio::chain FC_REFLECT(eosio::chain::chain_config, - (base_per_transaction_net_usage) - (base_per_transaction_cpu_usage) - (base_per_action_cpu_usage) - (base_setcode_cpu_usage) - (per_signature_cpu_usage)(per_lock_net_usage) - (context_free_discount_cpu_usage_num)(context_free_discount_cpu_usage_den) - (max_transaction_cpu_usage)(max_transaction_net_usage) - (max_block_cpu_usage)(target_block_cpu_usage_pct) (max_block_net_usage)(target_block_net_usage_pct) - (max_transaction_lifetime)(max_transaction_exec_time)(max_authority_depth) - (max_inline_depth)(max_inline_action_size)(max_generated_transaction_count) - (max_transaction_delay) + (max_transaction_net_usage)(base_per_transaction_net_usage)(net_usage_leeway) + (context_free_discount_net_usage_num)(context_free_discount_net_usage_den) + + (max_block_cpu_usage)(target_block_cpu_usage_pct) + (max_transaction_cpu_usage)(base_per_transaction_cpu_usage) + (base_per_action_cpu_usage)(base_setcode_cpu_usage)(per_signature_cpu_usage)(cpu_usage_leeway) + (context_free_discount_cpu_usage_num)(context_free_discount_cpu_usage_den) + + (max_transaction_lifetime)(deferred_trx_expiration_window)(max_transaction_delay) + (max_inline_action_size)(max_inline_action_depth) + (max_authority_depth)(max_generated_transaction_count) + ) diff --git a/libraries/chain/include/eosio/chain/chain_controller.hpp b/libraries/chain/include/eosio/chain/chain_controller.hpp deleted file mode 100644 index 17af0a93d03..00000000000 --- a/libraries/chain/include/eosio/chain/chain_controller.hpp +++ /dev/null @@ -1,479 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace eosio { namespace chain { - using database = chainbase::database; - using boost::signals2::signal; - using resource_limits_manager = resource_limits::resource_limits_manager; - class generated_transaction_object; - - namespace contracts{ class chain_initializer; } - - enum validation_steps - { - skip_nothing = 0, - skip_producer_signature = 1 << 0, ///< used while reindexing - skip_transaction_signatures = 1 << 1, ///< used by non-producer nodes - skip_transaction_dupe_check = 1 << 2, ///< used while reindexing - skip_fork_db = 1 << 3, ///< used while reindexing - skip_block_size_check = 1 << 4, ///< used when applying locally generated transactions - skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check as well - skip_authority_check = 1 << 6, ///< used while reindexing -- disables any checking of authority on transactions - skip_merkle_check = 1 << 7, ///< used while reindexing - skip_assert_evaluation = 1 << 8, ///< used while reindexing - skip_undo_history_check = 1 << 9, ///< used while reindexing - skip_producer_schedule_check= 1 << 10, ///< used while reindexing - skip_validate = 1 << 11, ///< used prior to checkpoint, skips transaction validation - skip_scope_check = 1 << 12, ///< used to skip checks for proper scope - skip_output_check = 1 << 13, ///< used to skip checks for outputs in block exactly matching those created from apply - pushed_transaction = 1 << 14, ///< used to indicate that the origination of the call was from a push_transaction, to determine time allotment - created_block = 1 << 15, ///< used to indicate that the origination of the call was for creating a block, to determine time allotment - received_block = 1 << 16, ///< used to indicate that the origination of the call was for a received block, to determine time allotment - genesis_setup = 1 << 17, ///< used to indicate that the origination of the call was for a genesis transaction - skip_missed_block_penalty = 1 << 18, ///< used to indicate that missed blocks shouldn't count against producers (used in long unit tests) - }; - - - /** - * @class database - * @brief tracks the blockchain state in an extensible manner - */ - class chain_controller : public boost::noncopyable { - public: - struct runtime_limits { - fc::microseconds max_push_block_us = fc::microseconds(-1); - fc::microseconds max_push_transaction_us = fc::microseconds(-1); - fc::microseconds max_deferred_transactions_us = fc::microseconds(-1); - }; - - struct controller_config { - path block_log_dir = config::default_block_log_dir; - path shared_memory_dir = config::default_shared_memory_dir; - uint64_t shared_memory_size = config::default_shared_memory_size; - bool read_only = false; - std::vector::slot_type> applied_block_callbacks; - std::vector::slot_type> applied_irreversible_block_callbacks; - std::vector::slot_type> on_pending_transaction_callbacks; - contracts::genesis_state_type genesis; - runtime_limits limits; - wasm_interface::vm_type wasm_runtime = config::default_wasm_runtime; - }; - - explicit chain_controller( const controller_config& cfg ); - ~chain_controller(); - - - void push_block( const signed_block& b, uint32_t skip = skip_nothing ); - transaction_trace push_transaction( const packed_transaction& trx, uint32_t skip = skip_nothing ); - vector push_deferred_transactions( bool flush = false, uint32_t skip = skip_nothing ); - - uint128_t transaction_id_to_sender_id( const transaction_id_type& tid )const; - - /** - * This signal is emitted after all operations and virtual operation for a - * block have been applied but before the get_applied_operations() are cleared. - * - * You may not yield from this callback because the blockchain is holding - * the write lock and may be in an "inconstant state" until after it is - * released. - */ - signal applied_block; - - /** - * This signal is emitted after irreversible block is written to disk. - * - * You may not yield from this callback because the blockchain is holding - * the write lock and may be in an "inconstant state" until after it is - * released. - */ - signal applied_irreversible_block; - - /** - * This signal is emitted any time a new transaction is added to the pending - * block state. - */ - signal on_pending_transaction; - - - - bool is_start_of_round( block_num_type n )const; - uint32_t blocks_per_round()const; - - - chain_id_type get_chain_id()const { return chain_id_type(); } /// TODO: make this hash of constitution - - - /** - * @return true if the block is in our fork DB or saved to disk as - * part of the official chain, otherwise return false - */ - bool is_known_block( const block_id_type& id )const; - bool is_known_transaction( const transaction_id_type& id )const; - block_id_type get_block_id_for_num( uint32_t block_num )const; - optional fetch_block_by_id( const block_id_type& id )const; - optional fetch_block_by_number( uint32_t num )const; - std::vector get_block_ids_on_fork(block_id_type head_of_fork)const; - - /** - * Usees the ABI for code::type to convert a JSON object (variant) into hex - vector action_to_binary( name code, name type, const fc::variant& obj )const; - fc::variant action_from_binary( name code, name type, const vector& bin )const; - */ - - - /** - * Calculate the percent of block production slots that were missed in the - * past 128 blocks, not including the current block. - */ - uint32_t producer_participation_rate()const; - - void add_checkpoints(const flat_map& checkpts); - const flat_map get_checkpoints()const { return _checkpoints; } - bool before_last_checkpoint()const; - - - - - /** - * Determine which public keys are needed to sign the given transaction. - * @param trx transaction that requires signature - * @param candidate_keys Set of public keys to examine for applicability - * @return Subset of candidate_keys whose private keys should be used to sign transaction - * @throws fc::exception if candidate_keys does not contain all required keys - */ - flat_set get_required_keys(const transaction& trx, const flat_set& candidate_keys)const; - - - bool _push_block( const signed_block& b ); - - signed_block generate_block( - block_timestamp_type when, - account_name producer, - const private_key_type& block_signing_private_key, - uint32_t skip = skip_nothing - ); - signed_block _generate_block( - block_timestamp_type when, - account_name producer, - const private_key_type& block_signing_private_key - ); - - - template - auto with_skip_flags( uint64_t flags, Function&& f ) - { - auto on_exit = fc::make_scoped_exit( [old_flags=_skip_flags,this](){ _skip_flags = old_flags; } ); - _skip_flags = flags; - return f(); - } - - - /** - * This method will backup all tranasctions in the current pending block, - * undo the pending block, call f(), and then push the pending transactions - * on top of the new state. - */ - template - auto without_pending_transactions( Function&& f ) - { - vector old_input; - - if( _pending_block ) - old_input = move(_pending_transaction_metas); - - clear_pending(); - - /** after applying f() push previously input transactions on top */ - auto on_exit = fc::make_scoped_exit( [&](){ - for( auto& t : old_input ) { - try { - if (!is_known_transaction(t.id)) - _push_transaction( std::move(t) ); - } catch ( ... ){} - } - }); - return f(); - } - - - - void pop_block(); - void clear_pending(); - - /** - * @brief Get the producer scheduled for block production in a slot. - * - * slot_num always corresponds to a time in the future. - * - * If slot_num == 1, returns the next scheduled producer. - * If slot_num == 2, returns the next scheduled producer after - * 1 block gap. - * - * Use the get_slot_time() and get_slot_at_time() functions - * to convert between slot_num and timestamp. - * - * Passing slot_num == 0 returns EOS_NULL_PRODUCER - */ - account_name get_scheduled_producer(uint32_t slot_num)const; - - /** - * Get the time at which the given slot occurs. - * - * If slot_num == 0, return time_point_sec(). - * - * If slot_num == N for N > 0, return the Nth next - * block-interval-aligned time greater than head_block_time(). - */ - block_timestamp_type get_slot_time(uint32_t slot_num)const; - - /** - * Get the last slot which occurs AT or BEFORE the given time. - * - * The return value is the greatest value N such that - * get_slot_time( N ) <= when. - * - * If no such N exists, return 0. - */ - uint32_t get_slot_at_time( block_timestamp_type when )const; - - const global_property_object& get_global_properties()const; - const dynamic_global_property_object& get_dynamic_global_properties()const; - const producer_object& get_producer(const account_name& ownername)const; - const permission_object* find_permission( const permission_level& level )const; - const permission_object& get_permission( const permission_level& level )const; - - time_point head_block_time()const; - uint32_t head_block_num()const; - block_id_type head_block_id()const; - account_name head_block_producer()const; - block_header head_block_header()const; - - uint32_t last_irreversible_block_num() const; - - const chainbase::database& get_database() const { return _db; } - chainbase::database& get_mutable_database() { return _db; } - - const resource_limits::resource_limits_manager& get_resource_limits_manager() const { return _resource_limits; } - resource_limits::resource_limits_manager& get_mutable_resource_limits_manager() { return _resource_limits; } - - wasm_interface& get_wasm_interface() { - return _wasm_interface; - } - - /** - * @param actions - the actions to check authorization across - * @param provided_keys - the set of public keys which have authorized the transaction - * @param allow_unused_signatures - true if method should not assert on unused signatures - * @param provided_accounts - the set of accounts which have authorized the transaction (presumed to be owner) - * - * @return fc::microseconds set to the max delay that this authorization requires to complete - */ - fc::microseconds check_authorization( const vector& actions, - const flat_set& provided_keys, - bool allow_unused_signatures = false, - flat_set provided_accounts = flat_set(), - flat_set provided_levels = flat_set() - )const; - - optional check_updateauth_authorization( const contracts::updateauth& update, const vector& auths )const; - fc::microseconds check_deleteauth_authorization( const contracts::deleteauth& del, const vector& auths )const; - fc::microseconds check_linkauth_authorization( const contracts::linkauth& link, const vector& auths )const; - fc::microseconds check_unlinkauth_authorization( const contracts::unlinkauth& unlink, const vector& auths )const; - void check_canceldelay_authorization( const contracts::canceldelay& cancel, const vector& auths )const; - - /** - * @param account - the account owner of the permission - * @param permission - the permission name to check for authorization - * @param provided_keys - a set of public keys - * - * @return true if the provided keys are sufficient to authorize the account permission - */ - bool check_authorization( account_name account, permission_name permission, - flat_set provided_keys, - bool allow_unused_signatures)const; - - private: - const apply_handler* find_apply_handler( account_name contract, scope_name scope, action_name act )const; - - friend class contracts::chain_initializer; - friend class apply_context; - - bool should_check_scope()const { return !(_skip_flags&skip_scope_check); } - - /** - * The controller can override any script endpoint with native code. - */ - ///@{ - void _set_apply_handler( account_name contract, account_name scope, action_name action, apply_handler v ); - //@} - - - transaction_trace _push_transaction( const packed_transaction& trx ); - transaction_trace _push_transaction( transaction_metadata&& data ); - transaction_trace _apply_transaction( transaction_metadata& data ); - transaction_trace __apply_transaction( transaction_metadata& data ); - transaction_trace _apply_error( transaction_metadata& data ); - vector _push_deferred_transactions( bool flush = false ); - - void _destroy_generated_transaction( const generated_transaction_object& gto ); - void _create_generated_transaction( const deferred_transaction& dto ); - - template - transaction_trace wrap_transaction_processing( transaction_metadata&& data, TransactionProcessing trx_processing ); - - transaction_trace delayed_transaction_processing( const transaction_metadata& mtrx ); - - /// Reset the object graph in-memory - void _initialize_indexes(); - void _initialize_chain(contracts::chain_initializer& starter); - void _update_producers_authority(); - - producer_schedule_type _calculate_producer_schedule()const; - const shared_producer_schedule_type& _head_producer_schedule()const; - - - void replay(); - - void _apply_block(const signed_block& next_block, uint32_t skip = skip_nothing); - void __apply_block(const signed_block& next_block); - - template - auto with_applying_block(Function&& f) -> decltype((*((Function*)nullptr))()) { - auto on_exit = fc::make_scoped_exit([this](){ - _currently_applying_block = false; - }); - _currently_applying_block = true; - return f(); - } - - fc::microseconds check_transaction_authorization(const transaction& trx, - const vector& signatures, - const vector& cfd = vector(), - bool allow_unused_signatures = false)const; - - - void require_scope(const scope_name& name) const; - void require_account(const account_name& name) const; - - /// Validate transaction helpers @{ - void validate_uniqueness( const transaction& trx )const; - void validate_tapos( const transaction& trx )const; - void validate_referenced_accounts( const transaction& trx )const; - void validate_not_expired( const transaction& trx )const; - void validate_expiration_not_too_far( const transaction& trx, fc::time_point reference_time )const; - void validate_transaction_without_state( const transaction& trx )const; - void validate_transaction_with_minimal_state( const transaction& trx, uint32_t min_net_usage = 0 )const; - void validate_transaction_with_minimal_state( const packed_transaction& packed_trx, const transaction* trx_ptr = nullptr )const; - /// @} - - void record_transaction( const transaction& trx ); - void update_resource_usage( transaction_trace& trace, const transaction_metadata& meta ); - - - /** - * @brief Find the lowest authority level required for @ref authorizer_account to authorize a message of the - * specified type - * @param authorizer_account The account authorizing the message - * @param code_account The account which publishes the contract that handles the message - * @param type The type of message - */ - optional lookup_minimum_permission( account_name authorizer_account, - scope_name code_account, - action_name type) const; - - /** - * @brief Find the linked permission for the passed in parameters - * @param authorizer_account The account authorizing the message - * @param code_account The account which publishes the contract that handles the message - * @param type The type of message - * @return active permission or the linked permission if one exists - */ - optional lookup_linked_permission( account_name authorizer_account, - scope_name code_account, - action_name type ) const; - - bool should_check_for_duplicate_transactions()const { return !(_skip_flags&skip_transaction_dupe_check); } - bool should_check_tapos()const { return !(_skip_flags&skip_tapos_check); } - bool should_check_signatures()const { return !(_skip_flags&skip_transaction_signatures); } - bool should_check_authorization()const { return !(_skip_flags&skip_authority_check); } - - ///Steps involved in applying a new block - ///@{ - const producer_object& validate_block_header(uint32_t skip, const signed_block& next_block)const; - void create_block_summary(const signed_block& next_block); - - void update_global_properties(const signed_block& b); - void update_global_dynamic_data(const signed_block& b); - void update_permission_usage( const transaction_metadata& meta ); - void update_signing_producer(const producer_object& signing_producer, const signed_block& new_block); - void update_last_irreversible_block(); - void update_or_create_producers( const producer_schedule_type& producers); - void clear_expired_transactions(); - /// @} - - void _spinup_db(); - void _spinup_fork_db(); - - void _start_pending_block( bool skip_deferred = false ); - void _start_pending_cycle(); - void _finalize_pending_cycle(); - void _apply_cycle_trace( const cycle_trace& trace ); - void _finalize_block( const block_trace& b, const producer_object& signing_producer ); - - transaction _get_on_block_transaction(); - void _apply_on_block_transaction(); - - // producer_schedule_type calculate_next_round( const signed_block& next_block ); - - database _db; - fork_database _fork_db; - block_log _block_log; - - optional _pending_block_session; - optional _pending_block; - optional _pending_block_trace; - vector _pending_transaction_metas; - optional _pending_cycle_trace; - - bool _currently_applying_block = false; - bool _currently_replaying_blocks = false; - uint64_t _skip_flags = 0; - - flat_map _checkpoints; - - typedef pair handler_key; - map< account_name, map > _apply_handlers; - - wasm_interface _wasm_interface; - - runtime_limits _limits; - resource_limits_manager _resource_limits; - }; - -} } diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index d2cb8e2dbe1..6d7971ef727 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -3,7 +3,6 @@ * @copyright defined in eos/LICENSE.txt */ #pragma once -#include #include #include @@ -15,18 +14,23 @@ typedef __uint128_t uint128_t; const static auto default_block_log_dir = "block_log"; const static auto default_shared_memory_dir = "shared_mem"; -const static auto default_shared_memory_size = 32*1024*1024*1024ll; +const static auto default_shared_memory_size = 1*1024*1024*1024ll; const static uint64_t system_account_name = N(eosio); -const static uint64_t nobody_account_name = N(nobody); -const static uint64_t anybody_account_name = N(anybody); -const static uint64_t producers_account_name = N(producers); +const static uint64_t null_account_name = N(eosio.null); +const static uint64_t producers_account_name = N(eosio.prods); + +// Active permission of producers account requires greater than 2/3 of the producers to authorize +const static uint64_t majority_producers_permission_name = N(prod.major); // greater than 1/2 of producers needed to authorize +const static uint64_t minority_producers_permission_name = N(prod.minor); // greater than 1/3 of producers needed to authorize0 + const static uint64_t eosio_auth_scope = N(eosio.auth); const static uint64_t eosio_all_scope = N(eosio.all); const static uint64_t active_name = N(active); const static uint64_t owner_name = N(owner); const static uint64_t eosio_any_name = N(eosio.any); +const static uint64_t eosio_code_name = N(eosio.code); const static int block_interval_ms = 500; const static int block_interval_us = block_interval_ms*1000; @@ -43,39 +47,52 @@ static const uint32_t account_net_usage_average_window_ms = 24*60*60*1000l; static const uint32_t block_cpu_usage_average_window_ms = 60*1000l; static const uint32_t block_size_average_window_ms = 60*1000l; +//const static uint64_t default_max_storage_size = 10 * 1024; +//const static uint32_t default_max_trx_runtime = 10*1000; +//const static uint32_t default_max_gen_trx_size = 64 * 1024; -const static uint32_t default_max_block_net_usage = 1024 * 1024; /// at 500ms blocks and 200byte trx, this enables ~10,000 TPS burst -const static int default_target_block_net_usage_pct = 10 * percent_1; /// we target 1000 TPS - -const static uint32_t default_max_block_cpu_usage = 100 * 1024 * 1024; /// at 500ms blocks and 20000instr trx, this enables ~10,000 TPS burst -const static uint32_t default_target_block_cpu_usage_pct = 10 * percent_1; /// target 1000 TPS - -const static uint64_t default_max_storage_size = 10 * 1024; -const static uint32_t default_max_trx_lifetime = 60*60; -const static uint16_t default_max_auth_depth = 6; -const static uint32_t default_max_trx_runtime = 10*1000; -const static uint16_t default_max_inline_depth = 4; -const static uint32_t default_max_inline_action_size = 4 * 1024; -const static uint32_t default_max_gen_trx_size = 64 * 1024; /// -const static uint32_t default_max_gen_trx_count = 16; ///< the number of generated transactions per action -const static uint32_t default_max_trx_delay = 45*24*3600; // 45 days const static uint32_t rate_limiting_precision = 1000*1000; -const static uint32_t producers_authority_threshold_pct = 66 * config::percent_1; - -const static uint16_t max_recursion_depth = 6; - -const static uint32_t default_base_per_transaction_net_usage = 100; // 100 bytes minimum (for signature and misc overhead) -const static uint32_t default_base_per_transaction_cpu_usage = 500; // TODO: is this reasonable? -const static uint32_t default_base_per_action_cpu_usage = 1000; -const static uint32_t default_base_setcode_cpu_usage = 2 * 1024 * 1024; /// overbilling cpu usage for setcode to cover incidental -const static uint32_t default_per_signature_cpu_usage = 100 * 1000; // TODO: is this reasonable? -const static uint32_t default_per_lock_net_usage = 32; -const static uint64_t default_context_free_discount_cpu_usage_num = 20; -const static uint64_t default_context_free_discount_cpu_usage_den = 100; -const static uint32_t default_max_transaction_cpu_usage = default_max_block_cpu_usage / 10; -const static uint32_t default_max_transaction_net_usage = default_max_block_net_usage / 10; +const static uint32_t default_max_block_net_usage = 1024 * 1024; /// at 500ms blocks and 200byte trx, this enables ~10,000 TPS burst +const static uint32_t default_target_block_net_usage_pct = 10 * percent_1; /// we target 1000 TPS +const static uint32_t default_max_transaction_net_usage = default_max_block_net_usage / 2; +const static uint32_t default_base_per_transaction_net_usage = 12; // 12 bytes (11 bytes for worst case of transaction_receipt_header + 1 byte for static_variant tag) +const static uint32_t default_net_usage_leeway = 500; // TODO: is this reasonable? +const static uint32_t default_context_free_discount_net_usage_num = 20; // TODO: is this reasonable? +const static uint32_t default_context_free_discount_net_usage_den = 100; +const static uint32_t transaction_id_net_usage = 32; // 32 bytes for the size of a transaction id + +const static uint32_t default_max_block_cpu_usage = 100'000; /// max block cpu usage in microseconds +const static uint32_t default_target_block_cpu_usage_pct = 5 * percent_1; /// target 1000 TPS +const static uint32_t default_max_transaction_cpu_usage = default_max_block_cpu_usage; +const static uint32_t default_min_transaction_cpu_usage_us = 100; /// 10000 TPS equiv +const static uint32_t default_base_per_transaction_cpu_usage = 512; // TODO: is this reasonable? +const static uint32_t default_base_per_action_cpu_usage = 1024; +const static uint32_t default_base_setcode_cpu_usage = 2 * 1024 * 1024; /// overbilling cpu usage for setcode to cover incidental +const static uint32_t default_per_signature_cpu_usage = 100 * 1024; // TODO: is this reasonable? +const static uint32_t default_cpu_usage_leeway = 2048; // TODO: is this reasonable? +const static uint32_t default_context_free_discount_cpu_usage_num = 20; +const static uint32_t default_context_free_discount_cpu_usage_den = 100; + +const static uint32_t default_max_trx_lifetime = 60*60; // 1 hour +const static uint32_t default_deferred_trx_expiration_window = 10*60; // 10 minutes +//static const uint32_t deferred_trx_expiration_window_ms = 10*60*1000l; // TODO: make 10 minutes configurable by system +const static uint32_t default_max_trx_delay = 45*24*3600; // 45 days +const static uint32_t default_max_inline_action_size = 4 * 1024; // 4 KB +const static uint16_t default_max_inline_action_depth = 4; +const static uint16_t default_max_auth_depth = 6; +const static uint32_t default_max_gen_trx_count = 16; + +const static uint32_t base_check_authorization_cpu_per_authorization = 64; // TODO: is this reasonable? +const static uint32_t base_authority_checker_cpu_per_permission = 128; // TODO: is this reasonable? +const static uint32_t resource_processing_cpu_overhead_per_billed_account = 256; // TODO: is this reasonable? +const static uint32_t determine_payers_cpu_overhead_per_authorization = 64; // TODO: is this reasonable? +const static uint32_t ram_usage_validation_overhead_per_account = 64; // TODO: is this reasonable? + +const static uint32_t fixed_net_overhead_of_packed_trx = 16; // TODO: is this reasonable? + +const static uint32_t fixed_overhead_shared_vector_ram_bytes = 16; ///< overhead accounts for fixed portion of size of shared_vector field const static uint32_t overhead_per_row_per_index_ram_bytes = 32; ///< overhead accounts for basic tracking structures in a row per index const static uint32_t overhead_per_account_ram_bytes = 2*1024; ///< overhead accounts for basic account storage and pre-pays features like account recovery const static uint32_t setcode_ram_bytes_multiplier = 10; ///< multiplier on contract size to account for multiple copies and cached compilation @@ -86,6 +103,11 @@ const static eosio::chain::wasm_interface::vm_type default_wasm_runtime = eosio: * The number of sequential blocks produced by a single producer */ const static int producer_repetitions = 12; +const static int max_producers = 125; + +const static size_t maximum_tracked_dpos_confirmations = 1024; ///< +static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); + /** * The number of blocks produced per round is based upon all producers having a chance @@ -106,9 +128,8 @@ constexpr uint64_t billable_size_v = ((billable_size::value + billable_alignm } } } // namespace eosio::chain::config -template -Number EOS_PERCENT(Number value, uint32_t percentage) { - return value * percentage / eosio::chain::config::percent_100; +constexpr uint64_t EOS_PERCENT(uint64_t value, uint32_t percentage) { + return (value * percentage) / eosio::chain::config::percent_100; } template diff --git a/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp b/libraries/chain/include/eosio/chain/contract_table_objects.hpp similarity index 84% rename from libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp rename to libraries/chain/include/eosio/chain/contract_table_objects.hpp index e4db26cdf42..bc2fff140c4 100644 --- a/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp +++ b/libraries/chain/include/eosio/chain/contract_table_objects.hpp @@ -4,7 +4,7 @@ */ #pragma once -#include +#include #include #include @@ -13,7 +13,7 @@ #include #include -namespace eosio { namespace chain { namespace contracts { +namespace eosio { namespace chain { /** * @brief The table_id_object class tracks the mapping of (scope, code, table) to an opaque identifier @@ -64,7 +64,7 @@ namespace eosio { namespace chain { namespace contracts { table_id t_id; uint64_t primary_key; account_name payer = 0; - shared_vector value; + shared_string value; }; using key_value_index = chainbase::shared_multi_index_container< @@ -160,63 +160,61 @@ namespace eosio { namespace chain { namespace contracts { typedef secondary_index::index_object index_long_double_object; typedef secondary_index::index_index index_long_double_index; -} // ::contracts - namespace config { template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 2; ///< overhead for 2x indices internal-key and code,scope,table static const uint64_t value = 44 + overhead; ///< 36 bytes for constant size fields + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 2; ///< overhead for potentially single-row table, 2x indices internal-key and primary key static const uint64_t value = 32 + 8 + 4 + overhead; ///< 32 bytes for our constant size fields, 8 for pointer to vector data, 4 bytes for a size of vector + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key static const uint64_t value = 24 + 8 + overhead; ///< 24 bytes for fixed fields + 8 bytes key + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key static const uint64_t value = 24 + 16 + overhead; ///< 24 bytes for fixed fields + 16 bytes key + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key static const uint64_t value = 24 + 32 + overhead; ///< 24 bytes for fixed fields + 32 bytes key + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key static const uint64_t value = 24 + 8 + overhead; ///< 24 bytes for fixed fields + 8 bytes key + overhead }; template<> - struct billable_size { + struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key static const uint64_t value = 24 + 16 + overhead; ///< 24 bytes for fixed fields + 16 bytes key + overhead }; -} +} // namespace config } } // namespace eosio::chain -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::table_id_object, eosio::chain::contracts::table_id_multi_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::key_value_object, eosio::chain::contracts::key_value_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::table_id_object, eosio::chain::table_id_multi_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::key_value_object, eosio::chain::key_value_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index64_object, eosio::chain::contracts::index64_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index128_object, eosio::chain::contracts::index128_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index256_object, eosio::chain::contracts::index256_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index_double_object, eosio::chain::contracts::index_double_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index_long_double_object, eosio::chain::contracts::index_long_double_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index64_object, eosio::chain::index64_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index128_object, eosio::chain::index128_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index256_object, eosio::chain::index256_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_double_object, eosio::chain::index_double_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_long_double_object, eosio::chain::index_long_double_index) -FC_REFLECT(eosio::chain::contracts::table_id_object, (id)(code)(scope)(table) ) -FC_REFLECT(eosio::chain::contracts::key_value_object, (id)(t_id)(primary_key)(value)(payer) ) +FC_REFLECT(eosio::chain::table_id_object, (id)(code)(scope)(table) ) +FC_REFLECT(eosio::chain::key_value_object, (id)(t_id)(primary_key)(value)(payer) ) diff --git a/libraries/chain/include/eosio/chain/contracts/types.hpp b/libraries/chain/include/eosio/chain/contract_types.hpp similarity index 65% rename from libraries/chain/include/eosio/chain/contracts/types.hpp rename to libraries/chain/include/eosio/chain/contract_types.hpp index f7f524ec107..b51b48b2eff 100644 --- a/libraries/chain/include/eosio/chain/contracts/types.hpp +++ b/libraries/chain/include/eosio/chain/contract_types.hpp @@ -7,19 +7,9 @@ #include -namespace eosio { namespace chain { namespace contracts { +namespace eosio { namespace chain { -using namespace boost::multiprecision; - -template -using uint_t = number >; - -using uint8 = uint_t<8>; -using uint16 = uint_t<16>; -using uint32 = uint_t<32>; -using uint64 = uint_t<64>; - -using fixed_string32 = fc::fixed_string>; +using fixed_string32 = fc::fixed_string>; using fixed_string16 = fc::fixed_string<>; using type_name = string; using field_name = string; @@ -117,7 +107,6 @@ struct newaccount { account_name name; authority owner; authority active; - authority recovery; static account_name get_account() { return config::system_account_name; @@ -130,8 +119,8 @@ struct newaccount { struct setcode { account_name account; - uint8 vmtype; - uint8 vmversion; + uint8_t vmtype = 0; + uint8_t vmversion = 0; bytes code; static account_name get_account() { @@ -161,8 +150,7 @@ struct updateauth { account_name account; permission_name permission; permission_name parent; - authority data; - uint32_t delay; + authority auth; static account_name get_account() { return config::system_account_name; @@ -230,57 +218,23 @@ struct unlinkauth { } }; -struct onerror : bytes { - using bytes::bytes; - - static account_name get_account() { - return config::system_account_name; - } - - static action_name get_name() { - return N(onerror); - } -}; +struct onerror { + uint128_t sender_id; + bytes sent_trx; -struct postrecovery { - account_name account; - authority data; - string memo; + onerror( uint128_t sid, const char* data, size_t len ) + :sender_id(sid),sent_trx(data,data+len){} static account_name get_account() { return config::system_account_name; } static action_name get_name() { - return N(postrecovery); - } -}; - -struct passrecovery { - account_name account; - - static account_name get_account() { - return config::system_account_name; - } - - static action_name get_name() { - return N(passrecovery); + return N(onerror); } }; -struct vetorecovery { - account_name account; - - static account_name get_account() { - return config::system_account_name; - } - - static action_name get_name() { - return N(vetorecovery); - } -}; - struct canceldelay { permission_level canceling_auth; transaction_id_type trx_id; @@ -294,24 +248,22 @@ struct canceldelay { } }; -} } } /// namespace eosio::chain::contracts - -FC_REFLECT( eosio::chain::contracts::type_def , (new_type_name)(type) ) -FC_REFLECT( eosio::chain::contracts::field_def , (name)(type) ) -FC_REFLECT( eosio::chain::contracts::struct_def , (name)(base)(fields) ) -FC_REFLECT( eosio::chain::contracts::action_def , (name)(type)(ricardian_contract) ) -FC_REFLECT( eosio::chain::contracts::clause_pair , (id)(body) ) -FC_REFLECT( eosio::chain::contracts::table_def , (name)(index_type)(key_names)(key_types)(type) ) -FC_REFLECT( eosio::chain::contracts::abi_def , (types)(structs)(actions)(tables)(ricardian_clauses) ) - -FC_REFLECT( eosio::chain::contracts::newaccount , (creator)(name)(owner)(active)(recovery) ) -FC_REFLECT( eosio::chain::contracts::setcode , (account)(vmtype)(vmversion)(code) ) //abi -FC_REFLECT( eosio::chain::contracts::setabi , (account)(abi) ) -FC_REFLECT( eosio::chain::contracts::updateauth , (account)(permission)(parent)(data)(delay) ) -FC_REFLECT( eosio::chain::contracts::deleteauth , (account)(permission) ) -FC_REFLECT( eosio::chain::contracts::linkauth , (account)(code)(type)(requirement) ) -FC_REFLECT( eosio::chain::contracts::unlinkauth , (account)(code)(type) ) -FC_REFLECT( eosio::chain::contracts::postrecovery , (account)(data)(memo) ) -FC_REFLECT( eosio::chain::contracts::passrecovery , (account) ) -FC_REFLECT( eosio::chain::contracts::vetorecovery , (account) ) -FC_REFLECT( eosio::chain::contracts::canceldelay , (canceling_auth)(trx_id) ) +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::type_def , (new_type_name)(type) ) +FC_REFLECT( eosio::chain::field_def , (name)(type) ) +FC_REFLECT( eosio::chain::struct_def , (name)(base)(fields) ) +FC_REFLECT( eosio::chain::action_def , (name)(type)(ricardian_contract) ) +FC_REFLECT( eosio::chain::clause_pair , (id)(body) ) +FC_REFLECT( eosio::chain::table_def , (name)(index_type)(key_names)(key_types)(type) ) +FC_REFLECT( eosio::chain::abi_def , (types)(structs)(actions)(tables)(ricardian_clauses) ) + +FC_REFLECT( eosio::chain::newaccount , (creator)(name)(owner)(active) ) +FC_REFLECT( eosio::chain::setcode , (account)(vmtype)(vmversion)(code) ) +FC_REFLECT( eosio::chain::setabi , (account)(abi) ) +FC_REFLECT( eosio::chain::updateauth , (account)(permission)(parent)(auth) ) +FC_REFLECT( eosio::chain::deleteauth , (account)(permission) ) +FC_REFLECT( eosio::chain::linkauth , (account)(code)(type)(requirement) ) +FC_REFLECT( eosio::chain::unlinkauth , (account)(code)(type) ) +FC_REFLECT( eosio::chain::canceldelay , (canceling_auth)(trx_id) ) +FC_REFLECT( eosio::chain::onerror, (sender_id)(sent_trx) ) diff --git a/libraries/chain/include/eosio/chain/contracts/chain_initializer.hpp b/libraries/chain/include/eosio/chain/contracts/chain_initializer.hpp deleted file mode 100644 index 15079b02ec5..00000000000 --- a/libraries/chain/include/eosio/chain/contracts/chain_initializer.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include -#include - -namespace eosio { namespace chain { namespace contracts { - - class chain_initializer - { - - public: - chain_initializer(const genesis_state_type& genesis) : genesis(genesis) {} - - time_point get_chain_start_time(); - chain::chain_config get_chain_start_configuration(); - producer_schedule_type get_chain_start_producers(); - - void register_types(chain::chain_controller& chain, chainbase::database& db); - - void prepare_database(chain::chain_controller& chain, chainbase::database& db); - - static abi_def eos_contract_abi(const abi_def& eosio_system_abi); - - private: - genesis_state_type genesis; - }; - -} } } // namespace eosio::chain::contracts - diff --git a/libraries/chain/include/eosio/chain/contracts/genesis_state.hpp b/libraries/chain/include/eosio/chain/contracts/genesis_state.hpp deleted file mode 100644 index cb6eeda2afb..00000000000 --- a/libraries/chain/include/eosio/chain/contracts/genesis_state.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include -#include - -#include - -#include -#include - -namespace eosio { namespace chain { namespace contracts { - -struct genesis_state_type { - chain_config initial_configuration = { - .base_per_transaction_net_usage = config::default_base_per_transaction_net_usage, - .base_per_transaction_cpu_usage = config::default_base_per_transaction_cpu_usage, - .base_per_action_cpu_usage = config::default_base_per_action_cpu_usage, - .base_setcode_cpu_usage = config::default_base_setcode_cpu_usage, - .per_signature_cpu_usage = config::default_per_signature_cpu_usage, - .per_lock_net_usage = config::default_per_lock_net_usage, - .context_free_discount_cpu_usage_num = config::default_context_free_discount_cpu_usage_num, - .context_free_discount_cpu_usage_den = config::default_context_free_discount_cpu_usage_den, - .max_transaction_cpu_usage = config::default_max_transaction_cpu_usage, - .max_transaction_net_usage = config::default_max_transaction_net_usage, - .max_block_cpu_usage = config::default_max_block_cpu_usage, - .target_block_cpu_usage_pct = config::default_target_block_cpu_usage_pct, - .max_block_net_usage = config::default_max_block_net_usage, - .target_block_net_usage_pct = config::default_target_block_net_usage_pct, - .max_transaction_lifetime = config::default_max_trx_lifetime, - .max_transaction_exec_time = 0, // TODO: unused? - .max_authority_depth = config::default_max_auth_depth, - .max_inline_depth = config::default_max_inline_depth, - .max_inline_action_size = config::default_max_inline_action_size, - .max_generated_transaction_count = config::default_max_gen_trx_count, - .max_transaction_delay = config::default_max_trx_delay - }; - - time_point initial_timestamp = fc::time_point::from_iso_string( "2018-03-01T12:00:00" );; - public_key_type initial_key = fc::variant("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV").as(); - - /** - * Temporary, will be moved elsewhere. - */ - chain_id_type initial_chain_id; - - /** - * Get the chain_id corresponding to this genesis state. - * - * This is the SHA256 serialization of the genesis_state. - */ - chain_id_type compute_chain_id() const; -}; - -} } } // namespace eosio::contracts - - -FC_REFLECT(eosio::chain::contracts::genesis_state_type, - (initial_timestamp)(initial_key)(initial_configuration)(initial_chain_id)) diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp new file mode 100644 index 00000000000..8f965bb7b2b --- /dev/null +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -0,0 +1,207 @@ +#pragma once +#include +#include +#include +#include + +#include +#include + +namespace chainbase { + class database; +} + + +namespace eosio { namespace chain { + + class authorization_manager; + + namespace resource_limits { + class resource_limits_manager; + }; + + struct controller_impl; + using chainbase::database; + using boost::signals2::signal; + + class dynamic_global_property_object; + class global_property_object; + class permission_object; + class account_object; + using resource_limits::resource_limits_manager; + using apply_handler = std::function; + + class fork_database; + + class controller { + public: + struct config { + path block_log_dir = chain::config::default_block_log_dir; + path shared_memory_dir = chain::config::default_shared_memory_dir; + uint64_t shared_memory_size = chain::config::default_shared_memory_size; + bool read_only = false; + + genesis_state genesis; + wasm_interface::vm_type wasm_runtime = chain::config::default_wasm_runtime; + }; + + + controller( const config& cfg ); + ~controller(); + + void startup(); + + /** + * Starts a new pending block session upon which new transactions can + * be pushed. + */ + void start_block( block_timestamp_type time = block_timestamp_type(), uint16_t confirm_block_count = 0 ); + + void abort_block(); + + /** + * These transactions were previously pushed by have since been unapplied, recalling push_transaction + * with the transaction_metadata_ptr will remove them from the source of this data IFF it succeeds. + * + * The caller is responsible for calling drop_unapplied_transaction on a failing transaction that + * they never intend to retry + * + * @return vector of transactions which have been unapplied + */ + vector get_unapplied_transactions() const; + void drop_unapplied_transaction(const transaction_metadata_ptr& trx); + + /** + * These transaction IDs represent transactions available in the head chain state as scheduled + * or otherwise generated transactions. + * + * calling push_scheduled_transaction with these IDs will remove the associated transaction from + * the chain state IFF it succeeds or objectively fails + * + * @return + */ + vector get_scheduled_transactions() const; + + /** + * + */ + transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us = 0 ); + + /** + * Attempt to execute a specific transaction in our deferred trx database + * + */ + transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& scheduled, fc::time_point deadline, uint32_t billed_cpu_time_us = 0 ); + + void finalize_block(); + void sign_block( const std::function& signer_callback ); + void commit_block(); + void log_irreversible_blocks(); + void pop_block(); + + void push_block( const signed_block_ptr& b ); + + /** + * Call this method when a producer confirmation is received, this might update + * the last bft irreversible block and/or cause a switch of forks + */ + void push_confirmation( const header_confirmation& c ); + + chainbase::database& db()const; + + fork_database& fork_db()const; + + const account_object& get_account( account_name n )const; + const global_property_object& get_global_properties()const; + const dynamic_global_property_object& get_dynamic_global_properties()const; + const permission_object& get_permission( const permission_level& level )const; + const resource_limits_manager& get_resource_limits_manager()const; + resource_limits_manager& get_mutable_resource_limits_manager(); + const authorization_manager& get_authorization_manager()const; + authorization_manager& get_mutable_authorization_manager(); + + uint32_t head_block_num()const; + time_point head_block_time()const; + block_id_type head_block_id()const; + account_name head_block_producer()const; + const block_header& head_block_header()const; + block_state_ptr head_block_state()const; + + time_point pending_block_time()const; + block_state_ptr pending_block_state()const; + + const producer_schedule_type& active_producers()const; + const producer_schedule_type& pending_producers()const; + optional proposed_producers()const; + + uint32_t last_irreversible_block_num() const; + block_id_type last_irreversible_block_id() const; + + signed_block_ptr fetch_block_by_number( uint32_t block_num )const; + signed_block_ptr fetch_block_by_id( block_id_type id )const; + + block_id_type get_block_id_for_num( uint32_t block_num )const; + + void validate_referenced_accounts( const transaction& t )const; + void validate_expiration( const transaction& t )const; + void validate_tapos( const transaction& t )const; + + bool set_proposed_producers( vector producers ); + + + + + signal accepted_block_header; + signal accepted_block; + signal irreversible_block; + signal accepted_transaction; + signal applied_transaction; + signal accepted_confirmation; + + /* + signal pre_apply_block; + signal post_apply_block; + signal abort_apply_block; + signal pre_apply_transaction; + signal post_apply_transaction; + signal pre_apply_action; + signal post_apply_action; + */ + + const apply_handler* find_apply_handler( account_name contract, scope_name scope, action_name act )const; + wasm_interface& get_wasm_interface(); + + + optional get_abi_serializer( account_name n )const { + if( n.good() ) { + try { + const auto& a = get_account( n ); + abi_def abi; + if( abi_serializer::to_abi( a.abi, abi )) + return abi_serializer( abi ); + } FC_CAPTURE_AND_LOG((n)) + } + return optional(); + } + + template + fc::variant to_variant_with_abi( const T& obj ) { + fc::variant pretty_output; + abi_serializer::to_variant( obj, pretty_output, [&]( account_name n ){ return get_abi_serializer( n ); }); + return pretty_output; + } + + private: + + std::unique_ptr my; + + }; + +} } /// eosio::chain + +FC_REFLECT( eosio::chain::controller::config, + (block_log_dir) + (shared_memory_dir)(shared_memory_size)(read_only) + (genesis) + (wasm_runtime) + ) diff --git a/libraries/chain/include/eosio/chain/contracts/eos_contract.hpp b/libraries/chain/include/eosio/chain/eosio_contract.hpp similarity index 77% rename from libraries/chain/include/eosio/chain/contracts/eos_contract.hpp rename to libraries/chain/include/eosio/chain/eosio_contract.hpp index 4ddc8741aa0..c2e90a0f73c 100644 --- a/libraries/chain/include/eosio/chain/contracts/eos_contract.hpp +++ b/libraries/chain/include/eosio/chain/eosio_contract.hpp @@ -4,11 +4,12 @@ */ #pragma once -#include - #include +#include + +namespace eosio { namespace chain { -namespace eosio { namespace chain { namespace contracts { + class apply_context; /** * @defgroup native_action_handlers Native Action Handlers @@ -20,16 +21,18 @@ namespace eosio { namespace chain { namespace contracts { void apply_eosio_linkauth(apply_context&); void apply_eosio_unlinkauth(apply_context&); + /* void apply_eosio_postrecovery(apply_context&); void apply_eosio_passrecovery(apply_context&); void apply_eosio_vetorecovery(apply_context&); + */ void apply_eosio_setcode(apply_context&); void apply_eosio_setabi(apply_context&); - void apply_eosio_onerror(apply_context&); - void apply_eosio_canceldelay(apply_context&); ///@} end action handlers + + abi_def eosio_contract_abi(const abi_def& eosio_system_abi); -} } } /// namespace eosio::contracts +} } /// namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index fd58f4ce5c8..8b3064330fc 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -6,108 +6,270 @@ #include #include -#include +#include + + +#define EOS_ASSERT( expr, exc_type, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( !(expr) ) \ + FC_THROW_EXCEPTION( exc_type, FORMAT, __VA_ARGS__ ); \ + FC_MULTILINE_MACRO_END + +#define EOS_THROW( exc_type, FORMAT, ... ) \ + throw exc_type( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ); + +/** + * Macro inspired from FC_RETHROW_EXCEPTIONS + * The main difference here is that if the exception caught isn't of type "eosio::chain::chain_exception" + * This macro will rethrow the exception as the specified "exception_type" + */ +#define EOS_RETHROW_EXCEPTIONS(exception_type, FORMAT, ... ) \ + catch (eosio::chain::chain_exception& e) { \ + FC_RETHROW_EXCEPTION( e, warn, FORMAT, __VA_ARGS__ ); \ + } catch (fc::exception& e) { \ + exception_type new_exception(FC_LOG_MESSAGE( warn, FORMAT, __VA_ARGS__ )); \ + for (const auto& log: e.get_log()) { \ + new_exception.append_log(log); \ + } \ + throw new_exception; \ + } catch( const std::exception& e ) { \ + exception_type fce(FC_LOG_MESSAGE( warn, FORMAT" (${what})" ,__VA_ARGS__("what",e.what()))); \ + throw fce;\ + } catch( ... ) { \ + throw fc::unhandled_exception( \ + FC_LOG_MESSAGE( warn, FORMAT,__VA_ARGS__), \ + std::current_exception() ); \ + } + +/** + * Macro inspired from FC_CAPTURE_AND_RETHROW + * The main difference here is that if the exception caught isn't of type "eosio::chain::chain_exception" + * This macro will rethrow the exception as the specified "exception_type" + */ +#define EOS_CAPTURE_AND_RETHROW( exception_type, ... ) \ + catch (eosio::chain::chain_exception& e) { \ + FC_RETHROW_EXCEPTION( e, warn, "", FC_FORMAT_ARG_PARAMS(__VA_ARGS__) ); \ + } catch (fc::exception& e) { \ + exception_type new_exception(e.get_log()); \ + throw new_exception; \ + } catch( const std::exception& e ) { \ + exception_type fce( \ + FC_LOG_MESSAGE( warn, "${what}: ",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)("what",e.what())), \ + fc::std_exception_code,\ + BOOST_CORE_TYPEID(decltype(e)).name(), \ + e.what() ) ; throw fce;\ + } catch( ... ) { \ + throw fc::unhandled_exception( \ + FC_LOG_MESSAGE( warn, "",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)), \ + std::current_exception() ); \ + } + +#define EOS_RECODE_EXC( cause_type, effect_type ) \ + catch( const cause_type& e ) \ + { throw( effect_type( e.what(), e.get_log() ) ); } + namespace eosio { namespace chain { - FC_DECLARE_EXCEPTION( chain_exception, 3000000, "blockchain exception" ) - FC_DECLARE_DERIVED_EXCEPTION( database_query_exception, eosio::chain::chain_exception, 3010000, "database query exception" ) - FC_DECLARE_DERIVED_EXCEPTION( block_validate_exception, eosio::chain::chain_exception, 3020000, "block validation exception" ) - FC_DECLARE_DERIVED_EXCEPTION( transaction_exception, eosio::chain::chain_exception, 3030000, "transaction validation exception" ) - FC_DECLARE_DERIVED_EXCEPTION( action_validate_exception, eosio::chain::chain_exception, 3040000, "message validation exception" ) - FC_DECLARE_DERIVED_EXCEPTION( utility_exception, eosio::chain::chain_exception, 3070000, "utility method exception" ) - FC_DECLARE_DERIVED_EXCEPTION( undo_database_exception, eosio::chain::chain_exception, 3080000, "undo database exception" ) - FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, eosio::chain::chain_exception, 3090000, "unlinkable block" ) - FC_DECLARE_DERIVED_EXCEPTION( black_swan_exception, eosio::chain::chain_exception, 3100000, "black swan" ) - FC_DECLARE_DERIVED_EXCEPTION( unknown_block_exception, eosio::chain::chain_exception, 3110000, "unknown block" ) - FC_DECLARE_DERIVED_EXCEPTION( chain_type_exception, eosio::chain::chain_exception, 3120000, "chain type exception" ) - FC_DECLARE_DERIVED_EXCEPTION( missing_plugin_exception, eosio::chain::chain_exception, 3130000, "missing plugin exception" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_exception, eosio::chain::chain_exception, 3140000, "wallet exception" ) - FC_DECLARE_DERIVED_EXCEPTION( rate_limiting_invariant_exception, eosio::chain::chain_exception, 3150000, "rate limiting invariant violated" ) - - FC_DECLARE_DERIVED_EXCEPTION( permission_query_exception, eosio::chain::database_query_exception, 3010001, "Permission Query Exception" ) - FC_DECLARE_DERIVED_EXCEPTION( account_query_exception, eosio::chain::database_query_exception, 3010002, "Account Query Exception" ) - FC_DECLARE_DERIVED_EXCEPTION( contract_table_query_exception, eosio::chain::database_query_exception, 3010003, "Contract Table Query Exception" ) - FC_DECLARE_DERIVED_EXCEPTION( contract_query_exception, eosio::chain::database_query_exception, 3010004, "Contract Query Exception" ) - - FC_DECLARE_DERIVED_EXCEPTION( block_tx_output_exception, eosio::chain::block_validate_exception, 3020001, "transaction outputs in block do not match transaction outputs from applying block" ) - FC_DECLARE_DERIVED_EXCEPTION( block_concurrency_exception, eosio::chain::block_validate_exception, 3020002, "block does not guarantee concurrent execution without conflicts" ) - FC_DECLARE_DERIVED_EXCEPTION( block_lock_exception, eosio::chain::block_validate_exception, 3020003, "shard locks in block are incorrect or mal-formed" ) - FC_DECLARE_DERIVED_EXCEPTION( block_resource_exhausted, eosio::chain::block_validate_exception, 3020004, "block exhausted allowed resources" ) - FC_DECLARE_DERIVED_EXCEPTION( block_too_old_exception, eosio::chain::block_validate_exception, 3020005, "block is too old to push" ) - - FC_DECLARE_DERIVED_EXCEPTION( tx_missing_auth, eosio::chain::transaction_exception, 3030001, "missing required authority" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_missing_sigs, eosio::chain::transaction_exception, 3030002, "signatures do not satisfy declared authorizations" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_auth, eosio::chain::transaction_exception, 3030003, "irrelevant authority included" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_sig, eosio::chain::transaction_exception, 3030004, "irrelevant signature included" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate_sig, eosio::chain::transaction_exception, 3030005, "duplicate signature included" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_committee_approval, eosio::chain::transaction_exception, 3030006, "committee account cannot directly approve transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( insufficient_fee, eosio::chain::transaction_exception, 3030007, "insufficient fee" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_missing_recipient, eosio::chain::transaction_exception, 3030009, "missing required recipient" ) - FC_DECLARE_DERIVED_EXCEPTION( checktime_exceeded, eosio::chain::transaction_exception, 3030010, "allotted processing time was exceeded" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate, eosio::chain::transaction_exception, 3030011, "duplicate transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction_exception, eosio::chain::transaction_exception, 3030012, "unknown transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_scheduling_exception, eosio::chain::transaction_exception, 3030013, "transaction failed during sheduling" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_unknown_argument, eosio::chain::transaction_exception, 3030014, "transaction provided an unknown value to a system call" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_resource_exhausted, eosio::chain::transaction_exception, 3030015, "transaction exhausted allowed resources" ) - FC_DECLARE_DERIVED_EXCEPTION( page_memory_error, eosio::chain::transaction_exception, 3030016, "error in WASM page memory" ) - FC_DECLARE_DERIVED_EXCEPTION( unsatisfied_permission, eosio::chain::transaction_exception, 3030017, "Unsatisfied permission" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_msgs_auth_exceeded, eosio::chain::transaction_exception, 3030018, "Number of transaction messages per authorized account has been exceeded" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_msgs_code_exceeded, eosio::chain::transaction_exception, 3030019, "Number of transaction messages per code account has been exceeded" ) - FC_DECLARE_DERIVED_EXCEPTION( wasm_execution_error, eosio::chain::transaction_exception, 3030020, "Runtime Error Processing WASM" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_decompression_error, eosio::chain::transaction_exception, 3030021, "Error decompressing transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( expired_tx_exception, eosio::chain::transaction_exception, 3030022, "Expired Transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_exp_too_far_exception, eosio::chain::transaction_exception, 3030023, "Transaction Expiration Too Far" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_ref_block_exception, eosio::chain::transaction_exception, 3030024, "Invalid Reference Block" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_apply_exception, eosio::chain::transaction_exception, 3030025, "Transaction Apply Exception" ) - FC_DECLARE_DERIVED_EXCEPTION( wasm_serialization_error, eosio::chain::transaction_exception, 3030026, "Serialization Error Processing WASM" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_empty_region, eosio::chain::transaction_exception, 3030027, "Transaction contains an empty region" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_empty_cycle, eosio::chain::transaction_exception, 3030028, "Transaction contains an empty cycle" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_empty_shard, eosio::chain::transaction_exception, 3030029, "Transaction contains an empty shard" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_receipt_inconsistent_status, eosio::chain::transaction_exception, 3030030, "Transaction receipt applied status does not match received status" ) - FC_DECLARE_DERIVED_EXCEPTION( cfa_irrelevant_auth, eosio::chain::transaction_exception, 3030031, "context-free action should have no required authority" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_no_action, eosio::chain::transaction_exception, 3030032, "transaction should have at least one normal action" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_no_auths, eosio::chain::transaction_exception, 3030033, "transaction should have at least one required authority" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_receipt_inconsistent_cpu, eosio::chain::transaction_exception, 3030034, "Transaction receipt applied kcpu_usage does not match received kcpu_usage" ) - FC_DECLARE_DERIVED_EXCEPTION( tx_receipt_inconsistent_net, eosio::chain::transaction_exception, 3030035, "Transaction receipt applied net_usage_words does not match received net_usage_words" ) - - FC_DECLARE_DERIVED_EXCEPTION( account_name_exists_exception, eosio::chain::action_validate_exception, 3040001, "account name already exists" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_action_args_exception, eosio::chain::action_validate_exception, 3040002, "Invalid Action Arguments" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eosio::chain::utility_exception, 3060001, "invalid pts address" ) - FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eosio::chain::chain_exception, 37006, "insufficient feeds" ) - - FC_DECLARE_DERIVED_EXCEPTION( pop_empty_chain, eosio::chain::undo_database_exception, 3070001, "there are no blocks to pop" ) - - FC_DECLARE_DERIVED_EXCEPTION( name_type_exception, eosio::chain::chain_type_exception, 3120001, "Invalid name" ) - FC_DECLARE_DERIVED_EXCEPTION( public_key_type_exception, eosio::chain::chain_type_exception, 3120002, "Invalid public key" ) - FC_DECLARE_DERIVED_EXCEPTION( private_key_type_exception, eosio::chain::chain_type_exception, 3120003, "Invalid private key" ) - FC_DECLARE_DERIVED_EXCEPTION( authority_type_exception, eosio::chain::chain_type_exception, 3120004, "Invalid authority" ) - FC_DECLARE_DERIVED_EXCEPTION( action_type_exception, eosio::chain::chain_type_exception, 3120005, "Invalid action" ) - FC_DECLARE_DERIVED_EXCEPTION( transaction_type_exception, eosio::chain::chain_type_exception, 3120006, "Invalid transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( abi_type_exception, eosio::chain::chain_type_exception, 3120007, "Invalid ABI" ) - FC_DECLARE_DERIVED_EXCEPTION( block_id_type_exception, eosio::chain::chain_type_exception, 3120008, "Invalid block ID" ) - FC_DECLARE_DERIVED_EXCEPTION( transaction_id_type_exception, eosio::chain::chain_type_exception, 3120009, "Invalid transaction ID" ) - FC_DECLARE_DERIVED_EXCEPTION( packed_transaction_type_exception, eosio::chain::chain_type_exception, 3120010, "Invalid packed transaction" ) - FC_DECLARE_DERIVED_EXCEPTION( asset_type_exception, eosio::chain::chain_type_exception, 3120011, "Invalid asset" ) - - FC_DECLARE_DERIVED_EXCEPTION( missing_chain_api_plugin_exception, eosio::chain::missing_plugin_exception, 3130001, "Missing Chain API Plugin" ) - FC_DECLARE_DERIVED_EXCEPTION( missing_wallet_api_plugin_exception, eosio::chain::missing_plugin_exception, 3130002, "Missing Wallet API Plugin" ) - FC_DECLARE_DERIVED_EXCEPTION( missing_account_history_api_plugin_exception, eosio::chain::missing_plugin_exception, 3130003, "Missing Account History API Plugin" ) - FC_DECLARE_DERIVED_EXCEPTION( missing_net_api_plugin_exception, eosio::chain::missing_plugin_exception, 3130004, "Missing Net API Plugin" ) - - FC_DECLARE_DERIVED_EXCEPTION( wallet_exist_exception, eosio::chain::wallet_exception, 3140001, "Wallet already exists" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_nonexistent_exception, eosio::chain::wallet_exception, 3140002, "Nonexistent wallet" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_locked_exception, eosio::chain::wallet_exception, 3140003, "Locked wallet" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_missing_pub_key_exception, eosio::chain::wallet_exception, 3140004, "Missing public key" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_invalid_password_exception, eosio::chain::wallet_exception, 3140005, "Invalid wallet password" ) - FC_DECLARE_DERIVED_EXCEPTION( wallet_not_available_exception, eosio::chain::wallet_exception, 3140006, "No available wallet" ) - - FC_DECLARE_DERIVED_EXCEPTION( rate_limiting_state_inconsistent, eosio::chain::rate_limiting_invariant_exception, 3150001, "internal state is no longer consistent" ) - FC_DECLARE_DERIVED_EXCEPTION( rate_limiting_overcommitment, eosio::chain::rate_limiting_invariant_exception, 3150002, "chain resource limits are overcommitted" ) - - - #define EOS_RECODE_EXC( cause_type, effect_type ) \ - catch( const cause_type& e ) \ - { throw( effect_type( e.what(), e.get_log() ) ); } + FC_DECLARE_EXCEPTION( chain_exception, + 3000000, "blockchain exception" ) + /** + * chain_exception + * |- chain_type_exception + * |- fork_database_exception + * |- block_validate_exception + * |- transaction_exception + * |- action_validate_exception + * |- database_exception + * |- wasm_exception + * |- resource_exhausted_exception + * |- misc_exception + * |- missing_plugin_exception + * |- wallet_exception + */ + + FC_DECLARE_DERIVED_EXCEPTION( chain_type_exception, chain_exception, + 3010000, "chain type exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( name_type_exception, chain_type_exception, + 3010001, "Invalid name" ) + FC_DECLARE_DERIVED_EXCEPTION( public_key_type_exception, chain_type_exception, + 3010002, "Invalid public key" ) + FC_DECLARE_DERIVED_EXCEPTION( private_key_type_exception, chain_type_exception, + 3010003, "Invalid private key" ) + FC_DECLARE_DERIVED_EXCEPTION( authority_type_exception, chain_type_exception, + 3010004, "Invalid authority" ) + FC_DECLARE_DERIVED_EXCEPTION( action_type_exception, chain_type_exception, + 3010005, "Invalid action" ) + FC_DECLARE_DERIVED_EXCEPTION( transaction_type_exception, chain_type_exception, + 3010006, "Invalid transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( abi_type_exception, chain_type_exception, + 3010007, "Invalid ABI" ) + FC_DECLARE_DERIVED_EXCEPTION( block_id_type_exception, chain_type_exception, + 3010008, "Invalid block ID" ) + FC_DECLARE_DERIVED_EXCEPTION( transaction_id_type_exception, chain_type_exception, + 3010009, "Invalid transaction ID" ) + FC_DECLARE_DERIVED_EXCEPTION( packed_transaction_type_exception, chain_type_exception, + 3010010, "Invalid packed transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_type_exception, chain_type_exception, + 3010011, "Invalid asset" ) + + + FC_DECLARE_DERIVED_EXCEPTION( fork_database_exception, chain_exception, + 3020000, "fork database exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, chain_exception, + 3020001, "unlinkable block" ) + + + FC_DECLARE_DERIVED_EXCEPTION( block_validate_exception, chain_exception, + 3030000, "block exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( block_tx_output_exception, block_validate_exception, + 3030001, "transaction outputs in block do not match transaction outputs from applying block" ) + FC_DECLARE_DERIVED_EXCEPTION( block_concurrency_exception, block_validate_exception, + 3030002, "block does not guarantee concurrent execution without conflicts" ) + FC_DECLARE_DERIVED_EXCEPTION( block_lock_exception, block_validate_exception, + 3030003, "shard locks in block are incorrect or mal-formed" ) + FC_DECLARE_DERIVED_EXCEPTION( block_resource_exhausted, block_validate_exception, + 3030004, "block exhausted allowed resources" ) + FC_DECLARE_DERIVED_EXCEPTION( block_too_old_exception, block_validate_exception, + 3030005, "block is too old to push" ) + + + FC_DECLARE_DERIVED_EXCEPTION( transaction_exception, chain_exception, + 3040000, "transaction exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( tx_decompression_error, transaction_exception, + 3040001, "Error decompressing transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_no_action, transaction_exception, + 3040002, "transaction should have at least one normal action" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_no_auths, transaction_exception, + 3040003, "transaction should have at least one required authority" ) + FC_DECLARE_DERIVED_EXCEPTION( cfa_irrelevant_auth, transaction_exception, + 3040004, "context-free action should have no required authority" ) + FC_DECLARE_DERIVED_EXCEPTION( expired_tx_exception, transaction_exception, + 3040005, "Expired Transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_exp_too_far_exception, transaction_exception, + 3040006, "Transaction Expiration Too Far" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_ref_block_exception, transaction_exception, + 3040007, "Invalid Reference Block" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate, transaction_exception, + 3040008, "duplicate transaction" ) + + + FC_DECLARE_DERIVED_EXCEPTION( action_validate_exception, chain_exception, + 3050000, "action exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( account_name_exists_exception, action_validate_exception, + 3050001, "account name already exists" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_action_args_exception, action_validate_exception, + 3050002, "Invalid Action Arguments" ) + + FC_DECLARE_DERIVED_EXCEPTION( database_exception, chain_exception, + 3060000, "database exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( permission_query_exception, database_exception, + 3060001, "Permission Query Exception" ) + FC_DECLARE_DERIVED_EXCEPTION( account_query_exception, database_exception, + 3060002, "Account Query Exception" ) + FC_DECLARE_DERIVED_EXCEPTION( contract_table_query_exception, database_exception, + 3060003, "Contract Table Query Exception" ) + FC_DECLARE_DERIVED_EXCEPTION( contract_query_exception, database_exception, + 3060004, "Contract Query Exception" ) + + + FC_DECLARE_DERIVED_EXCEPTION( wasm_exception, chain_exception, + 3070000, "WASM Exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( page_memory_error, wasm_exception, + 3070001, "error in WASM page memory" ) + + FC_DECLARE_DERIVED_EXCEPTION( wasm_execution_error, wasm_exception, + 3070002, "Runtime Error Processing WASM" ) + + FC_DECLARE_DERIVED_EXCEPTION( wasm_serialization_error, wasm_exception, + 3070003, "Serialization Error Processing WASM" ) + + FC_DECLARE_DERIVED_EXCEPTION( overlapping_memory_error, wasm_exception, + 3070004, "memcpy with overlapping memory" ) + + + + FC_DECLARE_DERIVED_EXCEPTION( resource_exhausted_exception, chain_exception, + 3080000, "resource exhausted exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( ram_usage_exceeded, resource_exhausted_exception, + 3080001, "account using more than allotted RAM usage" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_net_usage_exceeded, resource_exhausted_exception, + 3080002, "transaction exceeded the current network usage limit imposed on the transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( block_net_usage_exceeded, resource_exhausted_exception, + 3080003, "transaction network usage is too much for the remaining allowable usage of the current block" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_cpu_usage_exceeded, resource_exhausted_exception, + 3080004, "transaction exceeded the current CPU usage limit imposed on the transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( block_cpu_usage_exceeded, resource_exhausted_exception, + 3080005, "transaction CPU usage is too much for the remaining allowable usage of the current block" ) + FC_DECLARE_DERIVED_EXCEPTION( deadline_exception, resource_exhausted_exception, + 3080006, "transaction took too long" ) + FC_DECLARE_DERIVED_EXCEPTION( leeway_deadline_exception, deadline_exception, + 3081001, "transaction reached the deadline set due to leeway on account CPU limits" ) + + FC_DECLARE_DERIVED_EXCEPTION( authorization_exception, chain_exception, + 3090000, "Authorization exception" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate_sig, authorization_exception, + 3090001, "duplicate signature included" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_sig, authorization_exception, + 3090002, "irrelevant signature included" ) + FC_DECLARE_DERIVED_EXCEPTION( unsatisfied_authorization, authorization_exception, + 3090003, "provided keys, permissions, and delays do not satisfy declared authorizations" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_auth_exception, authorization_exception, + 3090004, "missing required authority" ) + FC_DECLARE_DERIVED_EXCEPTION( irrelevant_auth_exception, authorization_exception, + 3090005, "irrelevant authority included" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_delay_exception, authorization_exception, + 3090006, "insufficient delay" ) + + FC_DECLARE_DERIVED_EXCEPTION( misc_exception, chain_exception, + 3100000, "Miscellaneous exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( rate_limiting_state_inconsistent, misc_exception, + 3100001, "internal state is no longer consistent" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_block_exception, misc_exception, + 3100002, "unknown block" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction_exception, misc_exception, + 3100003, "unknown transaction" ) + + + FC_DECLARE_DERIVED_EXCEPTION( missing_plugin_exception, chain_exception, + 3110000, "missing plugin exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( missing_chain_api_plugin_exception, missing_plugin_exception, + 3110001, "Missing Chain API Plugin" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_wallet_api_plugin_exception, missing_plugin_exception, + 3110002, "Missing Wallet API Plugin" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_account_history_api_plugin_exception, missing_plugin_exception, + 3110003, "Missing Account History API Plugin" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_net_api_plugin_exception, missing_plugin_exception, + 3110004, "Missing Net API Plugin" ) + + + FC_DECLARE_DERIVED_EXCEPTION( wallet_exception, chain_exception, + 3120000, "wallet exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( wallet_exist_exception, wallet_exception, + 3120001, "Wallet already exists" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_nonexistent_exception, wallet_exception, + 3120002, "Nonexistent wallet" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_locked_exception, wallet_exception, + 3120003, "Locked wallet" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_missing_pub_key_exception, wallet_exception, + 3120004, "Missing public key" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_invalid_password_exception, wallet_exception, + 3120005, "Invalid wallet password" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_not_available_exception, wallet_exception, + 3120006, "No available wallet" ) + FC_DECLARE_DERIVED_EXCEPTION( wallet_unlocked_exception, wallet_exception, + 3120007, "Already unlocked" ) + } } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/ext.hpp b/libraries/chain/include/eosio/chain/ext.hpp deleted file mode 100644 index b009daf4132..00000000000 --- a/libraries/chain/include/eosio/chain/ext.hpp +++ /dev/null @@ -1,193 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include - -namespace eosio { namespace chain { - -template< typename T > -struct extension -{ - extension() {} - - T value; -}; - -template< typename T > -struct eos_extension_pack_count_visitor -{ - eos_extension_pack_count_visitor( const T& v ) : value(v) {} - - template - void operator()( const char* name )const - { - count += ((value.*member).valid()) ? 1 : 0; - } - - const T& value; - mutable uint32_t count = 0; -}; - -template< typename Stream, typename T > -struct eos_extension_pack_read_visitor -{ - eos_extension_pack_read_visitor( Stream& s, const T& v ) : stream(s), value(v) {} - - template - void operator()( const char* name )const - { - if( (value.*member).valid() ) - { - fc::raw::pack( stream, unsigned_int( which ) ); - fc::raw::pack( stream, *(value.*member) ); - } - ++which; - } - - Stream& stream; - const T& value; - mutable uint32_t which = 0; -}; - -template< typename Stream, class T > -void operator<<( Stream& stream, const eosio::chain::extension& value ) -{ - eos_extension_pack_count_visitor count_vtor( value.value ); - fc::reflector::visit( count_vtor ); - fc::raw::pack( stream, unsigned_int( count_vtor.count ) ); - eos_extension_pack_read_visitor read_vtor( stream, value.value ); - fc::reflector::visit( read_vtor ); -} - - - -template< typename Stream, typename T > -struct eos_extension_unpack_visitor -{ - eos_extension_unpack_visitor( Stream& s, T& v ) : stream(s), value(v) - { - unsigned_int c; - fc::raw::unpack( stream, c ); - count_left = c.value; - maybe_read_next_which(); - } - - void maybe_read_next_which()const - { - if( count_left > 0 ) - { - unsigned_int w; - fc::raw::unpack( stream, w ); - next_which = w.value; - } - } - - template< typename Member, class Class, Member (Class::*member)> - void operator()( const char* name )const - { - if( (count_left > 0) && (which == next_which) ) - { - typename Member::value_type temp; - fc::raw::unpack( stream, temp ); - (value.*member) = temp; - --count_left; - maybe_read_next_which(); - } - else - (value.*member).reset(); - ++which; - } - - mutable uint32_t which = 0; - mutable uint32_t next_which = 0; - mutable uint32_t count_left = 0; - - Stream& stream; - T& value; -}; - -template< typename Stream, typename T > -void operator>>( Stream& s, eosio::chain::extension& value ) -{ - value = eosio::chain::extension(); - eos_extension_unpack_visitor vtor( s, value.value ); - fc::reflector::visit( vtor ); - FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here -} - -} } // eosio::chain - -namespace fc { - -template< typename T > -struct eos_extension_from_variant_visitor -{ - eos_extension_from_variant_visitor( const variant_object& v, T& val ) - : vo( v ), value( val ) - { - count_left = vo.size(); - } - - template - void operator()( const char* name )const - { - auto it = vo.find(name); - if( it != vo.end() ) - { - from_variant( it->value(), (value.*member) ); - assert( count_left > 0 ); // x.find(k) returns true for n distinct values of k only if x.size() >= n - --count_left; - } - } - - const variant_object& vo; - T& value; - mutable uint32_t count_left = 0; -}; - -template< typename T > -void from_variant( const fc::variant& var, eosio::chain::extension& value ) -{ - value = eosio::chain::extension(); - if( var.is_null() ) - return; - if( var.is_array() ) - { - FC_ASSERT( var.size() == 0 ); - return; - } - - eos_extension_from_variant_visitor vtor( var.get_object(), value.value ); - fc::reflector::visit( vtor ); - FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here -} - -template< typename T > -struct eos_extension_to_variant_visitor -{ - eos_extension_to_variant_visitor( const T& v ) : value(v) {} - - template - void operator()( const char* name )const - { - if( (value.*member).valid() ) - mvo[ name ] = (value.*member); - } - - const T& value; - mutable mutable_variant_object mvo; -}; - -template< typename T > -void to_variant( const eosio::chain::extension& value, fc::variant& var ) -{ - eos_extension_to_variant_visitor vtor( value.value ); - fc::reflector::visit( vtor ); - var = vtor.mvo; -} - -} // fc diff --git a/libraries/chain/include/eosio/chain/fixed_key.hpp b/libraries/chain/include/eosio/chain/fixed_key.hpp index 9937c9b831d..84b8681535c 100644 --- a/libraries/chain/include/eosio/chain/fixed_key.hpp +++ b/libraries/chain/include/eosio/chain/fixed_key.hpp @@ -12,6 +12,8 @@ namespace eosio { + using chain::uint128_t; + template class fixed_key; diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 1cba21c3ce2..42e6c5f2922 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -1,105 +1,78 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ #pragma once -#include +#include +#include -#include -#include -#include -#include -#include +namespace eosio { namespace chain { + using boost::signals2::signal; -namespace eosio { namespace chain { - using boost::multi_index_container; - using namespace boost::multi_index; - - struct fork_item - { - fork_item( signed_block d ) - :num(d.block_num()),id(d.id()),data( std::move(d) ){} - - block_id_type previous_id()const { return data.previous; } - - weak_ptr< fork_item > prev; - uint32_t num; // initialized in ctor - /** - * Used to flag a block as invalid and prevent other blocks from - * building on top of it. - */ - bool invalid = false; - block_id_type id; - signed_block data; - }; - typedef shared_ptr item_ptr; + struct fork_database_impl; + typedef vector branch_type; /** - * As long as blocks are pushed in order the fork - * database will maintain a linked tree of all blocks - * that branch from the start_block. The tree will - * have a maximum depth of 1024 blocks after which - * the database will start lopping off forks. + * @class fork_database + * @brief manages light-weight state for all potential unconfirmed forks * - * Every time a block is pushed into the fork DB the - * block with the highest block_num will be returned. + * As new blocks are received, they are pushed into the fork database. The fork + * database tracks the longest chain and the last irreversible block number. All + * blocks older than the last irreversible block are freed after emitting the + * irreversible signal. */ - class fork_database - { + class fork_database { public: - typedef vector branch_type; - /// The maximum number of blocks that may be skipped in an out-of-order push - const static int MAX_BLOCK_REORDERING = 1024; - fork_database(); - void reset(); + fork_database( const fc::path& data_dir ); + ~fork_database(); + + void close(); - void start_block(signed_block b); - void remove(block_id_type b); - void set_head(shared_ptr h); - bool is_known_block(const block_id_type& id)const; - shared_ptr fetch_block(const block_id_type& id)const; - vector fetch_block_by_number(uint32_t n)const; + block_state_ptr get_block(const block_id_type& id)const; + block_state_ptr get_block_in_current_chain_by_num( uint32_t n )const; +// vector get_blocks_by_number(uint32_t n)const; /** - * @return the new head block ( the longest fork ) + * Provides a "valid" blockstate upon which other forks may build. */ - shared_ptr push_block(const signed_block& b); - shared_ptr head()const { return _head; } - void pop_block(); + void set( block_state_ptr s ); + + /** this method will attempt to append the block to an exsting + * block_state and will return a pointer to the new block state or + * throw on error. + */ + block_state_ptr add( signed_block_ptr b ); + block_state_ptr add( block_state_ptr next_block ); + void remove( const block_id_type& id ); + + void add( const header_confirmation& c ); + + const block_state_ptr& head()const; /** * Given two head blocks, return two branches of the fork graph that * end with a common ancestor (same prior block) */ - pair< branch_type, branch_type > fetch_branch_from(block_id_type first, - block_id_type second)const; - - struct block_id; - struct block_num; - struct by_previous; - typedef multi_index_container< - item_ptr, - indexed_by< - hashed_unique, member, std::hash>, - hashed_non_unique, const_mem_fun, std::hash>, - ordered_non_unique, member> - > - > fork_multi_index_type; - - void set_max_size( uint32_t s ); + pair< branch_type, branch_type > fetch_branch_from( const block_id_type& first, + const block_id_type& second )const; - private: - /** @return a pointer to the newly pushed item */ - void _push_block(const item_ptr& b ); - void _push_next(const item_ptr& newly_inserted); - uint32_t _max_size = 1024; + /** + * If the block is invalid, it will be removed. If it is valid, then blocks older + * than the LIB are pruned after emitting irreversible signal. + */ + void set_validity( const block_state_ptr& h, bool valid ); + void mark_in_current_chain( const block_state_ptr& h, bool in_current_chain ); + void prune( const block_state_ptr& h ); + + /** + * This signal is emited when a block state becomes irreversible, once irreversible + * it is removed unless it is the head block. + */ + signal irreversible; - fork_multi_index_type _unlinked_index; - fork_multi_index_type _index; - shared_ptr _head; + private: + void set_bft_irreversible( block_id_type id ); + unique_ptr my; }; -} } // eosio::chain + +} } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/generated_transaction_object.hpp b/libraries/chain/include/eosio/chain/generated_transaction_object.hpp index 305b85c4606..da808e6d122 100644 --- a/libraries/chain/include/eosio/chain/generated_transaction_object.hpp +++ b/libraries/chain/include/eosio/chain/generated_transaction_object.hpp @@ -34,7 +34,15 @@ namespace eosio { namespace chain { time_point delay_until; /// this generated transaction will not be applied until the specified time time_point expiration; /// this generated transaction will not be applied after this time time_point published; - shared_vector packed_trx; + shared_string packed_trx; + + uint32_t set( const transaction& trx ) { + auto trxsize = fc::raw::pack_size( trx ); + packed_trx.resize( trxsize ); + fc::datastream ds( packed_trx.data(), trxsize ); + fc::raw::pack( ds, trx ); + return trxsize; + } }; struct by_trx_id; @@ -48,13 +56,13 @@ namespace eosio { namespace chain { indexed_by< ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER(generated_transaction_object, generated_transaction_object::id_type, id)>, ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, transaction_id_type, trx_id)>, - ordered_unique< tag, + ordered_unique< tag, composite_key< generated_transaction_object, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, expiration), BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id) > >, - ordered_unique< tag, + ordered_unique< tag, composite_key< generated_transaction_object, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, delay_until), BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id) @@ -69,8 +77,6 @@ namespace eosio { namespace chain { > >; - typedef chainbase::generic_index generated_transaction_index; - namespace config { template<> struct billable_size { @@ -81,4 +87,3 @@ namespace eosio { namespace chain { } } // eosio::chain CHAINBASE_SET_INDEX_TYPE(eosio::chain::generated_transaction_object, eosio::chain::generated_transaction_multi_index) - diff --git a/libraries/chain/include/eosio/chain/genesis_state.hpp b/libraries/chain/include/eosio/chain/genesis_state.hpp new file mode 100644 index 00000000000..622e60cd03f --- /dev/null +++ b/libraries/chain/include/eosio/chain/genesis_state.hpp @@ -0,0 +1,68 @@ + +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace eosio { namespace chain { + +struct genesis_state { + chain_config initial_configuration = { + .max_block_net_usage = config::default_max_block_net_usage, + .target_block_net_usage_pct = config::default_target_block_net_usage_pct, + .max_transaction_net_usage = config::default_max_transaction_net_usage, + .base_per_transaction_net_usage = config::default_base_per_transaction_net_usage, + .net_usage_leeway = config::default_net_usage_leeway, + .context_free_discount_net_usage_num = config::default_context_free_discount_net_usage_num, + .context_free_discount_net_usage_den = config::default_context_free_discount_net_usage_den, + + .max_block_cpu_usage = config::default_max_block_cpu_usage, + .target_block_cpu_usage_pct = config::default_target_block_cpu_usage_pct, + .max_transaction_cpu_usage = config::default_max_transaction_cpu_usage, + .base_per_transaction_cpu_usage = config::default_base_per_transaction_cpu_usage, + .base_per_action_cpu_usage = config::default_base_per_action_cpu_usage, + .base_setcode_cpu_usage = config::default_base_setcode_cpu_usage, + .per_signature_cpu_usage = config::default_per_signature_cpu_usage, + .cpu_usage_leeway = config::default_cpu_usage_leeway, + .context_free_discount_cpu_usage_num = config::default_context_free_discount_cpu_usage_num, + .context_free_discount_cpu_usage_den = config::default_context_free_discount_cpu_usage_den, + + .max_transaction_lifetime = config::default_max_trx_lifetime, + .deferred_trx_expiration_window = config::default_deferred_trx_expiration_window, + .max_transaction_delay = config::default_max_trx_delay, + .max_inline_action_size = config::default_max_inline_action_size, + .max_inline_action_depth = config::default_max_inline_action_depth, + .max_authority_depth = config::default_max_auth_depth, + .max_generated_transaction_count = config::default_max_gen_trx_count, + }; + + time_point initial_timestamp = fc::time_point::from_iso_string( "2018-03-02T12:00:00" );; + public_key_type initial_key = fc::variant("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV").as(); + + /** + * Temporary, will be moved elsewhere. + */ + chain_id_type initial_chain_id; + + /** + * Get the chain_id corresponding to this genesis state. + * + * This is the SHA256 serialization of the genesis_state. + */ + chain_id_type compute_chain_id() const; +}; + +} } // namespace eosio::chain + + +FC_REFLECT(eosio::chain::genesis_state, + (initial_timestamp)(initial_key)(initial_configuration)(initial_chain_id)) diff --git a/libraries/chain/include/eosio/chain/get_config.hpp b/libraries/chain/include/eosio/chain/get_config.hpp deleted file mode 100644 index 4bd7707bf52..00000000000 --- a/libraries/chain/include/eosio/chain/get_config.hpp +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -namespace eosio { namespace chain { - -fc::variant_object get_config(); - -} } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 5479592d29d..fe9ae85db10 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -16,14 +16,6 @@ namespace eosio { namespace chain { - struct blocknum_producer_schedule { - blocknum_producer_schedule( allocator a ) - :second(a){} - - block_num_type first; - shared_producer_schedule_type second; - }; - /** * @class global_property_object * @brief Maintains global state information (committee_member list, current fees) @@ -34,19 +26,12 @@ namespace eosio { namespace chain { */ class global_property_object : public chainbase::object { - OBJECT_CTOR(global_property_object, (active_producers)(new_active_producers)(pending_active_producers) ) - - id_type id; - chain_config configuration; - shared_producer_schedule_type active_producers; - shared_producer_schedule_type new_active_producers; + OBJECT_CTOR(global_property_object, (proposed_schedule)) - /** every block that has change in producer schedule gets inserted into this list, this includes - * all blocks that see a change in producer signing keys or vote order. - * - * TODO: consider moving this to a more effeicent datatype - */ - shared_vector< blocknum_producer_schedule > pending_active_producers; + id_type id; + optional proposed_schedule_block_num; + shared_producer_schedule_type proposed_schedule; + chain_config configuration; }; @@ -62,45 +47,10 @@ namespace eosio { namespace chain { */ class dynamic_global_property_object : public chainbase::object { - OBJECT_CTOR(dynamic_global_property_object, (block_merkle_root)) - - id_type id; - uint32_t head_block_number = 0; - block_id_type head_block_id; - time_point time; - account_name current_producer; - - /** - * The current absolute slot number. Equal to the total - * number of slots since genesis. Also equal to the total - * number of missed slots plus head_block_number. - */ - uint64_t current_absolute_slot = 0; - - /** - * Bitmap used to compute producer participation. Stores - * a high bit for each generated block, a low bit for - * each missed block. Least significant bit is most - * recent block. - * - * NOTE: This bitmap always excludes the head block, - * which, by definition, exists. The least significant - * bit corresponds to the block with number - * head_block_num()-1 - * - * e.g. if the least significant 5 bits were 10011, it - * would indicate that the last two blocks prior to the - * head block were produced, the two before them were - * missed, and the one before that was produced. - */ - //uint64_t recent_slots_filled; - - uint32_t last_irreversible_block_num = 0; + OBJECT_CTOR(dynamic_global_property_object) - /** - * Used to calculate the merkle root over all blocks - */ - shared_incremental_merkle block_merkle_root; + id_type id; + uint64_t global_action_sequence = 0; }; using global_property_multi_index = chainbase::shared_multi_index_container< @@ -128,16 +78,9 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::dynamic_global_property_object, eosio::chain::dynamic_global_property_multi_index) FC_REFLECT(eosio::chain::dynamic_global_property_object, - (head_block_number) - (head_block_id) - (time) - (current_producer) - (current_absolute_slot) - /* (recent_slots_filled) */ - (last_irreversible_block_num) + (global_action_sequence) ) FC_REFLECT(eosio::chain::global_property_object, - (configuration) - (active_producers) + (proposed_schedule_block_num)(proposed_schedule)(configuration) ) diff --git a/libraries/chain/include/eosio/chain/immutable_chain_parameters.hpp b/libraries/chain/include/eosio/chain/immutable_chain_parameters.hpp deleted file mode 100644 index 8598b5f580a..00000000000 --- a/libraries/chain/include/eosio/chain/immutable_chain_parameters.hpp +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -#include - -#include - -namespace eosio { namespace chain { - -} } // eosio::chain - diff --git a/libraries/chain/include/eosio/chain/incremental_merkle.hpp b/libraries/chain/include/eosio/chain/incremental_merkle.hpp index 3b3d16b9fb3..15c3fc4392e 100644 --- a/libraries/chain/include/eosio/chain/incremental_merkle.hpp +++ b/libraries/chain/include/eosio/chain/incremental_merkle.hpp @@ -233,7 +233,7 @@ class incremental_merkle_impl { } } - private: +// private: uint64_t _node_count; Container _active_nodes; }; @@ -242,3 +242,5 @@ typedef incremental_merkle_impl incremental_merkle; typedef incremental_merkle_impl shared_incremental_merkle; } } /// eosio::chain + +FC_REFLECT( eosio::chain::incremental_merkle, (_active_nodes)(_node_count) ); diff --git a/libraries/chain/include/eosio/chain/internal_exceptions.hpp b/libraries/chain/include/eosio/chain/internal_exceptions.hpp deleted file mode 100644 index 390090a5ee1..00000000000 --- a/libraries/chain/include/eosio/chain/internal_exceptions.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include - -#define EOS_DECLARE_INTERNAL_EXCEPTION( exc_name, seqnum, msg ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - internal_ ## exc_name, \ - eosio::chain::internal_exception, \ - 3990000 + seqnum, \ - msg \ - ) - -namespace eosio { namespace chain { - -FC_DECLARE_DERIVED_EXCEPTION( internal_exception, eosio::chain::chain_exception, 3990000, "internal exception" ) - -EOS_DECLARE_INTERNAL_EXCEPTION( verify_auth_max_auth_exceeded, 1, "Exceeds max authority fan-out" ) -EOS_DECLARE_INTERNAL_EXCEPTION( verify_auth_account_not_found, 2, "Auth account not found" ) - -} } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/name.hpp b/libraries/chain/include/eosio/chain/name.hpp index c3117d5398b..af5af89ec23 100644 --- a/libraries/chain/include/eosio/chain/name.hpp +++ b/libraries/chain/include/eosio/chain/name.hpp @@ -99,6 +99,17 @@ namespace eosio { namespace chain { } } // eosio::chain +namespace std { + template<> struct hash : private hash { + typedef eosio::chain::name argument_type; + typedef typename hash::result_type result_type; + result_type operator()(const argument_type& name) const noexcept + { + return hash::operator()(name.value); + } + }; +}; + namespace fc { class variant; void to_variant(const eosio::chain::name& c, fc::variant& v); diff --git a/libraries/chain/include/eosio/chain/permission_object.hpp b/libraries/chain/include/eosio/chain/permission_object.hpp index b6bb47dcdcd..16185390475 100644 --- a/libraries/chain/include/eosio/chain/permission_object.hpp +++ b/libraries/chain/include/eosio/chain/permission_object.hpp @@ -8,54 +8,65 @@ #include "multi_index_includes.hpp" namespace eosio { namespace chain { + + class permission_usage_object : public chainbase::object { + OBJECT_CTOR(permission_usage_object) + + id_type id; + time_point last_used; ///< when this permission was last used + }; + + struct by_account_permission; + using permission_usage_index = chainbase::shared_multi_index_container< + permission_usage_object, + indexed_by< + ordered_unique, member> + > + >; + + class permission_object : public chainbase::object { OBJECT_CTOR(permission_object, (auth) ) - id_type id; - account_name owner; ///< the account this permission belongs to - id_type parent; ///< parent permission - permission_name name; ///< human-readable name for the permission - shared_authority auth; ///< authority required to execute this permission - time_point last_updated; ///< the last time this authority was updated - fc::microseconds delay; ///< delay associated with this permission + id_type id; + permission_usage_object::id_type usage_id; + id_type parent; ///< parent permission + account_name owner; ///< the account this permission belongs to + permission_name name; ///< human-readable name for the permission + time_point last_updated; ///< the last time this authority was updated + shared_authority auth; ///< authority required to execute this permission /** * @brief Checks if this permission is equivalent or greater than other * @tparam Index The permission_index - * @return a fc::microseconds set to the maximum delay encountered between this and the permission that is other; - * empty optional otherwise + * @return true if this permission is equivalent or greater than other, false otherwise * * Permissions are organized hierarchically such that a parent permission is strictly more powerful than its * children/grandchildren. This method checks whether this permission is of greater or equal power (capable of - * satisfying) permission @ref other. The returned value is an optional that will indicate the - * maximum delay encountered walking the hierarchy between this permission and other, if this satisfies other, - * otherwise an empty optional is returned. + * satisfying) permission @ref other. */ template - optional satisfies(const permission_object& other, const Index& permission_index) const { + bool satisfies(const permission_object& other, const Index& permission_index) const { // If the owners are not the same, this permission cannot satisfy other if( owner != other.owner ) - return optional(); + return false; - // if this permission satisfies other, then other's delay and this delay will have to contribute - auto max_delay = other.delay > delay ? other.delay : delay; // If this permission matches other, or is the immediate parent of other, then this permission satisfies other if( id == other.id || id == other.parent ) - return optional(max_delay); + return true; + // Walk up other's parent tree, seeing if we find this permission. If so, this permission satisfies other const permission_object* parent = &*permission_index.template get().find(other.parent); while( parent ) { - if( max_delay < parent->delay ) - max_delay = parent->delay; if( id == parent->parent ) - return optional(max_delay); + return true; if( parent->parent._id == 0 ) - return optional(); + return false; parent = &*permission_index.template get().find(parent->parent); } // This permission is not a parent of other, and so does not satisfy other - return optional(); + return false; } }; @@ -87,35 +98,11 @@ namespace eosio { namespace chain { > >; - class permission_usage_object : public chainbase::object { - OBJECT_CTOR(permission_usage_object) - - id_type id; - account_name account; ///< the account this permission belongs to - permission_name permission; ///< human-readable name for the permission - time_point last_used; ///< when this permission was last used - }; - - struct by_account_permission; - using permission_usage_index = chainbase::shared_multi_index_container< - permission_usage_object, - indexed_by< - ordered_unique, member>, - ordered_unique, - composite_key, - member, - member - > - > - > - >; - namespace config { template<> - struct billable_size { - static const uint64_t overhead = 6 * overhead_per_row_per_index_ram_bytes; ///< 6 indices 2x internal ID, parent, owner, name, name_usage - static const uint64_t value = 80 + overhead; ///< fixed field size + overhead + struct billable_size { // Also counts memory usage of the associated permission_usage_object + static const uint64_t overhead = 5 * overhead_per_row_per_index_ram_bytes; ///< 5 indices 2x internal ID, parent, owner, name + static const uint64_t value = (config::billable_size_v + 64) + overhead; ///< fixed field size + overhead }; } } } // eosio::chain @@ -124,7 +111,7 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::permission_object, eosio::chain::permissi CHAINBASE_SET_INDEX_TYPE(eosio::chain::permission_usage_object, eosio::chain::permission_usage_index) FC_REFLECT(chainbase::oid, (_id)) -FC_REFLECT(eosio::chain::permission_object, (id)(owner)(parent)(name)(auth)(last_updated)(delay)) +FC_REFLECT(eosio::chain::permission_object, (id)(usage_id)(parent)(owner)(name)(last_updated)(auth)) FC_REFLECT(chainbase::oid, (_id)) -FC_REFLECT(eosio::chain::permission_usage_object, (id)(account)(permission)(last_used)) +FC_REFLECT(eosio::chain::permission_usage_object, (id)(last_used)) diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index 5a715debfb4..6ebf064bf20 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -12,19 +12,26 @@ namespace eosio { namespace chain { account_name producer_name; public_key_type block_signing_key; - friend bool operator == ( const producer_key& a, const producer_key& b ) { - return tie( a.producer_name, a.block_signing_key ) == tie( b.producer_name,b.block_signing_key ); + friend bool operator == ( const producer_key& lhs, const producer_key& rhs ) { + return tie( lhs.producer_name, lhs.block_signing_key ) == tie( rhs.producer_name, rhs.block_signing_key ); } - friend bool operator != ( const producer_key& a, const producer_key& b ) { - return tie( a.producer_name, a.block_signing_key ) != tie( b.producer_name,b.block_signing_key ); + friend bool operator != ( const producer_key& lhs, const producer_key& rhs ) { + return tie( lhs.producer_name, lhs.block_signing_key ) != tie( rhs.producer_name, rhs.block_signing_key ); } }; /** - * Defines both the order, account name, and signing keys of the active set of producers. + * Defines both the order, account name, and signing keys of the active set of producers. */ struct producer_schedule_type { uint32_t version = 0; ///< sequentially incrementing version number vector producers; + + public_key_type get_producer_key( account_name p )const { + for( const auto& i : producers ) + if( i.producer_name == p ) + return i.block_signing_key; + return public_key_type(); + } }; struct shared_producer_schedule_type { @@ -49,12 +56,17 @@ namespace eosio { namespace chain { return result; } + void clear() { + version = 0; + producers.clear(); + } + uint32_t version = 0; ///< sequentially incrementing version number shared_vector producers; }; - inline bool operator == ( const producer_schedule_type& a, const producer_schedule_type& b ) + inline bool operator == ( const producer_schedule_type& a, const producer_schedule_type& b ) { if( a.version != b.version ) return false; if ( a.producers.size() != b.producers.size() ) return false; diff --git a/libraries/chain/include/eosio/chain/resource_limits.hpp b/libraries/chain/include/eosio/chain/resource_limits.hpp index d81c08019d4..ddb96bda9e0 100644 --- a/libraries/chain/include/eosio/chain/resource_limits.hpp +++ b/libraries/chain/include/eosio/chain/resource_limits.hpp @@ -22,6 +22,14 @@ namespace eosio { namespace chain { namespace resource_limits { uint32_t max_multiplier; // the multiplier by which virtual space can oversell usage when uncongested ratio contract_rate; // the rate at which a congested resource contracts its limit ratio expand_rate; // the rate at which an uncongested resource expands its limits + + void validate()const; // throws if the parameters do not satisfy basic sanity checks + }; + + struct account_resource_limit { + int64_t used = 0; ///< quantity used in current window + int64_t available = 0; ///< quantity available in current window (based upon fractional reserve) + int64_t max_gauranteed = 0; ///< max per window under 100% congestion }; class resource_limits_manager { @@ -31,18 +39,18 @@ namespace eosio { namespace chain { namespace resource_limits { { } + void add_indices(); void initialize_database(); - void initialize_chain(); void initialize_account( const account_name& account ); void set_block_parameters( const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ); void add_transaction_usage( const flat_set& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal ); + void add_pending_ram_usage( const account_name account, int64_t ram_delta ); + void verify_account_ram_usage( const account_name accunt )const; - void add_pending_account_ram_usage( const account_name account, int64_t ram_delta ); - void synchronize_account_ram_usage( ); - - void set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight); + /// set_account_limits returns true if new ram_bytes limit is more restrictive than the previously set one + bool set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight); void get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight) const; void process_account_limit_updates(); @@ -58,8 +66,14 @@ namespace eosio { namespace chain { namespace resource_limits { int64_t get_account_cpu_limit( const account_name& name ) const; int64_t get_account_net_limit( const account_name& name ) const; + account_resource_limit get_account_cpu_limit_ex( const account_name& name ) const; + account_resource_limit get_account_net_limit_ex( const account_name& name ) const; + + int64_t get_account_ram_usage( const account_name& name ) const; + private: chainbase::database& _db; }; } } } /// eosio::chain +FC_REFLECT( eosio::chain::resource_limits::account_resource_limit, (used)(available)(max_gauranteed) ) diff --git a/libraries/chain/include/eosio/chain/resource_limits_private.hpp b/libraries/chain/include/eosio/chain/resource_limits_private.hpp index 905eb7ca65f..376fe017d53 100644 --- a/libraries/chain/include/eosio/chain/resource_limits_private.hpp +++ b/libraries/chain/include/eosio/chain/resource_limits_private.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "multi_index_includes.hpp" @@ -18,6 +19,10 @@ namespace eosio { namespace chain { namespace resource_limits { return (value * r.numerator) / r.denominator; } + constexpr uint64_t integer_divide_ceil(uint64_t num, uint64_t den ) { + return (num / den) + ((num % den) > 0 ? 1 : 0); + } + /** * This class accumulates and exponential moving average based on inputs * This accumulator assumes there are no drops in input data @@ -27,6 +32,9 @@ namespace eosio { namespace chain { namespace resource_limits { template struct exponential_moving_average_accumulator { + static_assert( Precision > 0, "Precision must be positive" ); + static constexpr uint64_t max_raw_value = std::numeric_limits::max() / Precision; + exponential_moving_average_accumulator() : last_ordinal(0) , value_ex(0) @@ -42,14 +50,22 @@ namespace eosio { namespace chain { namespace resource_limits { * return the average value */ uint64_t average() const { - return value_ex / Precision; + return integer_divide_ceil(value_ex, Precision); } - void add( uint64_t units, uint32_t ordinal, uint32_t window_size ) + void add( uint64_t units, uint32_t ordinal, uint32_t window_size /* must be positive */ ) { + // check for some numerical limits before doing any state mutations + EOS_ASSERT(units <= max_raw_value, rate_limiting_state_inconsistent, "Usage exceeds maximum value representable after extending for precision"); + EOS_ASSERT(std::numeric_limits::max() - consumed >= units, rate_limiting_state_inconsistent, "Overflow in tracked usage when adding usage!"); + + auto value_ex_contrib = integer_divide_ceil(units * Precision, (uint64_t)window_size); + EOS_ASSERT(std::numeric_limits::max() - value_ex >= value_ex_contrib, rate_limiting_state_inconsistent, "Overflow in accumulated value when adding usage!"); + if( last_ordinal != ordinal ) { - if (last_ordinal + window_size > ordinal) { - const auto delta = ordinal - last_ordinal; + FC_ASSERT( ordinal > last_ordinal, "new ordinal cannot be less than the previous ordinal" ); + if( (uint64_t)last_ordinal + window_size > (uint64_t)ordinal ) { + const auto delta = ordinal - last_ordinal; // clearly 0 < delta < window_size const auto decay = make_ratio( (uint64_t)window_size - delta, (uint64_t)window_size @@ -61,11 +77,11 @@ namespace eosio { namespace chain { namespace resource_limits { } last_ordinal = ordinal; - consumed = value_ex / Precision; + consumed = average(); } consumed += units; - value_ex += units * Precision / (uint64_t)window_size; + value_ex += value_ex_contrib; } }; @@ -117,25 +133,13 @@ namespace eosio { namespace chain { namespace resource_limits { usage_accumulator cpu_usage; uint64_t ram_usage = 0; - uint64_t pending_ram_usage = 0; - - bool is_dirty() const { - // checks for ram_usage overflowing a signed int are maintained in the update step - return ram_usage != pending_ram_usage; - } }; using resource_usage_index = chainbase::shared_multi_index_container< resource_usage_object, indexed_by< ordered_unique, member>, - ordered_unique, member >, - ordered_unique, - composite_key - > + ordered_unique, member > > >; @@ -143,8 +147,18 @@ namespace eosio { namespace chain { namespace resource_limits { OBJECT_CTOR(resource_limits_config_object); id_type id; + static_assert( config::block_interval_ms > 0, "config::block_interval_ms must be positive" ); + static_assert( config::block_cpu_usage_average_window_ms >= config::block_interval_ms, + "config::block_cpu_usage_average_window_ms cannot be less than config::block_interval_ms" ); + static_assert( config::block_size_average_window_ms >= config::block_interval_ms, + "config::block_size_average_window_ms cannot be less than config::block_interval_ms" ); + + elastic_limit_parameters cpu_limit_parameters = {EOS_PERCENT(config::default_max_block_cpu_usage, config::default_target_block_cpu_usage_pct), config::default_max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}}; elastic_limit_parameters net_limit_parameters = {EOS_PERCENT(config::default_max_block_net_usage, config::default_target_block_net_usage_pct), config::default_max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}}; + + uint32_t account_cpu_usage_average_window = config::account_cpu_usage_average_window_ms / config::block_interval_ms; + uint32_t account_net_usage_average_window = config::account_net_usage_average_window_ms / config::block_interval_ms; }; using resource_limits_config_index = chainbase::shared_multi_index_container< diff --git a/libraries/chain/include/eosio/chain/scope_sequence_object.hpp b/libraries/chain/include/eosio/chain/scope_sequence_object.hpp deleted file mode 100644 index e1d9e11d1ed..00000000000 --- a/libraries/chain/include/eosio/chain/scope_sequence_object.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include -#include -#include -#include - -#include "multi_index_includes.hpp" - -namespace eosio { namespace chain { - - class scope_sequence_object : public chainbase::object { - OBJECT_CTOR(scope_sequence_object) - - id_type id; - scope_name scope; - account_name receiver; - uint64_t sequence = 0; - }; - using scope_sequence_id_type = scope_sequence_object::id_type; - - struct by_scope_receiver; - using scope_sequence_multi_index = chainbase::shared_multi_index_container< - scope_sequence_object, - indexed_by< - ordered_unique, member>, - ordered_unique, - composite_key< scope_sequence_object, - member, - member - > - > - > - >; - - typedef chainbase::generic_index scope_sequence_index; - -} } // eosio::chain - -CHAINBASE_SET_INDEX_TYPE(eosio::chain::scope_sequence_object, eosio::chain::scope_sequence_multi_index) - -FC_REFLECT(chainbase::oid, (_id)) - -FC_REFLECT(eosio::chain::scope_sequence_object, (id)(scope)(receiver)(sequence)) diff --git a/libraries/chain/include/eosio/chain/symbol.hpp b/libraries/chain/include/eosio/chain/symbol.hpp index 4317bc89d3d..b6c47c90ed2 100644 --- a/libraries/chain/include/eosio/chain/symbol.hpp +++ b/libraries/chain/include/eosio/chain/symbol.hpp @@ -59,8 +59,15 @@ namespace eosio { class symbol { public: - explicit symbol(uint8_t p, const char* s): m_value(string_to_symbol(p, s)) { } - explicit symbol(uint64_t v = SY(4, EOS)): m_value(v) { } + + static constexpr uint8_t max_precision = 18; + + explicit symbol(uint8_t p, const char* s): m_value(string_to_symbol(p, s)) { + FC_ASSERT(valid(), "invalid symbol: ${s}", ("s",s)); + } + explicit symbol(uint64_t v = SY(4, EOS)): m_value(v) { + FC_ASSERT(valid(), "invalid symbol: ${name}", ("name",name())); + } static symbol from_string(const string& from) { try { @@ -71,6 +78,7 @@ namespace eosio { auto prec_part = s.substr(0, comma_pos); uint8_t p = fc::to_int64(prec_part); string name_part = s.substr(comma_pos + 1); + FC_ASSERT( p <= max_precision, "precision should be <= 18"); return symbol(string_to_symbol(p, name_part.c_str())); } FC_CAPTURE_LOG_AND_RETHROW((from)) } @@ -78,7 +86,7 @@ namespace eosio { bool valid() const { const auto& s = name(); - return valid_name(s); + return decimals() <= max_precision && valid_name(s); } static bool valid_name(const string& name) { diff --git a/libraries/chain/include/eosio/chain/trace.hpp b/libraries/chain/include/eosio/chain/trace.hpp new file mode 100644 index 00000000000..47062e66691 --- /dev/null +++ b/libraries/chain/include/eosio/chain/trace.hpp @@ -0,0 +1,66 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include +#include + +namespace eosio { namespace chain { + + struct base_action_trace { + base_action_trace( const action_receipt& r ):receipt(r){} + base_action_trace(){} + + action_receipt receipt; + action act; + fc::microseconds elapsed; + uint64_t cpu_usage = 0; + string console; + + uint64_t total_cpu_usage = 0; /// total of inline_traces[x].cpu_usage + cpu_usage + transaction_id_type trx_id; ///< the transaction that generated this action + }; + + struct action_trace : public base_action_trace { + using base_action_trace::base_action_trace; + + vector inline_traces; + }; + + struct transaction_trace; + using transaction_trace_ptr = std::shared_ptr; + + struct transaction_trace { + transaction_id_type id; + fc::optional receipt; + fc::microseconds elapsed; + uint64_t net_usage = 0; + bool scheduled = false; + vector action_traces; ///< disposable + + transaction_trace_ptr failed_dtrx_trace; + fc::optional except; + std::exception_ptr except_ptr; + }; + + struct block_trace { + fc::microseconds elapsed; + uint64_t billed_cpu_usage_us; + vector trx_traces; + }; + using block_trace_ptr = std::shared_ptr; + +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::base_action_trace, + (receipt)(act)(elapsed)(cpu_usage)(console)(total_cpu_usage)(trx_id) ) + +FC_REFLECT_DERIVED( eosio::chain::action_trace, + (eosio::chain::base_action_trace), (inline_traces) ) + +FC_REFLECT( eosio::chain::transaction_trace, (id)(receipt)(elapsed)(net_usage)(scheduled) + (action_traces)(failed_dtrx_trace)(except) ) +FC_REFLECT( eosio::chain::block_trace, (elapsed)(billed_cpu_usage_us)(trx_traces) ) diff --git a/libraries/chain/include/eosio/chain/transaction.hpp b/libraries/chain/include/eosio/chain/transaction.hpp index 3a5a5798820..55d65a0bd46 100644 --- a/libraries/chain/include/eosio/chain/transaction.hpp +++ b/libraries/chain/include/eosio/chain/transaction.hpp @@ -3,102 +3,12 @@ * @copyright defined in eos/LICENSE.txt */ #pragma once -#include + +#include #include namespace eosio { namespace chain { - struct permission_level { - account_name actor; - permission_name permission; - }; - - inline bool operator== (const permission_level& lhs, const permission_level& rhs) - { - return (lhs.actor == rhs.actor) && (lhs.permission == rhs.permission); - } - - /** - * An action is performed by an actor, aka an account. It may - * be created explicitly and authorized by signatures or might be - * generated implicitly by executing application code. - * - * This follows the design pattern of React Flux where actions are - * named and then dispatched to one or more action handlers (aka stores). - * In the context of eosio, every action is dispatched to the handler defined - * by account 'scope' and function 'name', but the default handler may also - * forward the action to any number of additional handlers. Any application - * can write a handler for "scope::name" that will get executed if and only if - * this action is forwarded to that application. - * - * Each action may require the permission of specific actors. Actors can define - * any number of permission levels. The actors and their respective permission - * levels are declared on the action and validated independently of the executing - * application code. An application code will check to see if the required authorization - * were properly declared when it executes. - */ - struct action { - account_name account; - action_name name; - vector authorization; - bytes data; - - action(){} - - template::value, int> = 1> - action( vector auth, const T& value ) { - account = T::get_account(); - name = T::get_name(); - authorization = move(auth); - data.assign(value.data(), value.data() + value.size()); - } - - template::value, int> = 1> - action( vector auth, const T& value ) { - account = T::get_account(); - name = T::get_name(); - authorization = move(auth); - data = fc::raw::pack(value); - } - - action( vector auth, account_name account, action_name name, const bytes& data ) - : account(account), name(name), authorization(move(auth)), data(data) { - } - - template - T data_as()const { - FC_ASSERT( account == T::get_account() ); - FC_ASSERT( name == T::get_name() ); - return fc::raw::unpack(data); - } - }; - - struct action_notice : public action { - account_name receiver; - }; - - - /** - * When a transaction is referenced by a block it could imply one of several outcomes which - * describe the state-transition undertaken by the block producer. - */ - struct transaction_receipt { - enum status_enum { - executed = 0, ///< succeed, no error handler executed - soft_fail = 1, ///< objectively failed (not executed), error handler executed - hard_fail = 2, ///< objectively failed and error handler objectively failed thus no state change - delayed = 3 ///< transaction delayed - }; - - transaction_receipt() : status(hard_fail) {} - transaction_receipt( transaction_id_type tid ):status(executed),id(tid){} - - fc::enum_type status; - fc::unsigned_int kcpu_usage; - fc::unsigned_int net_usage_words; - transaction_id_type id; - }; - /** * The transaction header contains the fixed-sized data * associated with each transaction. It is separated from @@ -119,11 +29,10 @@ namespace eosio { namespace chain { */ struct transaction_header { time_point_sec expiration; ///< the time at which a transaction expires - uint16_t region = 0U; ///< the computational memory region this transaction applies to. uint16_t ref_block_num = 0U; ///< specifies a block num in the last 2^16 blocks. uint32_t ref_block_prefix = 0UL; ///< specifies the lower 32 bits of the blockid at get_ref_blocknum fc::unsigned_int max_net_usage_words = 0UL; /// upper limit on total network bandwidth (in 8 byte words) billed for this transaction - fc::unsigned_int max_kcpu_usage = 0UL; /// upper limit on the total number of kilo CPU usage units billed for this transaction + uint8_t max_cpu_usage_ms = 0; /// upper limit on the total CPU time billed for this transaction fc::unsigned_int delay_sec = 0UL; /// number of seconds to delay this transaction for during which it may be canceled. /** @@ -134,6 +43,7 @@ namespace eosio { namespace chain { } void set_reference_block( const block_id_type& reference_block ); bool verify_reference_block( const block_id_type& reference_block )const; + void validate()const; }; /** @@ -144,6 +54,7 @@ namespace eosio { namespace chain { struct transaction : public transaction_header { vector context_free_actions; vector actions; + extensions_type transaction_extensions; transaction_id_type id()const; digest_type sig_digest( const chain_id_type& chain_id, const vector& cfd = vector() )const; @@ -152,6 +63,15 @@ namespace eosio { namespace chain { const vector& cfd = vector(), bool allow_duplicate_keys = false )const; + uint32_t total_actions()const { return context_free_actions.size() + actions.size(); } + account_name first_authorizor()const { + for( const auto& a : actions ) { + for( const auto& u : a.authorization ) + return u.actor; + } + return account_name(); + } + }; struct signed_transaction : public transaction @@ -199,7 +119,8 @@ namespace eosio { namespace chain { set_transaction(t, std::move(t.context_free_data), _compression); } - uint32_t get_billable_size()const; + uint32_t get_unprunable_size()const; + uint32_t get_prunable_size()const; digest_type packed_digest()const; @@ -208,6 +129,7 @@ namespace eosio { namespace chain { bytes packed_context_free_data; bytes packed_trx; + time_point_sec expiration()const; transaction_id_type id()const; bytes get_raw_transaction()const; vector get_context_free_data()const; @@ -215,8 +137,13 @@ namespace eosio { namespace chain { signed_transaction get_signed_transaction()const; void set_transaction(const transaction& t, compression_type _compression = none); void set_transaction(const transaction& t, const vector& cfd, compression_type _compression = none); + + private: + mutable optional unpacked_trx; // <-- intermediate buffer used to retrieve values + void local_unpack()const; }; + using packed_transaction_ptr = std::shared_ptr; /** * When a transaction is generated it can be scheduled to occur @@ -226,7 +153,7 @@ namespace eosio { namespace chain { * passed back to the sender if the transaction fails for some * reason. */ - struct deferred_transaction : public transaction + struct deferred_transaction : public signed_transaction { uint128_t sender_id; /// ID assigned by sender of generated, accessible via WASM api when executing normal or error account_name sender; /// receives error handler callback @@ -235,8 +162,9 @@ namespace eosio { namespace chain { deferred_transaction() = default; - deferred_transaction(uint128_t sender_id, account_name sender, account_name payer,time_point_sec execute_after, const transaction& txn) - : transaction(txn), + deferred_transaction(uint128_t sender_id, account_name sender, account_name payer,time_point_sec execute_after, + const signed_transaction& txn) + : signed_transaction(txn), sender_id(sender_id), sender(sender), payer(payer), @@ -253,16 +181,16 @@ namespace eosio { namespace chain { account_name sender; uint128_t sender_id; }; -} } // eosio::chain -FC_REFLECT( eosio::chain::permission_level, (actor)(permission) ) -FC_REFLECT( eosio::chain::action, (account)(name)(authorization)(data) ) -FC_REFLECT( eosio::chain::transaction_receipt, (status)(kcpu_usage)(net_usage_words)(id)) -FC_REFLECT( eosio::chain::transaction_header, (expiration)(region)(ref_block_num)(ref_block_prefix) - (max_net_usage_words)(max_kcpu_usage)(delay_sec) ) -FC_REFLECT_DERIVED( eosio::chain::transaction, (eosio::chain::transaction_header), (context_free_actions)(actions) ) + uint128_t transaction_id_to_sender_id( const transaction_id_type& tid ); + +} } /// namespace eosio::chain + +FC_REFLECT( eosio::chain::transaction_header, (expiration)(ref_block_num)(ref_block_prefix) + (max_net_usage_words)(max_cpu_usage_ms)(delay_sec) ) +FC_REFLECT_DERIVED( eosio::chain::transaction, (eosio::chain::transaction_header), (context_free_actions)(actions)(transaction_extensions) ) FC_REFLECT_DERIVED( eosio::chain::signed_transaction, (eosio::chain::transaction), (signatures)(context_free_data) ) FC_REFLECT_ENUM( eosio::chain::packed_transaction::compression_type, (none)(zlib)) FC_REFLECT( eosio::chain::packed_transaction, (signatures)(compression)(packed_context_free_data)(packed_trx) ) -FC_REFLECT_DERIVED( eosio::chain::deferred_transaction, (eosio::chain::transaction), (sender_id)(sender)(payer)(execute_after) ) +FC_REFLECT_DERIVED( eosio::chain::deferred_transaction, (eosio::chain::signed_transaction), (sender_id)(sender)(payer)(execute_after) ) FC_REFLECT( eosio::chain::deferred_reference, (sender)(sender_id) ) diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp new file mode 100644 index 00000000000..3c49ae73d8b --- /dev/null +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include + +namespace eosio { namespace chain { + + class transaction_context { + private: + void init( uint64_t initial_net_usage ); + + public: + + transaction_context( controller& c, + const signed_transaction& t, + const transaction_id_type& trx_id, + fc::time_point start = fc::time_point::now() ); + + void init_for_implicit_trx( uint64_t initial_net_usage = 0 ); + + void init_for_input_trx( uint64_t packed_trx_unprunable_size, + uint64_t packed_trx_prunable_size, + uint32_t num_signatures ); + + void init_for_deferred_trx( fc::time_point published ); + + void exec(); + void finalize(); + void squash(); + + inline void add_net_usage( uint64_t u ) { net_usage += u; check_net_usage(); } + + void check_net_usage()const; + void check_time()const; + + void add_ram_usage( account_name account, int64_t ram_delta ); + + private: + + friend struct controller_impl; + friend class apply_context; + + void dispatch_action( action_trace& trace, const action& a, account_name receiver, bool context_free = false, uint32_t recurse_depth = 0 ); + inline void dispatch_action( action_trace& trace, const action& a, bool context_free = false ) { + dispatch_action(trace, a, a.account, context_free); + }; + void schedule_transaction(); + void record_transaction( const transaction_id_type& id, fc::time_point_sec expire ); + + void validate_cpu_usage_to_bill( int64_t u, bool check_minimum = true )const; + + /// Fields: + public: + + controller& control; + const signed_transaction& trx; + transaction_id_type id; + chainbase::database::session undo_session; + transaction_trace_ptr trace; + fc::time_point start; + + fc::time_point published; + + + vector executed; + flat_set bill_to_accounts; + flat_set validate_ram_usage; + + /// the maximum number of virtual CPU instructions of the transaction that can be safely billed to the billable accounts + uint64_t initial_max_billable_cpu = 0; + + fc::microseconds delay; + bool is_input = false; + bool apply_context_free = true; + + fc::time_point deadline = fc::time_point::maximum(); + fc::microseconds leeway = fc::microseconds(1000); + int64_t billed_cpu_time_us = 0; + + private: + bool is_initialized = false; + + + uint64_t net_limit = 0; + bool net_limit_due_to_block = true; + uint64_t eager_net_limit = 0; + uint64_t& net_usage; /// reference to trace->net_usage + + fc::microseconds objective_duration_limit; + bool objective_duration_limit_due_to_block = true; + int64_t deadline_exception_code = block_cpu_usage_exceeded::code_value; + }; + +} } diff --git a/libraries/chain/include/eosio/chain/transaction_metadata.hpp b/libraries/chain/include/eosio/chain/transaction_metadata.hpp index 4ed1201779c..66f63e1173b 100644 --- a/libraries/chain/include/eosio/chain/transaction_metadata.hpp +++ b/libraries/chain/include/eosio/chain/transaction_metadata.hpp @@ -5,75 +5,50 @@ #pragma once #include #include +#include namespace eosio { namespace chain { +/** + * This data structure should store context-free cached data about a transaction such as + * packed/unpacked/compressed and recovered keys + */ class transaction_metadata { public: - transaction_metadata( const transaction& t, const time_point& published, const account_name& sender, uint128_t sender_id, const char* raw_data, size_t raw_size, const optional& processing_deadline ) - :id(t.id()) - ,published(published) - ,sender(sender),sender_id(sender_id),raw_data(raw_data),raw_size(raw_size) - ,processing_deadline(processing_deadline) - ,_trx(&t) - {} - - transaction_metadata( const packed_transaction& t, chain_id_type chainid, const time_point& published, const optional& processing_deadline = optional(), bool implicit=false ); - - transaction_metadata( transaction_metadata && ) = default; - transaction_metadata& operator= (transaction_metadata &&) = default; - - // things for packed_transaction - optional raw_trx; - optional decompressed_trx; - vector context_free_data; - digest_type packed_digest; - - // things for signed/packed transactions - optional> signing_keys; - transaction_id_type id; + transaction_id_type signed_id; + signed_transaction trx; + packed_transaction packed_trx; + optional> signing_keys; + bool accepted = false; - uint32_t region_id = 0; - uint32_t cycle_index = 0; - uint32_t shard_index = 0; - uint32_t billable_packed_size = 0; - uint32_t signature_count = 0; - time_point published; - fc::microseconds delay; - - // things for processing deferred transactions - optional sender; - uint128_t sender_id = 0; - - // packed form to pass to contracts if needed - const char* raw_data = nullptr; - size_t raw_size = 0; - - vector packed_trx; + std::function on_result; - // is this transaction implicit - bool is_implicit = false; - // scopes available to this transaction if we are applying a block - optional*> allowed_read_locks; - optional*> allowed_write_locks; + transaction_metadata( const signed_transaction& t, packed_transaction::compression_type c = packed_transaction::none ) + :trx(t),packed_trx(t, c) { + id = trx.id(); + //raw_packed = fc::raw::pack( static_cast(trx) ); + signed_id = digest_type::hash(packed_trx); + } - const transaction& trx() const{ - if (decompressed_trx) { - return *decompressed_trx; - } else { - return *_trx; - } + transaction_metadata( const packed_transaction& ptrx ) + :trx( ptrx.get_signed_transaction() ), packed_trx(ptrx) { + id = trx.id(); + //raw_packed = fc::raw::pack( static_cast(trx) ); + signed_id = digest_type::hash(packed_trx); } - // limits - optional processing_deadline; + const flat_set& recover_keys() { + // TODO: Update caching logic below when we use a proper chain id setup for the particular blockchain rather than just chain_id_type() + if( !signing_keys ) + signing_keys = trx.get_signature_keys( chain_id_type() ); + return *signing_keys; + } - private: - const transaction* _trx = nullptr; + uint32_t total_actions()const { return trx.context_free_actions.size() + trx.actions.size(); } }; -} } // eosio::chain +using transaction_metadata_ptr = std::shared_ptr; -FC_REFLECT( eosio::chain::transaction_metadata, (raw_trx)(signing_keys)(id)(region_id)(cycle_index)(shard_index)(billable_packed_size)(published)(sender)(sender_id)(is_implicit)) +} } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/transaction_trace.hpp b/libraries/chain/include/eosio/chain/transaction_trace.hpp deleted file mode 100644 index 892690cb24b..00000000000 --- a/libraries/chain/include/eosio/chain/transaction_trace.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include -#include -#include - -#include - -namespace eosio { namespace chain { - - struct data_access_info { - enum access_type { - read = 0, ///< scope was read by this action - write = 1, ///< scope was (potentially) written to by this action - }; - - access_type type; - account_name code; - scope_name scope; - - uint64_t sequence; - }; - - struct action_trace { - account_name receiver; - bool context_free; - uint64_t cpu_usage; - action act; - string console; - vector data_access; - uint32_t auths_used; - - fc::microseconds _profiling_us = fc::microseconds(0); - }; - - struct transaction_trace : transaction_receipt { - using transaction_receipt::transaction_receipt; - - vector action_traces; - vector> deferred_transaction_requests; - - flat_set read_locks; - flat_set write_locks; - - uint64_t cpu_usage; - uint64_t net_usage; - - optional packed_trx_digest; - uint64_t region_id; - uint64_t cycle_index; - uint64_t shard_index; - - fc::microseconds _profiling_us = fc::microseconds(0); - fc::microseconds _setup_profiling_us = fc::microseconds(0); - }; -} } // eosio::chain - -FC_REFLECT_ENUM( eosio::chain::data_access_info::access_type, (read)(write)) -FC_REFLECT( eosio::chain::data_access_info, (type)(code)(scope)(sequence)) -FC_REFLECT( eosio::chain::action_trace, (receiver)(context_free)(cpu_usage)(act)(console)(data_access)(_profiling_us) ) -FC_REFLECT_ENUM( eosio::chain::transaction_receipt::status_enum, (executed)(soft_fail)(hard_fail)(delayed) ) -FC_REFLECT_DERIVED( eosio::chain::transaction_trace, (eosio::chain::transaction_receipt), (action_traces)(deferred_transaction_requests)(read_locks)(write_locks)(cpu_usage)(net_usage)(packed_trx_digest)(region_id)(cycle_index)(shard_index)(_profiling_us)(_setup_profiling_us) ) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index afac5280039..b52ea0583c2 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -115,6 +115,7 @@ namespace eosio { namespace chain { { null_object_type, account_object_type, + account_sequence_object_type, permission_object_type, permission_usage_object_type, permission_link_object_type, @@ -125,7 +126,6 @@ namespace eosio { namespace chain { index256_object_type, index_double_object_type, index_long_double_object_type, - action_permission_object_type, global_property_object_type, dynamic_global_property_object_type, block_summary_object_type, @@ -148,6 +148,8 @@ namespace eosio { namespace chain { resource_usage_object_type, resource_limits_state_object_type, resource_limits_config_object_type, + account_history_object_type, + action_history_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -168,12 +170,20 @@ namespace eosio { namespace chain { using bytes = vector; + /** + * Extentions are prefixed with type and are a buffer that can be + * interpreted by code that is aware and ignored by unaware code. + */ + typedef vector>> extensions_type; + + } } // eosio::chain FC_REFLECT_ENUM(eosio::chain::object_type, (null_object_type) (account_object_type) + (account_sequence_object_type) (permission_object_type) (permission_usage_object_type) (permission_link_object_type) @@ -184,7 +194,6 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (index256_object_type) (index_double_object_type) (index_long_double_object_type) - (action_permission_object_type) (global_property_object_type) (dynamic_global_property_object_type) (block_summary_object_type) @@ -207,6 +216,8 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (resource_usage_object_type) (resource_limits_state_object_type) (resource_limits_config_object_type) + (account_history_object_type) + (action_history_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT( eosio::chain::void_t, ) diff --git a/libraries/chain/include/eosio/chain/wasm_eosio_binary_ops.hpp b/libraries/chain/include/eosio/chain/wasm_eosio_binary_ops.hpp index c6e2c9ecbbb..c35982a9f5f 100644 --- a/libraries/chain/include/eosio/chain/wasm_eosio_binary_ops.hpp +++ b/libraries/chain/include/eosio/chain/wasm_eosio_binary_ops.hpp @@ -21,6 +21,33 @@ namespace eosio { namespace chain { namespace wasm_ops { +class instruction_stream { + public: + instruction_stream(size_t size) : idx(0) { + data.resize(size); + } + void operator<< (const char c) { + if (idx >= data.size()) + data.resize(data.size()*2); + data[idx++] = static_cast(c); + } + void set(size_t i, const char* arr) { + if (i+idx >= data.size()) + data.resize(data.size()*2+i); + memcpy((char*)&data[idx], arr, i); + idx += i; + } + size_t get_index() { return idx; } + std::vector get() { + std::vector ret = data; + ret.resize(idx); + return ret; + } +// private: + size_t idx; + std::vector data; +}; + // forward declaration struct instr; using namespace fc; @@ -73,40 +100,42 @@ inline std::string to_string( branchtabletype field ) { return std::string("branchtabletype : ")+std::to_string(field.target_depth)+std::string(", ")+std::to_string(field.table_index); } -inline std::vector pack( uint32_t field ) { - return { U8(field), U8(field >> 8), U8(field >> 16), U8(field >> 24) }; +inline void pack( instruction_stream* stream, uint32_t field ) { + const char packed[] = { char(field), char(field >> 8), char(field >> 16), char(field >> 24) }; + stream->set(sizeof(packed), packed); } -inline std::vector pack( uint64_t field ) { - return { U8(field), U8(field >> 8), U8(field >> 16), U8(field >> 24), - U8(field >> 32), U8(field >> 40), U8(field >> 48), U8(field >> 56) - }; +inline void pack( instruction_stream* stream, uint64_t field ) { + const char packed[] = { char(field), char(field >> 8), char(field >> 16), char(field >> 24), + char(field >> 32), char(field >> 40), char(field >> 48), char(field >> 56) }; + stream->set(sizeof(packed), packed); } -inline std::vector pack( blocktype field ) { - return { U8(field.result) }; +inline void pack( instruction_stream* stream, blocktype field ) { + const char packed[] = { char(field.result) }; + stream->set(sizeof(packed), packed); } -inline std::vector pack( memoryoptype field ) { - return { U8(field.end) }; +inline void pack( instruction_stream* stream, memoryoptype field ) { + const char packed[] = { char(field.end) }; + stream->set(sizeof(packed), packed); } -inline std::vector pack( memarg field ) { - return { U8(field.a), U8(field.a >> 8), U8(field.a >> 16), U8(field.a >> 24), - U8(field.o), U8(field.o >> 8), U8(field.o >> 16), U8(field.o >> 24), - }; +inline void pack( instruction_stream* stream, memarg field ) { + const char packed[] = { char(field.a), char(field.a >> 8), char(field.a >> 16), char(field.a >> 24), + char(field.o), char(field.o >> 8), char(field.o >> 16), char(field.o >> 24)}; + stream->set(sizeof(packed), packed); } -inline std::vector pack( branchtabletype field ) { - return { U8(field.target_depth), U8(field.target_depth >> 8), U8(field.target_depth >> 16), U8(field.target_depth >> 24), - U8(field.target_depth >> 32), U8(field.target_depth >> 40), U8(field.target_depth >> 48), U8(field.target_depth >> 56), - U8(field.table_index), U8(field.table_index >> 8), U8(field.table_index >> 16), U8(field.table_index >> 24), - U8(field.table_index >> 32), U8(field.table_index >> 40), U8(field.table_index >> 48), U8(field.table_index >> 56) - - }; +inline void pack( instruction_stream* stream, branchtabletype field ) { + const char packed[] = { char(field.target_depth), char(field.target_depth >> 8), char(field.target_depth >> 16), char(field.target_depth >> 24), + char(field.target_depth >> 32), char(field.target_depth >> 40), char(field.target_depth >> 48), char(field.target_depth >> 56), + char(field.table_index), char(field.table_index >> 8), char(field.table_index >> 16), char(field.table_index >> 24), + char(field.table_index >> 32), char(field.table_index >> 40), char(field.table_index >> 48), char(field.table_index >> 56) }; + stream->set(sizeof(packed), packed); } template struct field_specific_params { static constexpr int skip_ahead = sizeof(uint16_t) + sizeof(Field); static auto unpack( char* opcode, Field& f ) { f = *reinterpret_cast(opcode); } - static auto pack(Field& f) { return eosio::chain::wasm_ops::pack(f); } + static void pack(instruction_stream* stream, Field& f) { return eosio::chain::wasm_ops::pack(stream, f); } static auto to_string(Field& f) { return std::string(" ")+ eosio::chain::wasm_ops::to_string(f); } }; @@ -114,7 +143,7 @@ template <> struct field_specific_params { static constexpr int skip_ahead = sizeof(uint16_t); static auto unpack( char* opcode, voidtype& f ) {} - static auto pack(voidtype& f) { return std::vector{}; } + static void pack(instruction_stream* stream, voidtype& f) {} static auto to_string(voidtype& f) { return ""; } }; @@ -128,11 +157,9 @@ struct OP final : instr_base { void unpack( char* opcode ) override { \ field_specific_params::unpack( opcode, field ); \ } \ - std::vector pack() override { \ - std::vector output = { U8(code), 0 }; \ - std::vector field_pack = field_specific_params::pack( field ); \ - output.insert( output.end(), field_pack.begin(), field_pack.end() ); \ - return output; \ + void pack(instruction_stream* stream) override { \ + stream->set(2, (const char*)&code); \ + field_specific_params::pack( stream, field ); \ } \ std::string to_string() override { \ return std::string(BOOST_PP_STRINGIZE(OP))+field_specific_params::to_string( field ); \ @@ -495,10 +522,10 @@ enum code { }; // code struct visitor_arg { - IR::Module* module; - std::vector* new_code; - IR::FunctionDef* function_def; - uint32_t code_index; + IR::Module* module; + instruction_stream* new_code; + IR::FunctionDef* function_def; + size_t start_index; }; struct instr { @@ -507,7 +534,9 @@ struct instr { virtual void visit( visitor_arg&& arg ) = 0; virtual int skip_ahead() = 0; virtual void unpack( char* opcode ) = 0; - virtual std::vector pack() = 0; + virtual void pack( instruction_stream* stream ) = 0; + virtual bool is_kill() = 0; + virtual bool is_post() = 0; }; // base injector and utility classes for encoding if we should reflect the instr to the new code block @@ -527,40 +556,14 @@ template struct propagate_post_injection { static constexpr bool value = Mutator::post; }; - -template -struct base_mutator; - -template -struct base_mutator { - static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - } -}; - -template<> -struct base_mutator { - static void accept(wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - std::vector self_code = inst->pack(); - arg.new_code->insert( arg.new_code->begin(), self_code.begin(), self_code.end() ); - } -}; - -template<> -struct base_mutator { - static void accept(wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - std::vector self_code = inst->pack(); - arg.new_code->insert( arg.new_code->end(), self_code.begin(), self_code.end() ); - } -}; - template struct instr_base : instr { + bool is_post() override { return propagate_post_injection::value; } + bool is_kill() override { return propagate_should_kill::value; } virtual void visit( visitor_arg&& arg ) override { for ( auto m : { Mutators::accept... } ) { m(this, arg); } - base_mutator::value, - propagate_post_injection::value>::accept( this, arg ); } }; diff --git a/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp b/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp index f2281b8e442..ed7df9b0325 100644 --- a/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp +++ b/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include -#include #include +#include +#include #include #include "IR/Module.h" #include "IR/Operators.h" @@ -74,19 +76,19 @@ namespace eosio { namespace chain { namespace wasm_injections { // prepend to the head of the imports module.functions.imports.insert( module.functions.imports.begin()+(registered_injected.size()-1), new_import.begin(), new_import.end() ); injected_index_mapping.emplace( index, actual_index ); + // shift all exported functions by 1 - bool have_updated_start = false; for ( int i=0; i < module.exports.size(); i++ ) { if ( module.exports[i].kind == IR::ObjectKind::function ) { - // update the start function - if ( !have_updated_start && module.exports[i].index == module.startFunctionIndex ) { - module.startFunctionIndex++; - have_updated_start = true; - } module.exports[i].index++; } } + // update the start function + if ( module.startFunctionIndex != UINTPTR_MAX ) { + module.startFunctionIndex++; + } + // shift all table entries for call indirect for(TableSegment& ts : module.tableSegments) { for(auto& idx : ts.indices) @@ -157,38 +159,105 @@ namespace eosio { namespace chain { namespace wasm_injections { struct instruction_counter { static constexpr bool kills = false; static constexpr bool post = false; - static void init() { icnt = 0; } + static void init() { + icnt=0; + tcnt=0; + bcnt=0; + while ( !fcnts.empty() ) + fcnts.pop(); + } static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - // maybe change this later to variable weighting for different instruction types icnt++; + tcnt++; + } + static uint32_t icnt; /* instructions so far */ + static uint32_t tcnt; /* total instructions */ + static uint32_t bcnt; /* total instructions from block types */ + static std::queue fcnts; + }; + + struct checktime_block_type { + static constexpr bool kills = false; + static constexpr bool post = false; +#define CLEAR(x) \ + while( !x.empty() ) \ + x.pop() + + static void init() { + CLEAR(block_stack); + CLEAR(type_stack); + CLEAR(orderings); + CLEAR(bcnt_tables); } - static uint32_t icnt; +#undef CLEAR + static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { + instruction_counter::icnt = 0; + block_stack.push(arg.start_index); + orderings.back().push_back(arg.start_index); + bcnt_tables.back().emplace(arg.start_index, 0); + type_stack.push(inst->get_code() == wasm_ops::loop_code); + } + + static std::stack block_stack; + static std::stack type_stack; /* this might capture more than if a block is a loop in the future */ + static std::queue> orderings; /* record the order in which we found the blocks */ + static std::queue> bcnt_tables; /* table for each blocks instruction count */ }; - struct checktime_injector { + struct checktime_end { + static constexpr bool kills = false; + static constexpr bool post = false; + static void init() {} + static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { + if ( checktime_block_type::type_stack.empty() ) + return; + if ( !checktime_block_type::type_stack.top() ) { // empty or is not a loop + checktime_block_type::block_stack.pop(); + checktime_block_type::type_stack.pop(); + return; + } + size_t inst_idx = checktime_block_type::block_stack.top(); + checktime_block_type::bcnt_tables.back()[inst_idx] = instruction_counter::icnt; + instruction_counter::bcnt += instruction_counter::icnt; + instruction_counter::icnt = 0; + checktime_block_type::block_stack.pop(); + checktime_block_type::type_stack.pop(); + } + }; + + struct checktime_function_end { + static constexpr bool kills = false; + static constexpr bool post = false; + static void init() { fcnt = 0; } + static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { + size_t inst_idx = checktime_block_type::block_stack.top(); + fcnt = instruction_counter::tcnt - instruction_counter::bcnt; + } + static size_t fcnt; + }; + + struct checktime_injection { static constexpr bool kills = false; static constexpr bool post = true; - static void init() { checktime_idx = -1; } + static void init() { + idx = 0; + chktm_idx = 0; + } static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - // first add the import for checktime - injector_utils::add_import( *(arg.module), u8"checktime", checktime_idx ); + auto mapped_index = injector_utils::injected_index_mapping.find(chktm_idx); wasm_ops::op_types<>::i32_const_t cnt; - cnt.field = instruction_counter::icnt; - + cnt.field = checktime_block_type::bcnt_tables.front()[checktime_block_type::orderings.front()[idx++]]; wasm_ops::op_types<>::call_t chktm; - chktm.field = checktime_idx; - instruction_counter::icnt = 0; - - // pack the ops for insertion - std::vector injected = cnt.pack(); - std::vector tmp = chktm.pack(); - injected.insert( injected.end(), tmp.begin(), tmp.end() ); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + chktm.field = mapped_index->second; + cnt.pack(arg.new_code); + chktm.pack(arg.new_code); } - static int32_t checktime_idx; + + static int32_t idx; + static int32_t chktm_idx; }; - + struct fix_call_index { static constexpr bool kills = false; static constexpr bool post = false; @@ -240,11 +309,8 @@ namespace eosio { namespace chain { namespace wasm_injections { set_global_inst.field = global_idx; const_inst.field = -1; -#define INSERT_INJECTED(X) \ - tmp = X.pack(); \ - injected.insert( injected.end(), tmp.begin(), tmp.end() ) - std::vector injected; - std::vector tmp; +#define INSERT_INJECTED(X) \ + X.pack(arg.new_code); \ INSERT_INJECTED(get_global_inst); INSERT_INJECTED(eqz_inst); @@ -260,13 +326,12 @@ namespace eosio { namespace chain { namespace wasm_injections { /* print the correct call type */ if ( inst->get_code() == wasm_ops::call_code ) { wasm_ops::op_types<>::call_t* call_inst = reinterpret_cast::call_t*>(inst); - tmp = call_inst->pack(); + call_inst->pack(arg.new_code); } else { wasm_ops::op_types<>::call_indirect_t* call_inst = reinterpret_cast::call_indirect_t*>(inst); - tmp = call_inst->pack(); + call_inst->pack(arg.new_code); } - injected.insert( injected.end(), tmp.begin(), tmp.end() ); const_inst.field = 1; INSERT_INJECTED(get_global_inst); @@ -275,7 +340,6 @@ namespace eosio { namespace chain { namespace wasm_injections { INSERT_INJECTED(set_global_inst); #undef INSERT_INJECTED - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); } }; @@ -414,8 +478,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; @@ -429,8 +492,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; @@ -444,8 +506,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -458,8 +519,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f64op; f64op.field = idx; - std::vector injected = f64op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f64op.pack(arg.new_code); } }; @@ -473,8 +533,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f64op; f64op.field = idx; - std::vector injected = f64op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f64op.pack(arg.new_code); } }; @@ -488,8 +547,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f64op; f64op.field = idx; - std::vector injected = f64op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f64op.pack(arg.new_code); } }; @@ -503,8 +561,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -517,8 +574,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -531,8 +587,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -545,8 +600,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -559,8 +613,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -573,8 +626,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f32op; f32op.field = idx; - std::vector injected = f32op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32op.pack(arg.new_code); } }; template @@ -587,8 +639,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f64op; f64op.field = idx; - std::vector injected = f64op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f64op.pack(arg.new_code); } }; template @@ -601,8 +652,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), inject_which_op(Opcode), idx ); wasm_ops::op_types<>::call_t f64op; f64op.field = idx; - std::vector injected = f64op.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f64op.pack(arg.new_code); } }; @@ -615,8 +665,7 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), u8"_eosio_f32_promote", idx ); wasm_ops::op_types<>::call_t f32promote; f32promote.field = idx; - std::vector injected = f32promote.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32promote.pack(arg.new_code); } }; @@ -629,18 +678,17 @@ namespace eosio { namespace chain { namespace wasm_injections { injector_utils::add_import( *(arg.module), u8"_eosio_f64_demote", idx ); wasm_ops::op_types<>::call_t f32promote; f32promote.field = idx; - std::vector injected = f32promote.pack(); - arg.new_code->insert( arg.new_code->end(), injected.begin(), injected.end() ); + f32promote.pack(arg.new_code); } }; struct pre_op_injectors : wasm_ops::op_types { - using block_t = wasm_ops::block ; - using loop_t = wasm_ops::loop ; + using block_t = wasm_ops::block ; + using loop_t = wasm_ops::loop ; using if__t = wasm_ops::if_ ; using else__t = wasm_ops::else_ ; - using end_t = wasm_ops::end ; + using end_t = wasm_ops::end ; using unreachable_t = wasm_ops::unreachable ; using br_t = wasm_ops::br ; using br_if_t = wasm_ops::br_if ; @@ -750,7 +798,7 @@ namespace eosio { namespace chain { namespace wasm_injections { using i64_shr_u_t = wasm_ops::i64_shr_u ; using i64_rotl_t = wasm_ops::i64_rotl ; using i64_rotr_t = wasm_ops::i64_rotr ; - + // float binops using f32_add_t = wasm_ops::f32_add >; using f32_sub_t = wasm_ops::f32_sub >; @@ -835,6 +883,7 @@ namespace eosio { namespace chain { namespace wasm_injections { struct post_op_injectors : wasm_ops::op_types { + using loop_t = wasm_ops::loop ; using call_t = wasm_ops::call ; }; @@ -863,33 +912,70 @@ namespace eosio { namespace chain { namespace wasm_injections { // initialize static fields of injectors injector_utils::init( mod ); instruction_counter::init(); - checktime_injector::init(); + checktime_injection::init(); + checktime_block_type::init(); + checktime_function_end::init(); call_depth_check::init(); } void inject() { _module_injectors.inject( *_module ); + // inject checktime first + injector_utils::add_import( *_module, u8"checktime", checktime_injection::chktm_idx ); + for ( auto& fd : _module->functions.defs ) { wasm_ops::EOSIO_OperatorDecoderStream pre_decoder(fd.code); - std::vector new_code; + wasm_ops::instruction_stream pre_code(fd.code.size()*2); + checktime_block_type::orderings.emplace(); + checktime_block_type::bcnt_tables.emplace(); + instruction_counter::icnt = 0; + instruction_counter::tcnt = 0; + instruction_counter::bcnt = 0; while ( pre_decoder ) { - std::vector new_inst; auto op = pre_decoder.decodeOp(); - op->visit( { _module, &new_inst, &fd, pre_decoder.index() } ); - new_code.insert( new_code.end(), new_inst.begin(), new_inst.end() ); + if (op->is_post()) { + op->pack(&pre_code); + op->visit( { _module, &pre_code, &fd, pre_decoder.index() } ); + } + else { + op->visit( { _module, &pre_code, &fd, pre_decoder.index() } ); + if (!(op->is_kill())) + op->pack(&pre_code); + } } - fd.code = new_code; + instruction_counter::fcnts.push(instruction_counter::tcnt - instruction_counter::bcnt); + fd.code = pre_code.get(); } for ( auto& fd : _module->functions.defs ) { wasm_ops::EOSIO_OperatorDecoderStream post_decoder(fd.code); - std::vector post_code; + wasm_ops::instruction_stream post_code(fd.code.size()*2); + bool is_start = true; while ( post_decoder ) { - std::vector new_inst; auto op = post_decoder.decodeOp(); - op->visit( { _module, &new_inst, &fd, post_decoder.index() } ); - post_code.insert( post_code.end(), new_inst.begin(), new_inst.end() ); + if ( is_start ) { + is_start = false; + wasm_ops::op_types<>::i32_const_t cnt; + cnt.field = instruction_counter::fcnts.front(); + wasm_ops::op_types<>::call_t chktm; + chktm.field = injector_utils::injected_index_mapping.find(checktime_injection::chktm_idx)->second; + cnt.pack(&post_code); + chktm.pack(&post_code); + } + if (op->is_post()) { + op->pack(&post_code); + op->visit( { _module, &post_code, &fd, post_decoder.index() } ); + } + else { + op->visit( { _module, &post_code, &fd, post_decoder.index() } ); + if (!(op->is_kill())) + op->pack(&post_code); + } } - fd.code = post_code; + fd.code = post_code.get(); + instruction_counter::fcnts.pop(); + checktime_block_type::orderings.pop(); + checktime_block_type::bcnt_tables.pop(); + checktime_injection::idx = 0; } } private: diff --git a/libraries/chain/include/eosio/chain/wasm_eosio_validation.hpp b/libraries/chain/include/eosio/chain/wasm_eosio_validation.hpp index cebe1331be5..c7707da7400 100644 --- a/libraries/chain/include/eosio/chain/wasm_eosio_validation.hpp +++ b/libraries/chain/include/eosio/chain/wasm_eosio_validation.hpp @@ -319,7 +319,7 @@ namespace eosio { namespace chain { namespace wasm_validations { for ( auto& fd : _module->functions.defs ) { wasm_ops::EOSIO_OperatorDecoderStream decoder(fd.code); while ( decoder ) { - std::vector new_code; + wasm_ops::instruction_stream new_code(0); auto op = decoder.decodeOp(); op->visit( { _module, &new_code, &fd, decoder.index() } ); } diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index ef1040decd9..8fada814098 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -61,7 +61,7 @@ namespace eosio { namespace chain { static void validate(const bytes& code); //Calls apply or error on a given code - void apply(const digest_type& code_id, const shared_vector& code, apply_context& context); + void apply(const digest_type& code_id, const shared_string& code, apply_context& context); private: unique_ptr my; @@ -73,3 +73,5 @@ namespace eosio { namespace chain { namespace eosio{ namespace chain { std::istream& operator>>(std::istream& in, wasm_interface::vm_type& runtime); }} + +FC_REFLECT_ENUM( eosio::chain::wasm_interface::vm_type, (wavm)(binaryen) ) diff --git a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp index a8252bd1d1e..1af3942604f 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp @@ -47,7 +47,7 @@ namespace eosio { namespace chain { return mem_image; } - std::unique_ptr& get_instantiated_module(const digest_type& code_id, const shared_vector& code) { + std::unique_ptr& get_instantiated_module(const digest_type& code_id, const shared_string& code) { auto it = instantiation_cache.find(code_id); if(it == instantiation_cache.end()) { IR::Module module; @@ -57,6 +57,7 @@ namespace eosio { namespace chain { } catch(Serialization::FatalSerializationException& e) { EOS_ASSERT(false, wasm_serialization_error, e.message.c_str()); } + wasm_injections::wasm_binary_injection injector(module); injector.inject(); @@ -68,7 +69,6 @@ namespace eosio { namespace chain { } catch(Serialization::FatalSerializationException& e) { EOS_ASSERT(false, wasm_serialization_error, e.message.c_str()); } - it = instantiation_cache.emplace(code_id, runtime_interface->instantiate_module((const char*)bytes.data(), bytes.size(), parse_initial_memory(module))).first; } return it->second; diff --git a/libraries/chain/include/eosio/chain/webassembly/binaryen.hpp b/libraries/chain/include/eosio/chain/webassembly/binaryen.hpp index 9942e8d2b3b..0f38c66fae9 100644 --- a/libraries/chain/include/eosio/chain/webassembly/binaryen.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/binaryen.hpp @@ -76,12 +76,12 @@ struct interpreter_interface : ModuleInstance::ExternalInterface { FC_THROW_EXCEPTION(wasm_execution_error, why); } - void assert_memory_is_accessible(uint32_t offset, size_t size) { - if (offset + size > current_memory_size || offset + size < offset) - FC_THROW_EXCEPTION(wasm_execution_error, "access violation"); + void assert_memory_is_accessible(uint32_t offset, uint32_t size) { + EOS_ASSERT(offset + size <= current_memory_size && offset + size >= offset, + wasm_execution_error, "access violation"); } - char* get_validated_pointer(uint32_t offset, size_t size) { + char* get_validated_pointer(uint32_t offset, uint32_t size) { assert_memory_is_accessible(offset, size); return memory.data + offset; } @@ -155,9 +155,9 @@ class binaryen_runtime : public eosio::chain::wasm_runtime_interface { * @tparam T */ template -inline array_ptr array_ptr_impl (interpreter_interface* interface, uint32_t ptr, size_t length) +inline array_ptr array_ptr_impl (interpreter_interface* interface, uint32_t ptr, uint32_t length) { - return array_ptr((T*)(interface->get_validated_pointer(ptr, length * sizeof(T)))); + return array_ptr((T*)(interface->get_validated_pointer(ptr, length * (uint32_t)sizeof(T)))); } /** @@ -348,13 +348,40 @@ struct intrinsic_invoker_impl, size_t, Inputs...>> using next_step = intrinsic_invoker_impl>; using then_type = Ret(*)(interpreter_interface*, array_ptr, size_t, Inputs..., LiteralList&, int); - template - static Ret translate_one(interpreter_interface* interface, Inputs... rest, LiteralList& args, int offset) { + template + static auto translate_one(interpreter_interface* interface, Inputs... rest, LiteralList& args, int offset) -> std::enable_if_t::value, Ret> { + static_assert(!std::is_pointer::value, "Currently don't support array of pointers"); uint32_t ptr = args.at(offset - 1).geti32(); size_t length = args.at(offset).geti32(); - return Then(interface, array_ptr_impl(interface, ptr, length), length, rest..., args, offset - 2); + T* base = array_ptr_impl(interface, ptr, length); + if ( reinterpret_cast(base) % alignof(T) != 0 ) { + wlog( "misaligned array of const values" ); + std::remove_const_t copy[length]; + T* copy_ptr = ©[0]; + memcpy( (void*)copy_ptr, (void*)base, length * sizeof(T) ); + return Then(interface, static_cast>(copy_ptr), length, rest..., args, offset - 2); + } + return Then(interface, static_cast>(base), length, rest..., args, offset - 2); }; + template + static auto translate_one(interpreter_interface* interface, Inputs... rest, LiteralList& args, int offset) -> std::enable_if_t::value, Ret> { + static_assert(!std::is_pointer::value, "Currently don't support array of pointers"); + uint32_t ptr = args.at(offset - 1).geti32(); + size_t length = args.at(offset).geti32(); + T* base = array_ptr_impl(interface, ptr, length); + if ( reinterpret_cast(base) % alignof(T) != 0 ) { + wlog( "misaligned array of values" ); + std::remove_const_t copy[length]; + T* copy_ptr = ©[0]; + memcpy( (void*)copy_ptr, (void*)base, length * sizeof(T) ); + Ret ret = Then(interface, static_cast>(copy_ptr), length, rest..., args, offset - 2); + memcpy( (void*)base, (void*)copy_ptr, length * sizeof(T) ); + return ret; + } + return Then(interface, static_cast>(base), length, rest..., args, offset - 2); + }; + template static const auto fn() { return next_step::template fn>(); @@ -404,7 +431,7 @@ struct intrinsic_invoker_impl, array_ptr, size_t uint32_t ptr_t = args.at(offset - 2).geti32(); uint32_t ptr_u = args.at(offset - 1).geti32(); size_t length = args.at(offset).geti32(); - assert(sizeof(T) == sizeof(U)); + static_assert(std::is_same, char>::value && std::is_same, char>::value, "Currently only support array of (const)chars"); return Then(interface, array_ptr_impl(interface, ptr_t, length), array_ptr_impl(interface, ptr_u, length), length, args, offset - 3); }; diff --git a/libraries/chain/include/eosio/chain/webassembly/common.hpp b/libraries/chain/include/eosio/chain/webassembly/common.hpp index 831b99ee8aa..84211d9b638 100644 --- a/libraries/chain/include/eosio/chain/webassembly/common.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/common.hpp @@ -60,7 +60,7 @@ namespace eosio { namespace chain { } T *value; - }; + }; /** * class to represent an in-wasm-memory char array that must be null terminated diff --git a/libraries/chain/include/eosio/chain/webassembly/wavm.hpp b/libraries/chain/include/eosio/chain/webassembly/wavm.hpp index 2091aae2736..bad0fe02ce2 100644 --- a/libraries/chain/include/eosio/chain/webassembly/wavm.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/wavm.hpp @@ -53,6 +53,8 @@ inline array_ptr array_ptr_impl (running_instance_context& ctx, U32 ptr, size size_t mem_total = IR::numBytesPerPage * Runtime::getMemoryNumPages(mem); if (ptr >= mem_total || length > (mem_total - ptr) / sizeof(T)) Runtime::causeException(Exception::Cause::accessViolation); + + T* ret_ptr = (T*)(getMemoryBaseAddress(mem) + ptr); return array_ptr((T*)(getMemoryBaseAddress(mem) + ptr)); } @@ -373,10 +375,36 @@ struct intrinsic_invoker_impl, size_t, Inputs...>, using next_step = intrinsic_invoker_impl, std::tuple>; using then_type = Ret(*)(running_instance_context&, array_ptr, size_t, Inputs..., Translated...); - template - static Ret translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr, I32 size) { + template + static auto translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr, I32 size) -> std::enable_if_t::value, Ret> { + static_assert(!std::is_pointer::value, "Currently don't support array of pointers"); + const auto length = size_t(size); + T* base = array_ptr_impl(ctx, ptr, length); + if ( reinterpret_cast(base) % alignof(T) != 0 ) { + wlog( "misaligned array of const values" ); + std::remove_const_t copy[length]; + T* copy_ptr = ©[0]; + memcpy( (void*)copy_ptr, (void*)base, length * sizeof(T) ); + return Then(ctx, static_cast>(copy_ptr), length, rest..., translated...); + } + return Then(ctx, static_cast>(base), length, rest..., translated...); + }; + + template + static auto translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr, I32 size) -> std::enable_if_t::value, Ret> { + static_assert(!std::is_pointer::value, "Currently don't support array of pointers"); const auto length = size_t(size); - return Then(ctx, array_ptr_impl(ctx, ptr, length), length, rest..., translated...); + T* base = array_ptr_impl(ctx, ptr, length); + if ( reinterpret_cast(base) % alignof(T) != 0 ) { + wlog( "misaligned array of values" ); + std::remove_const_t copy[length]; + T* copy_ptr = ©[0]; + memcpy( (void*)copy_ptr, (void*)base, length * sizeof(T) ); + Ret ret = Then(ctx, static_cast>(copy_ptr), length, rest..., translated...); + memcpy( (void*)base, (void*)copy_ptr, length * sizeof(T) ); + return ret; + } + return Then(ctx, static_cast>(base), length, rest..., translated...); }; template @@ -426,8 +454,8 @@ struct intrinsic_invoker_impl, array_ptr, size_t template static Ret translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr_t, I32 ptr_u, I32 size) { + static_assert(std::is_same, char>::value && std::is_same, char>::value, "Currently only support array of (const)chars"); const auto length = size_t(size); - assert(sizeof(T)==sizeof(U)); return Then(ctx, array_ptr_impl(ctx, ptr_t, length), array_ptr_impl(ctx, ptr_u, length), length, rest..., translated...); }; @@ -535,32 +563,6 @@ struct intrinsic_invoker_impl, std::tupl } }; -/** - * Specialization for transcribing a reference to a fc::time_point_sec which can be passed as a native value - * This type transcribes into a native type which is loaded by value into a - * variable on the stack and then passed by reference to the intrinsic. - * - * @tparam Ret - the return type of the native method - * @tparam Inputs - the remaining native parameters to transcribe - * @tparam Translated - the list of transcribed wasm parameters - */ -template -struct intrinsic_invoker_impl, std::tuple> { - using next_step = intrinsic_invoker_impl, std::tuple >>; - using then_type = Ret (*)(running_instance_context&, const fc::time_point_sec&, Inputs..., Translated...); - - template - static Ret translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, native_to_wasm_t wasm_value) { - auto value = fc::time_point_sec(wasm_value); - return Then(ctx, value, rest..., translated...); - } - - template - static const auto fn() { - return next_step::template fn>(); - } -}; - /** * Specialization for transcribing a reference type in the native method signature * This type transcribes into an int32 pointer checks the validity of that memory diff --git a/libraries/chain/old_fork_database.cpp b/libraries/chain/old_fork_database.cpp new file mode 100644 index 00000000000..82813a2c456 --- /dev/null +++ b/libraries/chain/old_fork_database.cpp @@ -0,0 +1,311 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include +#include + +namespace eosio { namespace chain { +fork_database::fork_database() +{ +} +void fork_database::reset() +{ + _head.reset(); + _index.clear(); +} + +void fork_database::pop_block() +{ + FC_ASSERT( _head, "no blocks to pop" ); + auto prev = _head->prev.lock(); + FC_ASSERT( prev, "poping block would leave head block null" ); + _head = prev; +} + +void fork_database::start_block(signed_block b) +{ + auto item = std::make_shared(std::move(b)); + _index.insert(item); + _head = item; +} + +/** + * Pushes the block into the fork database and caches it if it doesn't link + * + */ +shared_ptr fork_database::push_block( const signed_block& b ) +{ + auto item = push_block_header( b ); + item->data = std::make_shared(b); + return item; +} + +shared_ptr fork_database::push_block_header( const signed_block_header& b ) { try { + const auto& by_id_idx = _index.get(); + auto existing = by_id_idx.find( b.id() ); + + if( existing != by_id_idx.end() ) return *existing; + + auto previous = by_id_idx.find( b.previous ); + if( previous == by_id_idx.end() ) { + wlog( "Pushing block to fork database that failed to link: ${id}, ${num}", ("id",b.id())("num",b.block_num()) ); + wlog( "Head: ${num}, ${id}", ("num",_head->num)("id",_head->id) ); + EOS_ASSERT( previous != by_id_idx.end(), unlinkable_block_exception, "block does not link to known chain"); + } + + EOS_ASSERT( !(*previous)->invalid, unlinkable_block_exception, "unable to link to a known invalid block" ); + + auto next = std::make_shared( **previous ); + next->confirmations.clear(); + next->data.reset(); + + next->prev = *previous; + next->header = b; + next->id = b.id(); + next->invalid = false; + next->num = (*previous)->num + 1; + next->last_block_per_producer[b.producer] = next->num; + + FC_ASSERT( b.timestamp > (*previous)->header.timestamp, "block must advance time" ); + + // next->last_irreversible_block = next->calculate_last_irr + + if( next->last_irreversible_block >= next->pending_schedule_block ) + next->active_schedule = std::move(next->pending_schedule ); + + if( b.new_producers ) { + FC_ASSERT( !next->pending_schedule, "there is already an unconfirmed pending schedule" ); + next->pending_schedule = std::make_shared( *b.new_producers ); + } + + FC_ASSERT( b.schedule_version == next->active_schedule->version, "wrong schedule version provided" ); + + auto schedule_hash = fc::sha256::hash( *next->active_schedule ); + auto signee = b.signee( schedule_hash ); + + auto num_producers = next->active_schedule->producers.size(); + vector ibb; + flat_map new_block_per_producer; + ibb.reserve( num_producers ); + + size_t offset = EOS_PERCENT(ibb.size(), config::percent_100- config::irreversible_threshold_percent); + + for( const auto& item : next->active_schedule->producers ) { + ibb.push_back( next->last_block_per_producer[item.producer_name] ); + new_block_per_producer[item.producer_name] = ibb.back(); + } + next->last_block_per_producer = move(new_block_per_producer); + + std::nth_element( ibb.begin(), ibb.begin() + offset, ibb.end() ); + next->last_irreversible_block = ibb[offset]; + + auto index = b.timestamp.slot % (num_producers * config::producer_repetitions); + index /= config::producer_repetitions; + + auto prod = next->active_schedule->producers[index]; + FC_ASSERT( prod.producer_name == b.producer, "unexpected producer specified" ); + FC_ASSERT( prod.block_signing_key == signee, "block not signed by expected key" ); + + _push_block( next ); + return _head; +} FC_CAPTURE_AND_RETHROW( (b) ) } + +void fork_database::_push_block(const item_ptr& item ) +{ + if( _head ) // make sure the block is within the range that we are caching + { + FC_ASSERT( item->num > std::max( 0, int64_t(_head->num) - (_max_size) ), + "attempting to push a block that is too old", + ("item->num",item->num)("head",_head->num)("max_size",_max_size)); + } + + if( _head && item->previous_id() != block_id_type() ) + { + auto& index = _index.get(); + auto itr = index.find(item->previous_id()); + EOS_ASSERT(itr != index.end(), unlinkable_block_exception, "block does not link to known chain"); + FC_ASSERT(!(*itr)->invalid); + + item->prev = *itr; + } + + _index.insert(item); + if( !_head ) _head = item; + else if( item->num > _head->num ) + { + uint32_t delta = item->data->timestamp.slot - _head->data->timestamp.slot; + if (delta > 1) + wlog("Number of missed blocks: ${num}", ("num", delta-1)); + _head = item; + + uint32_t min_num = _head->last_irreversible_block - 1; //_head->num - std::min( _max_size, _head->num ); +// ilog( "min block in fork DB ${n}, max_size: ${m}", ("n",min_num)("m",_max_size) ); + auto& num_idx = _index.get(); + while( num_idx.size() && (*num_idx.begin())->num < min_num ) + num_idx.erase( num_idx.begin() ); + } +} + + +void fork_database::set_max_size( uint32_t s ) +{ + _max_size = s; + if( !_head ) return; + + { /// index + auto& by_num_idx = _index.get(); + auto itr = by_num_idx.begin(); + while( itr != by_num_idx.end() ) + { + if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) + by_num_idx.erase(itr); + else + break; + itr = by_num_idx.begin(); + } + } + +/* + { /// unlinked_index + + auto& by_num_idx = _unlinked_index.get(); + auto itr = by_num_idx.begin(); + while( itr != by_num_idx.end() ) + { + if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) + by_num_idx.erase(itr); + else + break; + itr = by_num_idx.begin(); + } + } +*/ +} + +bool fork_database::is_known_block(const block_id_type& id)const +{ + auto& index = _index.get(); + auto itr = index.find(id); + if( itr != index.end() ) + return true; + return false; + // auto& unlinked_index = _unlinked_index.get(); + //auto unlinked_itr = unlinked_index.find(id); + // return unlinked_itr != unlinked_index.end(); +} + +item_ptr fork_database::fetch_block(const block_id_type& id)const +{ + auto& index = _index.get(); + auto itr = index.find(id); + if( itr != index.end() ) + return *itr; + /* + auto& unlinked_index = _unlinked_index.get(); + auto unlinked_itr = unlinked_index.find(id); + if( unlinked_itr != unlinked_index.end() ) + return *unlinked_itr; + */ + return item_ptr(); +} + +vector fork_database::fetch_block_by_number(uint32_t num)const +{ + vector result; + auto itr = _index.get().find(num); + while( itr != _index.get().end() ) + { + if( (*itr)->num == num ) + result.push_back( *itr ); + else + break; + ++itr; + } + return result; +} + +pair + fork_database::fetch_branch_from(block_id_type first, block_id_type second)const +{ try { + // This function gets a branch (i.e. vector) leading + // back to the most recent common ancestor. + pair result; + auto first_branch_itr = _index.get().find(first); + FC_ASSERT(first_branch_itr != _index.get().end()); + auto first_branch = *first_branch_itr; + + auto second_branch_itr = _index.get().find(second); + FC_ASSERT(second_branch_itr != _index.get().end()); + auto second_branch = *second_branch_itr; + + + while( first_branch->num > second_branch->num ) + { + result.first.push_back(first_branch); + first_branch = first_branch->prev.lock(); + FC_ASSERT(first_branch); + } + while( second_branch->num > first_branch->num ) + { + result.second.push_back( second_branch ); + second_branch = second_branch->prev.lock(); + FC_ASSERT(second_branch); + } + while( first_branch->data->previous != second_branch->data->previous ) + { + result.first.push_back(first_branch); + result.second.push_back(second_branch); + first_branch = first_branch->prev.lock(); + FC_ASSERT(first_branch); + second_branch = second_branch->prev.lock(); + FC_ASSERT(second_branch); + } + if( first_branch && second_branch ) + { + result.first.push_back(first_branch); + result.second.push_back(second_branch); + } + return result; +} FC_CAPTURE_AND_RETHROW( (first)(second) ) } + +void fork_database::set_head(shared_ptr h) +{ + wdump(("set head")); + _head = h; +} + +void fork_database::remove(block_id_type id) +{ + _index.get().erase(id); +} + +shared_ptr fork_database::push_confirmation( const producer_confirmation& c ) { + /* + auto b = fetch_block( c.block_id ); + if( !b->schedule ) return shared_ptr(); + + bool found = false; + for( const auto& pro : b->schedule->producers ) { + if( pro.producer_name == c.producer ){ + public_key_type pub( c.digest, c.sig ); + FC_ASSERT( pub == pro.block_signing_key, "not signed by expected key" ); + /// TODO: recover key and compare keys + found = true; break; + } + } + + FC_ASSERT( found, "Producer signed who wasn't in schedule" ); + + for( const auto& con : b->confirmations ) { + if( con == c ) return shared_ptr(); + FC_ASSERT( con.producer != c.producer, "DOUBLE SIGN FOUND" ); + } + b->confirmations.push_back(c); + return b; + */ + return shared_ptr(); +} + +} } // eosio::chain diff --git a/libraries/chain/resource_limits.cpp b/libraries/chain/resource_limits.cpp index 658577badaf..2b7a95d55b1 100644 --- a/libraries/chain/resource_limits.cpp +++ b/libraries/chain/resource_limits.cpp @@ -7,6 +7,8 @@ namespace eosio { namespace chain { namespace resource_limits { +static_assert( config::rate_limiting_precision > 0, "config::rate_limiting_precision must be positive" ); + static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) { uint64_t result = current_limit; if (average_usage > params.target ) { @@ -14,26 +16,36 @@ static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_us } else { result = result * params.expand_rate; } - return std::min(std::max(result, params.max), params.max * params.max_multiplier); } +void elastic_limit_parameters::validate()const { + // At the very least ensure parameters are not set to values that will cause divide by zero errors later on. + // Stricter checks for sensible values can be added later. + FC_ASSERT( periods > 0, "elastic limit parameter 'periods' cannot be zero" ); + FC_ASSERT( contract_rate.denominator > 0, "elastic limit parameter 'contract_rate' is not a well-defined ratio" ); + FC_ASSERT( expand_rate.denominator > 0, "elastic limit parameter 'expand_rate' is not a well-defined ratio" ); +} + + void resource_limits_state_object::update_virtual_cpu_limit( const resource_limits_config_object& cfg ) { + //idump((average_block_cpu_usage.average())); virtual_cpu_limit = update_elastic_limit(virtual_cpu_limit, average_block_cpu_usage.average(), cfg.cpu_limit_parameters); + //idump((virtual_cpu_limit)); } void resource_limits_state_object::update_virtual_net_limit( const resource_limits_config_object& cfg ) { virtual_net_limit = update_elastic_limit(virtual_net_limit, average_block_net_usage.average(), cfg.net_limit_parameters); } -void resource_limits_manager::initialize_database() { +void resource_limits_manager::add_indices() { _db.add_index(); _db.add_index(); _db.add_index(); _db.add_index(); } -void resource_limits_manager::initialize_chain() { +void resource_limits_manager::initialize_database() { const auto& config = _db.create([](resource_limits_config_object& config){ // see default settings in the declaration }); @@ -58,6 +70,8 @@ void resource_limits_manager::initialize_account(const account_name& account) { } void resource_limits_manager::set_block_parameters(const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ) { + cpu_limit_parameters.validate(); + net_limit_parameters.validate(); const auto& config = _db.get(); _db.modify(config, [&](resource_limits_config_object& c){ c.cpu_limit_parameters = cpu_limit_parameters; @@ -73,40 +87,52 @@ void resource_limits_manager::add_transaction_usage(const flat_set for( const auto& a : accounts ) { const auto& usage = _db.get( a ); - const auto& limits = _db.get( boost::make_tuple(false, a)); + int64_t unused; + int64_t net_weight; + int64_t cpu_weight; + get_account_limits( a, unused, net_weight, cpu_weight ); + _db.modify( usage, [&]( auto& bu ){ - bu.net_usage.add( net_usage, time_slot, config.net_limit_parameters.periods ); - bu.cpu_usage.add( cpu_usage, time_slot, config.cpu_limit_parameters.periods ); + bu.net_usage.add( net_usage, time_slot, config.account_net_usage_average_window ); + bu.cpu_usage.add( cpu_usage, time_slot, config.account_cpu_usage_average_window ); }); - if (limits.cpu_weight >= 0) { - uint128_t consumed_cpu_ex = usage.cpu_usage.consumed * config::rate_limiting_precision; - uint128_t capacity_cpu_ex = state.virtual_cpu_limit * config::rate_limiting_precision; + if( cpu_weight >= 0 && state.total_cpu_weight > 0 ) { + uint128_t window_size = config.account_cpu_usage_average_window; + auto virtual_network_capacity_in_window = state.virtual_cpu_limit * window_size; + auto cpu_used_in_window = (usage.cpu_usage.value_ex * window_size) / config::rate_limiting_precision; + + uint128_t user_weight = cpu_weight; + uint128_t all_user_weight = state.total_cpu_weight; + + auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight; - EOS_ASSERT( state.total_cpu_weight > 0 && (consumed_cpu_ex * state.total_cpu_weight) <= (limits.cpu_weight * capacity_cpu_ex), - tx_resource_exhausted, + EOS_ASSERT( cpu_used_in_window <= max_user_use_in_window, + tx_cpu_usage_exceeded, "authorizing account '${n}' has insufficient cpu resources for this transaction", - ("n", name(a)) - ("consumed", (double)consumed_cpu_ex/(double)config::rate_limiting_precision) - ("cpu_weight", limits.cpu_weight) - ("virtual_cpu_capacity", (double)state.virtual_cpu_limit ) - ("total_cpu_weight", state.total_cpu_weight) - ); + ("n", name(a)) + ("cpu_used_in_window",cpu_used_in_window) + ("max_user_use_in_window",max_user_use_in_window) ); } - if (limits.net_weight >= 0) { - uint128_t consumed_net_ex = usage.net_usage.consumed * config::rate_limiting_precision; - uint128_t capacity_net_ex = state.virtual_net_limit * config::rate_limiting_precision; + if( net_weight >= 0 && state.total_net_weight > 0) { - EOS_ASSERT( state.total_net_weight > 0 && (consumed_net_ex * state.total_net_weight) <= (limits.net_weight * capacity_net_ex), - tx_resource_exhausted, + uint128_t window_size = config.account_net_usage_average_window; + auto virtual_network_capacity_in_window = state.virtual_net_limit * window_size; + auto net_used_in_window = (usage.net_usage.value_ex * window_size) / config::rate_limiting_precision; + + uint128_t user_weight = net_weight; + uint128_t all_user_weight = state.total_net_weight; + + auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight; + + EOS_ASSERT( net_used_in_window <= max_user_use_in_window, + tx_net_usage_exceeded, "authorizing account '${n}' has insufficient net resources for this transaction", - ("n", name(a)) - ("consumed", (double)consumed_net_ex/(double)config::rate_limiting_precision) - ("net_weight", limits.net_weight) - ("virtual_net_capacity", (double)state.virtual_net_limit ) - ("total_net_weight", state.total_net_weight) - ); + ("n", name(a)) + ("net_used_in_window",net_used_in_window) + ("max_user_use_in_window",max_user_use_in_window) ); + } } @@ -120,46 +146,41 @@ void resource_limits_manager::add_transaction_usage(const flat_set EOS_ASSERT( state.pending_net_usage <= config.net_limit_parameters.max, block_resource_exhausted, "Block has insufficient net resources" ); } -void resource_limits_manager::add_pending_account_ram_usage( const account_name account, int64_t ram_delta ) { +void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) { if (ram_delta == 0) { return; } - const auto& usage = _db.get( account ); + const auto& usage = _db.get( account ); - EOS_ASSERT(ram_delta < 0 || UINT64_MAX - usage.pending_ram_usage >= (uint64_t)ram_delta, transaction_exception, "Ram usage delta would overflow UINT64_MAX"); - EOS_ASSERT(ram_delta > 0 || usage.pending_ram_usage >= (uint64_t)(-ram_delta), transaction_exception, "Ram usage delta would underflow UINT64_MAX"); + EOS_ASSERT( ram_delta <= 0 || UINT64_MAX - usage.ram_usage >= (uint64_t)ram_delta, transaction_exception, + "Ram usage delta would overflow UINT64_MAX"); + EOS_ASSERT(ram_delta >= 0 || usage.ram_usage >= (uint64_t)(-ram_delta), transaction_exception, + "Ram usage delta would underflow UINT64_MAX"); - _db.modify(usage, [&](resource_usage_object& o){ - o.pending_ram_usage += ram_delta; + _db.modify( usage, [&]( auto& u ) { + u.ram_usage += ram_delta; }); } -void resource_limits_manager::synchronize_account_ram_usage( ) { - auto& multi_index = _db.get_mutable_index(); - auto& by_dirty_index = multi_index.indices().get(); - - while(!by_dirty_index.empty()) { - const auto& itr = by_dirty_index.lower_bound(boost::make_tuple(true)); - if (itr == by_dirty_index.end() || itr->is_dirty() != true) { - break; - } - - const auto& limits = _db.get( boost::make_tuple(false, itr->owner)); - - if (limits.ram_bytes >= 0 && itr->pending_ram_usage > limits.ram_bytes) { - tx_resource_exhausted e(FC_LOG_MESSAGE(error, "account ${a} has insufficient ram bytes", ("a", itr->owner))); - e.append_log(FC_LOG_MESSAGE(error, "needs ${d} has ${m}", ("d",itr->pending_ram_usage)("m",limits.ram_bytes))); - throw e; - } +void resource_limits_manager::verify_account_ram_usage( const account_name account )const { + int64_t ram_bytes; int64_t net_weight; int64_t cpu_weight; + get_account_limits( account, ram_bytes, net_weight, cpu_weight ); + const auto& usage = _db.get( account ); - _db.modify(*itr, [&](resource_usage_object& o){ - o.ram_usage = o.pending_ram_usage; - }); + if( ram_bytes >= 0 ) { + EOS_ASSERT( usage.ram_usage <= ram_bytes, ram_usage_exceeded, + "account ${account} has insufficient ram bytes; needs ${available} has ${needs}", + ("account", account)("available",usage.ram_usage)("needs",ram_bytes) ); } } -void resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) { +int64_t resource_limits_manager::get_account_ram_usage( const account_name& name )const { + return _db.get( name ).ram_usage; +} + + +bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) { const auto& usage = _db.get( account ); /* * Since we need to delay these until the next resource limiting boundary, these are created in a "pending" @@ -185,13 +206,19 @@ void resource_limits_manager::set_account_limits( const account_name& account, i // update the users weights directly auto& limits = find_or_create_pending_limits(); - if (ram_bytes >= 0) { - if (limits.ram_bytes < 0 ) { + bool decreased_limit = false; + + if( ram_bytes >= 0 ) { + + decreased_limit = ( (limits.ram_bytes < 0) || (ram_bytes < limits.ram_bytes) ); + + /* + if( limits.ram_bytes < 0 ) { EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "converting unlimited account would result in overcommitment [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes)); } else { EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "attempting to release committed ram resources [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes)); } - + */ } auto old_ram_bytes = limits.ram_bytes; @@ -200,6 +227,8 @@ void resource_limits_manager::set_account_limits( const account_name& account, i pending_limits.net_weight = net_weight; pending_limits.cpu_weight = cpu_weight; }); + + return decreased_limit; } void resource_limits_manager::get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight ) const { @@ -297,42 +326,152 @@ uint64_t resource_limits_manager::get_block_net_limit() const { } int64_t resource_limits_manager::get_account_cpu_limit( const account_name& name ) const { + const auto& state = _db.get(); const auto& usage = _db.get(name); - const auto& limits = _db.get(boost::make_tuple(false, name)); - if (limits.cpu_weight < 0) { + const auto& config = _db.get(); + + int64_t unused; + int64_t cpu_weight; + get_account_limits( name, unused, unused, cpu_weight ); + + if( cpu_weight < 0 || state.total_cpu_weight == 0 ) { return -1; } + uint128_t window_size = config.account_cpu_usage_average_window; + + auto virtual_cpu_capacity_in_window = state.virtual_cpu_limit * window_size; + uint128_t user_weight = cpu_weight; + uint128_t all_user_weight = state.total_cpu_weight; + + auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight; + auto cpu_used_in_window = (usage.cpu_usage.value_ex * window_size) / config::rate_limiting_precision; + + if( max_user_use_in_window <= cpu_used_in_window ) return 0; + + return max_user_use_in_window - cpu_used_in_window; + +/* + const auto& state = _db.get(); + const auto& usage = _db.get(name); + + int64_t x; + int64_t cpu_weight; + get_account_limits( name, x, x, cpu_weight ); + + if( cpu_weight < 0 ) { + return -1; + } + + auto total_cpu_weight = state.total_cpu_weight; + if( total_cpu_weight == 0 ) total_cpu_weight = 1; + uint128_t consumed_ex = (uint128_t)usage.cpu_usage.consumed * (uint128_t)config::rate_limiting_precision; uint128_t virtual_capacity_ex = (uint128_t)state.virtual_cpu_limit * (uint128_t)config::rate_limiting_precision; - uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.cpu_weight) / (uint128_t)state.total_cpu_weight; - if (usable_capacity_ex < consumed_ex) { + uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * cpu_weight) / (uint128_t)total_cpu_weight; + + if( usable_capacity_ex < consumed_ex ) { return 0; } return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision); + */ +} + +account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name ) const { + const auto& config = _db.get(); + const auto& state = _db.get(); + const auto& usage = _db.get(name); + + int64_t x; + int64_t cpu_weight; + get_account_limits( name, x, x, cpu_weight ); + + if( cpu_weight < 0 || state.total_cpu_weight == 0 ) { + return { -1, -1, -1 }; + } + + account_resource_limit arl; + + uint128_t window_size = config.account_cpu_usage_average_window; + + auto virtual_cpu_capacity_in_window = state.virtual_cpu_limit * window_size; + uint128_t user_weight = cpu_weight; + uint128_t all_user_weight = state.total_cpu_weight; + + auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight; + auto cpu_used_in_window = (usage.cpu_usage.value_ex * window_size) / config::rate_limiting_precision; + + if( max_user_use_in_window <= cpu_used_in_window ) + arl.available = 0; + else + arl.available = max_user_use_in_window - cpu_used_in_window; + + arl.used = cpu_used_in_window; + + return arl; } int64_t resource_limits_manager::get_account_net_limit( const account_name& name ) const { const auto& state = _db.get(); const auto& usage = _db.get(name); - const auto& limits = _db.get(boost::make_tuple(false, name)); - if (limits.net_weight < 0) { + const auto& config = _db.get(); + + int64_t unused; + int64_t net_weight; + get_account_limits( name, unused, net_weight, unused ); + + if( net_weight < 0 ) { return -1; } - uint128_t consumed_ex = (uint128_t)usage.net_usage.consumed * (uint128_t)config::rate_limiting_precision; - uint128_t virtual_capacity_ex = (uint128_t)state.virtual_net_limit * (uint128_t)config::rate_limiting_precision; - uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.net_weight) / (uint128_t)state.total_net_weight; + uint128_t window_size = config.account_net_usage_average_window; - if (usable_capacity_ex < consumed_ex) { - return 0; + auto virtual_network_capacity_in_window = state.virtual_net_limit * window_size; + uint128_t user_weight = net_weight; + uint128_t all_user_weight = state.total_net_weight; + + auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight; + auto net_used_in_window = (usage.net_usage.value_ex * window_size) / config::rate_limiting_precision; + + if( max_user_use_in_window <= net_used_in_window ) return 0; + + return max_user_use_in_window - net_used_in_window; +} + +account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name ) const { + const auto& config = _db.get(); + const auto& state = _db.get(); + const auto& usage = _db.get(name); + + int64_t x; + int64_t net_weight; + get_account_limits( name, x, net_weight, x ); + + if( net_weight < 0 || state.total_net_weight == 0) { + return { -1, -1, -1 }; } - return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision); + account_resource_limit arl; + + uint128_t window_size = config.account_net_usage_average_window; + + auto virtual_network_capacity_in_window = state.virtual_net_limit * window_size; + uint128_t user_weight = net_weight; + uint128_t all_user_weight = state.total_net_weight; + + auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight; + auto net_used_in_window = (usage.net_usage.value_ex * window_size) / config::rate_limiting_precision; + + if( max_user_use_in_window <= net_used_in_window ) + arl.available = 0; + else + arl.available = max_user_use_in_window - net_used_in_window; + arl.used = net_used_in_window; + return arl; } diff --git a/libraries/chain/test.cpp b/libraries/chain/test.cpp deleted file mode 100644 index c971f302d15..00000000000 --- a/libraries/chain/test.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include - -using namespace eosio::chain; - -int main( int argc, char** argv ) { - try { - chain_controller::controller_config cfg; - cfg.block_log_dir = "testdir/blocklog"; - cfg.shared_memory_dir = "testdir/shared"; - cfg.shared_memory_size = 1024*1024*8; - - cfg.genesis = fc::json::from_file("genesis.json").as(); - cfg.genesis.initial_timestamp = block_timestamp_type(fc::time_point::now()); - idump((cfg.genesis)); - - chain_controller ctrl( cfg ); - - auto head_time = ctrl.head_block_time(); - auto next_time = head_time + fc::milliseconds(config::block_interval_ms); - uint32_t slot = ctrl.get_slot_at_time( next_time ); - - auto str = string(next_time); - auto tt = fc::variant(str).as(); - idump((str)(tt)); - - idump( (ctrl.head_block_time()) ); - idump( (ctrl.head_block_num()) ); - idump( (ctrl.head_block_id()) ); - idump( (next_time)(slot)(ctrl.get_slot_at_time(next_time)) ); - idump( (ctrl.get_scheduled_producer( slot ) ) ); - idump((next_time.time_since_epoch().count())); - next_time += fc::milliseconds(config::block_interval_ms); - idump((next_time)(next_time.time_since_epoch().count())); - - - private_key_type default_priv_key = private_key_type::regenerate(fc::sha256::hash(std::string("nathan"))); - public_key_type pub_key = default_priv_key.get_public_key(); - - - /* - auto private_key_default = std::make_pair(eosio::chain::public_key_type(default_priv_key.get_public_key()), - eosio::utilities::key_to_wif(default_priv_key)); - */ - - idump((pub_key)(default_priv_key)); - - for( uint32_t i = 0; i < 50; ++i ) - { - next_time = next_time + fc::milliseconds(config::block_interval_ms); - uint32_t slot = ctrl.get_slot_at_time( next_time ); - auto sch_pro = ctrl.get_scheduled_producer(slot); - const auto& pro = ctrl.get_producer( sch_pro ); - auto b = ctrl.generate_block( next_time, sch_pro, default_priv_key ); - idump((b.block_num())); - // idump((b)); - } - //idump((pro.signing_key)); - - } catch ( const fc::exception& e ) { - elog( "${e}", ("e",e.to_detail_string()) ); - } - return 0; -} diff --git a/libraries/chain/transaction.cpp b/libraries/chain/transaction.cpp index 8e6ec5edf28..18dfcf1b1ca 100644 --- a/libraries/chain/transaction.cpp +++ b/libraries/chain/transaction.cpp @@ -2,7 +2,6 @@ * @file * @copyright defined in eos/LICENSE.txt */ -#include #include #include #include @@ -17,6 +16,8 @@ #include #include +#include +#include namespace eosio { namespace chain { @@ -56,6 +57,11 @@ bool transaction_header::verify_reference_block( const block_id_type& reference_ ref_block_prefix == (decltype(ref_block_prefix))reference_block._hash[1]; } +void transaction_header::validate()const { + EOS_ASSERT( max_net_usage_words.value < UINT32_MAX / 8UL, transaction_exception, + "declared max_net_usage_words overflows when expanded to max net usage" ); +} + transaction_id_type transaction::id() const { digest_type::encoder enc; fc::raw::pack( enc, *this ); @@ -66,8 +72,11 @@ digest_type transaction::sig_digest( const chain_id_type& chain_id, const vector digest_type::encoder enc; fc::raw::pack( enc, chain_id ); fc::raw::pack( enc, *this ); - if( cfd.size() ) - fc::raw::pack( enc, cfd ); + if( cfd.size() ) { + fc::raw::pack( enc, digest_type::hash(cfd) ); + } else { + fc::raw::pack( enc, digest_type() ); + } return enc.result(); } @@ -92,7 +101,7 @@ flat_set transaction::get_signature_keys( const vector signed_transaction::get_signature_keys( const chain_id return transaction::get_signature_keys(signatures, chain_id, context_free_data, allow_duplicate_keys); } -uint32_t packed_transaction::get_billable_size()const { - auto size = fc::raw::pack_size(*this); +uint32_t packed_transaction::get_unprunable_size()const { + uint64_t size = config::fixed_net_overhead_of_packed_trx; + size += packed_trx.size(); + FC_ASSERT( size <= std::numeric_limits::max(), "packed_transaction is too big" ); + return static_cast(size); +} + +uint32_t packed_transaction::get_prunable_size()const { + uint64_t size = fc::raw::pack_size(signatures); + size += packed_context_free_data.size(); FC_ASSERT( size <= std::numeric_limits::max(), "packed_transaction is too big" ); return static_cast(size); } digest_type packed_transaction::packed_digest()const { + digest_type::encoder prunable; + fc::raw::pack( prunable, signatures ); + fc::raw::pack( prunable, packed_context_free_data ); + digest_type::encoder enc; - fc::raw::pack( enc, *this ); + fc::raw::pack( enc, compression ); + fc::raw::pack( enc, packed_trx ); + fc::raw::pack( enc, prunable.result() ); + return enc.result(); } @@ -255,25 +279,40 @@ vector packed_transaction::get_context_free_data()const } FC_CAPTURE_AND_RETHROW((compression)(packed_context_free_data)) } +time_point_sec packed_transaction::expiration()const +{ + local_unpack(); + return unpacked_trx->expiration; +} + transaction_id_type packed_transaction::id()const { - try { - return get_transaction().id(); - } FC_CAPTURE_AND_RETHROW((compression)(packed_trx)) + local_unpack(); + return get_transaction().id(); } -transaction packed_transaction::get_transaction()const +void packed_transaction::local_unpack()const { - try { - switch(compression) { + if (!unpacked_trx) { + try { + switch(compression) { case none: - return unpack_transaction(packed_trx); + unpacked_trx = unpack_transaction(packed_trx); + break; case zlib: - return zlib_decompress_transaction(packed_trx); + unpacked_trx = zlib_decompress_transaction(packed_trx); + break; default: FC_THROW("Unknown transaction compression algorithm"); - } - } FC_CAPTURE_AND_RETHROW((compression)(packed_trx)) + } + } FC_CAPTURE_AND_RETHROW((compression)(packed_trx)) + } +} + +transaction packed_transaction::get_transaction()const +{ + local_unpack(); + return transaction(*unpacked_trx); } signed_transaction packed_transaction::get_signed_transaction() const diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp new file mode 100644 index 00000000000..2f2b8d3eea7 --- /dev/null +++ b/libraries/chain/transaction_context.cpp @@ -0,0 +1,384 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio { namespace chain { + + transaction_context::transaction_context( controller& c, + const signed_transaction& t, + const transaction_id_type& trx_id, + fc::time_point s ) + :control(c) + ,trx(t) + ,id(trx_id) + ,undo_session(c.db().start_undo_session(true)) + ,trace(std::make_shared()) + ,start(s) + ,net_usage(trace->net_usage) + { + trace->id = id; + executed.reserve( trx.total_actions() ); + FC_ASSERT( trx.transaction_extensions.size() == 0, "we don't support any extensions yet" ); + } + + void transaction_context::init(uint64_t initial_net_usage ) + { + FC_ASSERT( !is_initialized, "cannot initialize twice" ); + const static int64_t large_number_no_overflow = std::numeric_limits::max()/2; + + auto original_deadline = deadline; + + const auto& cfg = control.get_global_properties().configuration; + auto& rl = control.get_mutable_resource_limits_manager(); + + net_limit = rl.get_block_net_limit(); + + objective_duration_limit = fc::microseconds( rl.get_block_cpu_limit() ); + deadline = start + objective_duration_limit; + + // Check if deadline is limited by block deadline or caller-set deadline + if( original_deadline <= deadline ) { + deadline = original_deadline; + deadline_exception_code = deadline_exception::code_value; + } + + // Possibly lower net_limit to the maximum net usage a transaction is allowed to be billed + if( cfg.max_transaction_net_usage <= net_limit ) { + net_limit = cfg.max_transaction_net_usage; + net_limit_due_to_block = false; + } + + // Possibly lower net_limit to optional limit set in the transaction header + uint64_t trx_specified_net_usage_limit = static_cast(trx.max_net_usage_words.value) * 8; + if( trx_specified_net_usage_limit > 0 && trx_specified_net_usage_limit <= net_limit ) { + net_limit = trx_specified_net_usage_limit; + net_limit_due_to_block = false; + } + + // Possibly lower objective_duration_limit to optional limit set in transaction header + if( trx.max_cpu_usage_ms > 0 ) { + auto trx_specified_cpu_usage_limit = fc::milliseconds(trx.max_cpu_usage_ms); + if( trx_specified_cpu_usage_limit <= objective_duration_limit ) { + objective_duration_limit = trx_specified_cpu_usage_limit; + objective_duration_limit_due_to_block = false; + } + } + + // Possibly limit deadline if objective_duration_limit is not due to the block and does not exceed current delta + if( !objective_duration_limit_due_to_block && objective_duration_limit <= (deadline - start) ) { + deadline = start + objective_duration_limit; + deadline_exception_code = tx_cpu_usage_exceeded::code_value; + } + + if( billed_cpu_time_us > 0 ) + validate_cpu_usage_to_bill( billed_cpu_time_us, false ); // Fail early if the amount to be billed is too high + + // Record accounts to be billed for network and CPU usage + for( const auto& act : trx.actions ) { + for( const auto& auth : act.authorization ) { + bill_to_accounts.insert( auth.actor ); + } + } + validate_ram_usage.reserve( bill_to_accounts.size() ); + + // Update usage values of accounts to reflect new time + rl.add_transaction_usage( bill_to_accounts, 0, 0, block_timestamp_type(control.pending_block_time()).slot ); + + // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed + int64_t account_net_limit = large_number_no_overflow; + int64_t account_cpu_limit = large_number_no_overflow; + for( const auto& a : bill_to_accounts ) { + auto net_limit = rl.get_account_net_limit(a); + if( net_limit >= 0 ) + account_net_limit = std::min( account_net_limit, net_limit ); + auto cpu_limit = rl.get_account_cpu_limit(a); + if( cpu_limit >= 0 ) + account_cpu_limit = std::min( account_cpu_limit, cpu_limit ); + } + + eager_net_limit = net_limit; + + // Possible lower eager_net_limit to what the billed accounts can pay plus some (objective) leeway + auto new_eager_net_limit = std::min( eager_net_limit, static_cast(account_net_limit + cfg.net_usage_leeway) ); + if( new_eager_net_limit < eager_net_limit ) { + eager_net_limit = new_eager_net_limit; + net_limit_due_to_block = false; + } + + // Possibly limit deadline if the duration accounts can be billed for (+ a subjective leeway) does not exceed current delta + if( ( fc::microseconds(account_cpu_limit) + leeway ) <= (deadline - start) ) { + deadline = start + fc::microseconds(account_cpu_limit) + leeway; + deadline_exception_code = leeway_deadline_exception::code_value; + } + + eager_net_limit = (eager_net_limit/8)*8; // Round down to nearest multiple of word size (8 bytes) so check_net_usage can be efficient + + if( initial_net_usage > 0 ) + add_net_usage( initial_net_usage ); // Fail early if current net usage is already greater than the calculated limit + + if( billed_cpu_time_us > 0 ) + deadline = original_deadline; // Only change deadline if billed_cpu_time_us is not set + + check_time(); // Fail early if deadline has already been exceeded + + is_initialized = true; + } + + void transaction_context::init_for_implicit_trx( uint64_t initial_net_usage ) + { + published = control.pending_block_time(); + init( initial_net_usage ); + } + + void transaction_context::init_for_input_trx( uint64_t packed_trx_unprunable_size, + uint64_t packed_trx_prunable_size, + uint32_t num_signatures ) + { + const auto& cfg = control.get_global_properties().configuration; + + uint64_t discounted_size_for_pruned_data = packed_trx_prunable_size; + if( cfg.context_free_discount_net_usage_den > 0 + && cfg.context_free_discount_net_usage_num < cfg.context_free_discount_net_usage_den ) + { + discounted_size_for_pruned_data *= cfg.context_free_discount_net_usage_num; + discounted_size_for_pruned_data = ( discounted_size_for_pruned_data + cfg.context_free_discount_net_usage_den - 1) + / cfg.context_free_discount_net_usage_den; // rounds up + } + + uint64_t initial_net_usage = static_cast(cfg.base_per_transaction_net_usage) + + packed_trx_unprunable_size + discounted_size_for_pruned_data; + + + if( trx.delay_sec.value > 0 ) { + // If delayed, also charge ahead of time for the additional net usage needed to retire the delayed transaction + // whether that be by successfully executing, soft failure, hard failure, or expiration. + initial_net_usage += static_cast(cfg.base_per_transaction_net_usage) + + static_cast(config::transaction_id_net_usage); + } + + published = control.pending_block_time(); + is_input = true; + control.validate_expiration( trx ); + control.validate_tapos( trx ); + control.validate_referenced_accounts( trx ); + init( initial_net_usage ); + record_transaction( id, trx.expiration ); /// checks for dupes + } + + void transaction_context::init_for_deferred_trx( fc::time_point p ) + { + published = p; + trace->scheduled = true; + apply_context_free = false; + init( 0 ); + } + + void transaction_context::exec() { + FC_ASSERT( is_initialized, "must first initialize" ); + + if( apply_context_free ) { + for( const auto& act : trx.context_free_actions ) { + trace->action_traces.emplace_back(); + dispatch_action( trace->action_traces.back(), act, true ); + } + } + + if( delay == fc::microseconds() ) { + for( const auto& act : trx.actions ) { + trace->action_traces.emplace_back(); + dispatch_action( trace->action_traces.back(), act ); + } + } else { + schedule_transaction(); + } + } + + void transaction_context::finalize() { + FC_ASSERT( is_initialized, "must first initialize" ); + const static int64_t large_number_no_overflow = std::numeric_limits::max()/2; + + if( is_input ) { + auto& am = control.get_mutable_authorization_manager(); + for( const auto& act : trx.actions ) { + for( const auto& auth : act.authorization ) { + am.update_permission_usage( am.get_permission(auth) ); + } + } + } + + auto& rl = control.get_mutable_resource_limits_manager(); + for( auto a : validate_ram_usage ) { + rl.verify_account_ram_usage( a ); + } + + // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed + int64_t account_net_limit = large_number_no_overflow; + int64_t account_cpu_limit = large_number_no_overflow; + for( const auto& a : bill_to_accounts ) { + auto net_limit = rl.get_account_net_limit(a); + if( net_limit >= 0 ) + account_net_limit = std::min( account_net_limit, net_limit ); + auto cpu_limit = rl.get_account_cpu_limit(a); + if( cpu_limit >= 0 ) + account_cpu_limit = std::min( account_cpu_limit, cpu_limit ); + } + + // Possibly lower net_limit to what the billed accounts can pay + if( static_cast(account_net_limit) <= net_limit ) { + net_limit = static_cast(account_net_limit); + net_limit_due_to_block = false; + } + + // Possibly lower objective_duration_limit to what the billed accounts can pay + if( account_cpu_limit <= objective_duration_limit.count() ) { + objective_duration_limit = fc::microseconds(account_cpu_limit); + objective_duration_limit_due_to_block = false; + } + + net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes) + + eager_net_limit = net_limit; + check_net_usage(); + + trace->elapsed = fc::time_point::now() - start; + + if( billed_cpu_time_us == 0 ) + billed_cpu_time_us = std::max( trace->elapsed.count(), static_cast(config::default_min_transaction_cpu_usage_us) ); + + validate_cpu_usage_to_bill( billed_cpu_time_us ); + + rl.add_transaction_usage( bill_to_accounts, static_cast(billed_cpu_time_us), net_usage, + block_timestamp_type(control.pending_block_time()).slot ); // Should never fail + } + + void transaction_context::squash() { + undo_session.squash(); + } + + + void transaction_context::check_net_usage()const { + if( BOOST_UNLIKELY(net_usage > eager_net_limit) ) { + if( net_limit_due_to_block ) { + EOS_THROW( block_net_usage_exceeded, + "not enough space left in block: ${net_usage} > ${net_limit}", + ("net_usage", net_usage)("net_limit", eager_net_limit) ); + } else { + EOS_THROW( tx_net_usage_exceeded, + "net usage of transaction is too high: ${net_usage} > ${net_limit}", + ("net_usage", net_usage)("net_limit", eager_net_limit) ); + } + } + } + + void transaction_context::check_time()const { + auto now = fc::time_point::now(); + if( BOOST_UNLIKELY( now > deadline ) ) { + if( billed_cpu_time_us > 0 || deadline_exception_code == deadline_exception::code_value ) { + EOS_THROW( deadline_exception, "deadline exceeded", ("now", now)("deadline", deadline)("start", start) ); + } else if( deadline_exception_code == block_cpu_usage_exceeded::code_value ) { + EOS_THROW( block_cpu_usage_exceeded, + "not enough time left in block to complete executing transaction", + ("now", now)("deadline", deadline)("start", start) ); + } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { + EOS_THROW( tx_cpu_usage_exceeded, + "transaction was executing for too long", + ("now", now)("deadline", deadline)("start", start) ); + } else if( deadline_exception_code == leeway_deadline_exception::code_value ) { + EOS_THROW( leeway_deadline_exception, + "the transaction was unable to complete by deadline, " + "but it is possible it could have succeeded if it were allow to run to completion", + ("now", now)("deadline", deadline)("start", start) ); + } + FC_ASSERT( false, "unexpected deadline exception code" ); + } + } + void transaction_context::validate_cpu_usage_to_bill( int64_t billed_us, bool check_minimum )const { +#warning make min_transaction_cpu_us into a configuration parameter + EOS_ASSERT( !check_minimum || billed_us >= config::default_min_transaction_cpu_usage_us, transaction_exception, + "cannot bill CPU time less than the minimum of ${min_billable} us", + ("min_billable", config::default_min_transaction_cpu_usage_us)("billed_cpu_time_us", billed_us) + ); + + if( objective_duration_limit_due_to_block ) { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + block_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } else { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + tx_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } + } + + void transaction_context::add_ram_usage( account_name account, int64_t ram_delta ) { + auto& rl = control.get_mutable_resource_limits_manager(); + rl.add_pending_ram_usage( account, ram_delta ); + if( ram_delta > 0 ) { + validate_ram_usage.insert( account ); + } + } + + void transaction_context::dispatch_action( action_trace& trace, const action& a, account_name receiver, bool context_free, uint32_t recurse_depth ) { + apply_context acontext( control, *this, a, recurse_depth ); + acontext.context_free = context_free; + acontext.receiver = receiver; + + try { + acontext.exec(); + } catch( ... ) { + trace = move(acontext.trace); + throw; + } + + trace = move(acontext.trace); + } + + void transaction_context::schedule_transaction() { + // Charge ahead of time for the additional net usage needed to retire the delayed transaction + // whether that be by successfully executing, soft failure, hard failure, or expiration. + if( trx.delay_sec.value == 0 ) { // Do not double bill. Only charge if we have not already charged for the delay. + const auto& cfg = control.get_global_properties().configuration; + add_net_usage( static_cast(cfg.base_per_transaction_net_usage) + + static_cast(config::transaction_id_net_usage) ); // Will exit early if net usage cannot be payed. + } + + auto first_auth = trx.first_authorizor(); + + uint32_t trx_size = 0; + const auto& cgto = control.db().create( [&]( auto& gto ) { + gto.trx_id = id; + gto.payer = first_auth; + gto.sender = account_name(); /// delayed transactions have no sender + gto.sender_id = transaction_id_to_sender_id( gto.trx_id ); + gto.published = control.pending_block_time(); + gto.delay_until = gto.published + delay; + gto.expiration = gto.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window); + trx_size = gto.set( trx ); + }); + + add_ram_usage( cgto.payer, (config::billable_size_v + trx_size) ); + } + + void transaction_context::record_transaction( const transaction_id_type& id, fc::time_point_sec expire ) { + try { + control.db().create([&](transaction_object& transaction) { + transaction.trx_id = id; + transaction.expiration = expire; + }); + } catch ( ... ) { + EOS_ASSERT( false, tx_duplicate, + "duplicate transaction ${id}", ("id", id ) ); + } + } /// record_transaction + + +} } /// eosio::chain diff --git a/libraries/chain/transaction_metadata.cpp b/libraries/chain/transaction_metadata.cpp deleted file mode 100644 index 802fedf8f72..00000000000 --- a/libraries/chain/transaction_metadata.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include - -namespace eosio { namespace chain { - -transaction_metadata::transaction_metadata( const packed_transaction& t, chain_id_type chainid, const time_point& published, const optional& processing_deadline, bool implicit ) - :raw_trx(t.get_raw_transaction()) - ,decompressed_trx(fc::raw::unpack(*raw_trx)) - ,context_free_data(t.get_context_free_data()) - ,id(decompressed_trx->id()) - ,billable_packed_size( t.get_billable_size() ) - ,signature_count(t.signatures.size()) - ,published(published) - ,packed_digest(t.packed_digest()) - ,raw_data(raw_trx->data()) - ,raw_size(raw_trx->size()) - ,is_implicit(implicit) - ,processing_deadline(processing_deadline) -{ } - -} } // eosio::chain diff --git a/libraries/chain/wasm_eosio_injection.cpp b/libraries/chain/wasm_eosio_injection.cpp index 50d41d5d6af..a4afa44d46d 100644 --- a/libraries/chain/wasm_eosio_injection.cpp +++ b/libraries/chain/wasm_eosio_injection.cpp @@ -37,6 +37,16 @@ void max_memory_injection_visitor::initializer() {} int32_t call_depth_check::global_idx = -1; uint32_t instruction_counter::icnt = 0; -int32_t checktime_injector::checktime_idx = -1; +uint32_t instruction_counter::tcnt = 0; +uint32_t instruction_counter::bcnt = 0; +std::queue instruction_counter::fcnts; + +int32_t checktime_injection::idx = 0; +int32_t checktime_injection::chktm_idx = 0; +std::stack checktime_block_type::block_stack; +std::stack checktime_block_type::type_stack; +std::queue> checktime_block_type::orderings; +std::queue> checktime_block_type::bcnt_tables; +size_t checktime_function_end::fcnt = 0; }}} // namespace eosio, chain, injectors diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index dc8abcd82b1..1688226a4a4 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -1,28 +1,30 @@ #include #include -#include +#include +#include #include -#include #include #include #include +#include #include #include #include #include +#include +#include #include #include #include #include -#include #include +#include #include #include #include namespace eosio { namespace chain { - using namespace contracts; using namespace webassembly; using namespace webassembly::common; @@ -48,9 +50,9 @@ namespace eosio { namespace chain { //there are a couple opportunties for improvement here-- //Easy: Cache the Module created here so it can be reused for instantiaion //Hard: Kick off instantiation in a separate thread at this location - } + } - void wasm_interface::apply( const digest_type& code_id, const shared_vector& code, apply_context& context ) { + void wasm_interface::apply( const digest_type& code_id, const shared_string& code, apply_context& context ) { my->get_instantiated_module(code_id, code)->apply(context); } @@ -131,32 +133,33 @@ class privileged_api : public context_aware_api { EOS_ASSERT(ram_bytes >= -1, wasm_execution_error, "invalid value for ram resource limit expected [-1,INT64_MAX]"); EOS_ASSERT(net_weight >= -1, wasm_execution_error, "invalid value for net resource weight expected [-1,INT64_MAX]"); EOS_ASSERT(cpu_weight >= -1, wasm_execution_error, "invalid value for cpu resource weight expected [-1,INT64_MAX]"); - context.mutable_controller.get_mutable_resource_limits_manager().set_account_limits(account, ram_bytes, net_weight, cpu_weight); + if( context.control.get_mutable_resource_limits_manager().set_account_limits(account, ram_bytes, net_weight, cpu_weight) ) { + context.trx_context.validate_ram_usage.insert( account ); + } } void get_resource_limits( account_name account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight ) { - context.controller.get_resource_limits_manager().get_account_limits( account, ram_bytes, net_weight, cpu_weight); + context.control.get_resource_limits_manager().get_account_limits( account, ram_bytes, net_weight, cpu_weight); } - void set_active_producers( array_ptr packed_producer_schedule, size_t datalen) { + bool set_active_producers( array_ptr packed_producer_schedule, size_t datalen) { datastream ds( packed_producer_schedule, datalen ); - producer_schedule_type psch; - fc::raw::unpack(ds, psch); + vector producers; + fc::raw::unpack(ds, producers); + EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain"); // check that producers are unique std::set unique_producers; - for (const auto& p: psch.producers) { - EOS_ASSERT(context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account"); + for (const auto& p: producers) { + EOS_ASSERT( context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account" ); + EOS_ASSERT( p.block_signing_key.valid(), wasm_execution_error, "producer schedule includes an invalid key" ); unique_producers.insert(p.producer_name); } - EOS_ASSERT(psch.producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule"); - context.mutable_db.modify( context.controller.get_global_properties(), - [&]( auto& gprops ) { - gprops.new_active_producers = psch; - }); + EOS_ASSERT( producers.size() == unique_producers.size(), wasm_execution_error, "duplicate producer name in producer schedule" ); + return context.control.set_proposed_producers( std::move(producers) ); } uint32_t get_blockchain_parameters_packed( array_ptr packed_blockchain_parameters, size_t buffer_size) { - auto& gpo = context.controller.get_global_properties(); + auto& gpo = context.control.get_global_properties(); auto s = fc::raw::pack_size( gpo.configuration ); if( buffer_size == 0 ) return s; @@ -169,12 +172,11 @@ class privileged_api : public context_aware_api { return 0; } - void set_blockchain_parameters_packed( array_ptr packed_blockchain_parameters, size_t datalen) { datastream ds( packed_blockchain_parameters, datalen ); chain::chain_config cfg; fc::raw::unpack(ds, cfg); - context.mutable_db.modify( context.controller.get_global_properties(), + context.db.modify( context.control.get_global_properties(), [&]( auto& gprops ) { gprops.configuration = cfg; }); @@ -186,27 +188,19 @@ class privileged_api : public context_aware_api { void set_privileged( account_name n, bool is_priv ) { const auto& a = context.db.get( n ); - context.mutable_db.modify( a, [&]( auto& ma ){ + context.db.modify( a, [&]( auto& ma ){ ma.privileged = is_priv; }); } }; -class checktime_api : public context_aware_api { -public: - explicit checktime_api( apply_context& ctx ) - :context_aware_api(ctx,true){} - - void checktime(uint32_t instruction_count) { - context.checktime(instruction_count); - } -}; - class softfloat_api : public context_aware_api { public: // TODO add traps on truncations for special cases (NaN or outside the range which rounds to an integer) - using context_aware_api::context_aware_api; + softfloat_api( apply_context& ctx ) + :context_aware_api(ctx, true) {} + // float binops float _eosio_f32_add( float a, float b ) { float32_t ret = f32_add( to_softfloat32(a), to_softfloat32(b) ); @@ -673,6 +667,7 @@ class softfloat_api : public context_aware_api { static bool sign_bit( float64_t f ) { return f.v >> 63; } }; + class producer_api : public context_aware_api { public: using context_aware_api::context_aware_api; @@ -686,7 +681,7 @@ class producer_api : public context_aware_api { auto copy_size = std::min( buffer_size, s ); memcpy( producers, active_producers.data(), copy_size ); - + return copy_size; } }; @@ -767,60 +762,159 @@ class permission_api : public context_aware_api { public: using context_aware_api::context_aware_api; - bool check_authorization( account_name account, permission_name permission, array_ptr packed_pubkeys, size_t datalen) { + bool check_transaction_authorization( array_ptr trx_data, size_t trx_size, + array_ptr pubkeys_data, size_t pubkeys_size, + array_ptr perms_data, size_t perms_size + ) + { + transaction trx = fc::raw::unpack( trx_data, trx_size ); - vector pub_keys; - datastream ds( packed_pubkeys, datalen ); - while(ds.remaining()) { - public_key_type pub; - fc::raw::unpack(ds, pub); - pub_keys.emplace_back(pub); - } + flat_set provided_keys; + unpack_provided_keys( provided_keys, pubkeys_data, pubkeys_size ); - return context.controller.check_authorization( - account, permission, - {pub_keys.begin(), pub_keys.end()}, - false - ); + flat_set provided_permissions; + unpack_provided_permissions( provided_permissions, perms_data, perms_size ); + + try { + context.control + .get_authorization_manager() + .check_authorization( trx.actions, + provided_keys, + provided_permissions, + fc::seconds(trx.delay_sec), + std::bind(&apply_context::checktime, &context, std::placeholders::_1), + false + ); + return true; + } catch( const authorization_exception& e ) {} + + return false; } + + bool check_permission_authorization( account_name account, permission_name permission, + array_ptr pubkeys_data, size_t pubkeys_size, + array_ptr perms_data, size_t perms_size, + uint64_t delay_us + ) + { + EOS_ASSERT( delay_us <= static_cast(std::numeric_limits::max()), + action_validate_exception, "provided delay is too large" ); + + flat_set provided_keys; + unpack_provided_keys( provided_keys, pubkeys_data, pubkeys_size ); + + flat_set provided_permissions; + unpack_provided_permissions( provided_permissions, perms_data, perms_size ); + + try { + context.control + .get_authorization_manager() + .check_authorization( account, + permission, + provided_keys, + provided_permissions, + fc::microseconds(delay_us), + std::bind(&apply_context::checktime, &context, std::placeholders::_1), + false + ); + return true; + } catch( const authorization_exception& e ) {} + + return false; + } + + int64_t get_permission_last_used( account_name account, permission_name permission ) { + const auto& am = context.control.get_authorization_manager(); + return am.get_permission_last_used( am.get_permission({account, permission}) ).time_since_epoch().count(); + }; + + int64_t get_account_creation_time( account_name account ) { + auto* acct = context.db.find(account); + EOS_ASSERT( acct != nullptr, action_validate_exception, + "account '${account}' does not exist", ("account", account) ); + return time_point(acct->creation_date).time_since_epoch().count(); + } + + private: + void unpack_provided_keys( flat_set& keys, const char* pubkeys_data, size_t pubkeys_size ) { + keys.clear(); + if( pubkeys_size == 0 ) return; + + keys = fc::raw::unpack>( pubkeys_data, pubkeys_size ); + } + + void unpack_provided_permissions( flat_set& permissions, const char* perms_data, size_t perms_size ) { + permissions.clear(); + if( perms_size == 0 ) return; + + permissions = fc::raw::unpack>( perms_data, perms_size ); + } + }; -class string_api : public context_aware_api { +class authorization_api : public context_aware_api { public: using context_aware_api::context_aware_api; - void assert_is_utf8(array_ptr str, size_t datalen, null_terminated_ptr msg) { - const bool test = fc::is_utf8(std::string( str, datalen )); + void require_authorization( const account_name& account ) { + context.require_authorization( account ); + } + + bool has_authorization( const account_name& account )const { + return context.has_authorization( account ); + } + + void require_authorization(const account_name& account, + const permission_name& permission) { + context.require_authorization( account, permission ); + } + + void require_recipient( account_name recipient ) { + context.require_recipient( recipient ); + } + + bool is_account( const account_name& account )const { + return context.is_account( account ); + } - FC_ASSERT( test, "assertion failed: ${s}", ("s",msg.value) ); - } }; class system_api : public context_aware_api { public: - explicit system_api( apply_context& ctx ) - :context_aware_api(ctx,true){} + using context_aware_api::context_aware_api; - void abort() { - edump(("abort() called")); - FC_ASSERT( false, "abort() called"); + uint64_t current_time() { + return static_cast( context.control.pending_block_time().time_since_epoch().count() ); } - void eosio_assert(bool condition, null_terminated_ptr str) { - if( !condition ) { - std::string message( str ); - edump((message)); - FC_ASSERT( condition, "assertion failed: ${s}", ("s",message)); - } + uint64_t publication_time() { + return static_cast( context.trx_context.published.time_since_epoch().count() ); } - void eosio_exit(int32_t code) { - throw wasm_exit{code}; - } +}; + +class context_free_system_api : public context_aware_api { +public: + explicit context_free_system_api( apply_context& ctx ) + :context_aware_api(ctx,true){} - fc::time_point_sec now() { - return context.controller.head_block_time(); + void abort() { + edump(("abort() called")); + FC_ASSERT( false, "abort() called"); + } + + void eosio_assert(bool condition, null_terminated_ptr str) { + if( !condition ) { + std::string message( str ); + edump((message)); + FC_ASSERT( condition, "assertion failed: ${s}", ("s",message)); } + } + + void eosio_exit(int32_t code) { + throw wasm_exit{code}; + } + }; class action_api : public context_aware_api { @@ -842,18 +936,6 @@ class action_api : public context_aware_api { return context.act.data.size(); } - fc::time_point_sec publication_time() { - return context.trx_meta.published; - } - - name current_sender() { - if (context.trx_meta.sender) { - return *context.trx_meta.sender; - } else { - return name(); - } - } - name current_receiver() { return context.receiver; } @@ -1126,6 +1208,8 @@ class memory_api : public context_aware_api { :context_aware_api(ctx,true){} char* memcpy( array_ptr dest, array_ptr src, size_t length) { + EOS_ASSERT((std::abs((ptrdiff_t)dest.value - (ptrdiff_t)src.value)) >= length, + overlapping_memory_error, "memcpy can only accept non-aliasing pointers"); return (char *)::memcpy(dest, src, length); } @@ -1147,8 +1231,9 @@ class transaction_api : public context_aware_api { using context_aware_api::context_aware_api; void send_inline( array_ptr data, size_t data_len ) { - // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size - FC_ASSERT( data_len < config::default_max_inline_action_size, "inline action too big" ); + //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions + FC_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, + "inline action too big" ); action act; fc::raw::unpack(data, data_len, act); @@ -1156,8 +1241,9 @@ class transaction_api : public context_aware_api { } void send_context_free_inline( array_ptr data, size_t data_len ) { - // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size - FC_ASSERT( data_len < config::default_max_inline_action_size, "inline action too big" ); + //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions + FC_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, + "inline action too big" ); action act; fc::raw::unpack(data, data_len, act); @@ -1166,19 +1252,15 @@ class transaction_api : public context_aware_api { void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr data, size_t data_len ) { try { - deferred_transaction dtrx; - fc::raw::unpack(data, data_len, dtrx); - dtrx.sender = context.receiver; - dtrx.sender_id = sender_id; - dtrx.execute_after = time_point_sec( (context.controller.head_block_time() + fc::seconds(dtrx.delay_sec)) + fc::microseconds(999'999) ); // rounds up to nearest second - dtrx.payer = payer; - context.execute_deferred(std::move(dtrx)); + transaction trx; + fc::raw::unpack(data, data_len, trx); + context.schedule_deferred_transaction(sender_id, payer, std::move(trx)); } FC_CAPTURE_AND_RETHROW((fc::to_hex(data, data_len))); } void cancel_deferred( const unsigned __int128& val ) { fc::uint128_t sender_id(val>>64, uint64_t(val) ); - context.cancel_deferred( (unsigned __int128)sender_id ); + context.cancel_deferred_transaction( (unsigned __int128)sender_id ); } }; @@ -1205,30 +1287,26 @@ class context_free_transaction_api : public context_aware_api { } int expiration() { - return context.trx_meta.trx().expiration.sec_since_epoch(); + return context.trx_context.trx.expiration.sec_since_epoch(); } int tapos_block_num() { - return context.trx_meta.trx().ref_block_num; + return context.trx_context.trx.ref_block_num; } int tapos_block_prefix() { - return context.trx_meta.trx().ref_block_prefix; + return context.trx_context.trx.ref_block_prefix; } int get_action( uint32_t type, uint32_t index, array_ptr buffer, size_t buffer_size )const { return context.get_action( type, index, buffer, buffer_size ); } - - void check_auth( array_ptr trx_data, size_t trx_size, array_ptr perm_data, size_t perm_size ) { - transaction trx = fc::raw::unpack( trx_data, trx_size ); - vector perm = fc::raw::unpack>( perm_data, perm_size ); - return context.check_auth( trx, perm ); - } }; class compiler_builtins : public context_aware_api { public: - using context_aware_api::context_aware_api; + compiler_builtins( apply_context& ctx ) + :context_aware_api(ctx,true){} + void __ashlti3(__int128& ret, uint64_t low, uint64_t high, uint32_t shift) { fc::uint128_t i(high, low); i <<= shift; @@ -1334,6 +1412,7 @@ class compiler_builtins : public context_aware_api { ret = lhs; } + // arithmetic long double void __addtf3( float128_t& ret, uint64_t la, uint64_t ha, uint64_t lb, uint64_t hb ) { float128_t a = {{ la, ha }}; float128_t b = {{ lb, hb }}; @@ -1354,6 +1433,85 @@ class compiler_builtins : public context_aware_api { float128_t b = {{ lb, hb }}; ret = f128_div( a, b ); } + void __negtf2( float128_t& ret, uint64_t la, uint64_t ha ) { + ret = {{ la, (ha ^ (uint64_t)1 << 63) }}; + } + + // conversion long double + void __extendsftf2( float128_t& ret, float f ) { + ret = f32_to_f128( softfloat_api::to_softfloat32(f) ); + } + void __extenddftf2( float128_t& ret, double d ) { + ret = f64_to_f128( softfloat_api::to_softfloat64(d) ); + } + double __trunctfdf2( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return softfloat_api::from_softfloat64(f128_to_f64( f )); + } + float __trunctfsf2( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return softfloat_api::from_softfloat32(f128_to_f32( f )); + } + int32_t __fixtfsi( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return f128_to_i32( f, 0, false ); + } + int64_t __fixtfdi( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return f128_to_i64( f, 0, false ); + } + void __fixtfti( __int128& ret, uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + ret = ___fixtfti( f ); + } + uint32_t __fixunstfsi( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return f128_to_ui32( f, 0, false ); + } + uint64_t __fixunstfdi( uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + return f128_to_ui64( f, 0, false ); + } + void __fixunstfti( unsigned __int128& ret, uint64_t l, uint64_t h ) { + float128_t f = {{ l, h }}; + ret = ___fixunstfti( f ); + } + void __fixsfti( __int128& ret, float a ) { + ret = ___fixsfti( softfloat_api::to_softfloat32(a).v ); + } + void __fixdfti( __int128& ret, double a ) { + ret = ___fixdfti( softfloat_api::to_softfloat64(a).v ); + } + void __fixunssfti( unsigned __int128& ret, float a ) { + ret = ___fixunssfti( softfloat_api::to_softfloat32(a).v ); + } + void __fixunsdfti( unsigned __int128& ret, double a ) { + ret = ___fixunsdfti( softfloat_api::to_softfloat64(a).v ); + } + double __floatsidf( int32_t i ) { + return softfloat_api::from_softfloat64(i32_to_f64(i)); + } + void __floatsitf( float128_t& ret, int32_t i ) { + ret = i32_to_f128(i); + } + void __floatditf( float128_t& ret, uint64_t a ) { + ret = i64_to_f128( a ); + } + void __floatunsitf( float128_t& ret, uint32_t i ) { + ret = ui32_to_f128(i); + } + void __floatunditf( float128_t& ret, uint64_t a ) { + ret = ui64_to_f128( a ); + } + double __floattidf( uint64_t l, uint64_t h ) { + fc::uint128_t v(h, l); + unsigned __int128 val = (unsigned __int128)v; + return ___floattidf( *(__int128*)&val ); + } + double __floatuntidf( uint64_t l, uint64_t h ) { + fc::uint128_t v(h, l); + return ___floatuntidf( (unsigned __int128)v ); + } int ___cmptf2( uint64_t la, uint64_t ha, uint64_t lb, uint64_t hb, int return_value_if_nan ) { float128_t a = {{ la, ha }}; float128_t b = {{ lb, hb }}; @@ -1393,131 +1551,10 @@ class compiler_builtins : public context_aware_api { return 1; return 0; } - double __floatsidf( int32_t i ) { - edump((i)( "warning returning float64") ); - float64_t ret = i32_to_f64(i); - return *reinterpret_cast(&ret); - } - void __floatsitf( float128_t& ret, int32_t i ) { - ret = i32_to_f128(i); /// TODO: should be 128 - } - void __floatunsitf( float128_t& ret, uint32_t i ) { - ret = ui32_to_f128(i); /// TODO: should be 128 - } - void __floatditf( float128_t& ret, uint64_t a ) { - ret = i64_to_f128( a ); - } - void __floatunditf( float128_t& ret, uint64_t a ) { - ret = ui64_to_f128( a ); - } - void __extendsftf2( float128_t& ret, uint32_t f ) { - float32_t in = { f }; - ret = f32_to_f128( in ); - } - void __extenddftf2( float128_t& ret, double in ) { - edump(("warning in flaot64..." )); - ret = f64_to_f128( float64_t{*(uint64_t*)&in} ); - } - int64_t __fixtfdi( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_i64( f, 0, false ); - } - int32_t __fixtfsi( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_i32( f, 0, false ); - } - uint64_t __fixunstfdi( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_ui64( f, 0, false ); - } - uint32_t __fixunstfsi( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_ui32( f, 0, false ); - } - uint64_t __trunctfdf2( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_f64( f ).v; - } - uint32_t __trunctfsf2( uint64_t l, uint64_t h ) { - float128_t f = {{ l, h }}; - return f128_to_f32( f ).v; - } static constexpr uint32_t SHIFT_WIDTH = (sizeof(uint64_t)*8)-1; }; -class math_api : public context_aware_api { - public: - math_api( apply_context& ctx ) - :context_aware_api(ctx,true){} - - void diveq_i128(unsigned __int128* self, const unsigned __int128* other) { - fc::uint128_t s(*self); - const fc::uint128_t o(*other); - FC_ASSERT( o != 0, "divide by zero" ); - - s = s/o; - *self = (unsigned __int128)s; - } - - void multeq_i128(unsigned __int128* self, const unsigned __int128* other) { - fc::uint128_t s(*self); - const fc::uint128_t o(*other); - s *= o; - *self = (unsigned __int128)s; - } - - uint64_t double_add(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - DOUBLE c = DOUBLE(*reinterpret_cast(&a)) - + DOUBLE(*reinterpret_cast(&b)); - double res = c.convert_to(); - return *reinterpret_cast(&res); - } - - uint64_t double_mult(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - DOUBLE c = DOUBLE(*reinterpret_cast(&a)) - * DOUBLE(*reinterpret_cast(&b)); - double res = c.convert_to(); - return *reinterpret_cast(&res); - } - - uint64_t double_div(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - DOUBLE divisor = DOUBLE(*reinterpret_cast(&b)); - FC_ASSERT(divisor != 0, "divide by zero"); - DOUBLE c = DOUBLE(*reinterpret_cast(&a)) / divisor; - double res = c.convert_to(); - return *reinterpret_cast(&res); - } - - uint32_t double_eq(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - return DOUBLE(*reinterpret_cast(&a)) == DOUBLE(*reinterpret_cast(&b)); - } - - uint32_t double_lt(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - return DOUBLE(*reinterpret_cast(&a)) < DOUBLE(*reinterpret_cast(&b)); - } - - uint32_t double_gt(uint64_t a, uint64_t b) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - return DOUBLE(*reinterpret_cast(&a)) > DOUBLE(*reinterpret_cast(&b)); - } - - uint64_t double_to_i64(uint64_t n) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - return DOUBLE(*reinterpret_cast(&n)).convert_to(); - } - - uint64_t i64_to_double(int64_t n) { - using DOUBLE = boost::multiprecision::cpp_bin_float_50; - double res = DOUBLE(n).convert_to(); - return *reinterpret_cast(&res); - } -}; /* * This api will be removed with fix for `eos #2561` @@ -1526,7 +1563,7 @@ class call_depth_api : public context_aware_api { public: call_depth_api( apply_context& ctx ) :context_aware_api(ctx,true){} - void call_depth_assert() { + void call_depth_assert() { FC_THROW_EXCEPTION(wasm_execution_error, "Exceeded call depth maximum"); } }; @@ -1535,19 +1572,6 @@ REGISTER_INJECTED_INTRINSICS(call_depth_api, (call_depth_assert, void() ) ); -REGISTER_INTRINSICS(math_api, - (diveq_i128, void(int, int) ) - (multeq_i128, void(int, int) ) - (double_add, int64_t(int64_t, int64_t) ) - (double_mult, int64_t(int64_t, int64_t) ) - (double_div, int64_t(int64_t, int64_t) ) - (double_eq, int32_t(int64_t, int64_t) ) - (double_lt, int32_t(int64_t, int64_t) ) - (double_gt, int32_t(int64_t, int64_t) ) - (double_to_i64, int64_t(int64_t) ) - (i64_to_double, int64_t(int64_t) ) -); - REGISTER_INTRINSICS(compiler_builtins, (__ashlti3, void(int, int64_t, int64_t, int) ) (__ashrti3, void(int, int64_t, int64_t, int) ) @@ -1570,19 +1594,28 @@ REGISTER_INTRINSICS(compiler_builtins, (__letf2, int(int64_t, int64_t, int64_t, int64_t) ) (__cmptf2, int(int64_t, int64_t, int64_t, int64_t) ) (__unordtf2, int(int64_t, int64_t, int64_t, int64_t) ) + (__negtf2, void (int, int64_t, int64_t) ) (__floatsitf, void (int, int) ) (__floatunsitf, void (int, int) ) (__floatditf, void (int, int64_t) ) (__floatunditf, void (int, int64_t) ) + (__floattidf, double (int64_t, int64_t) ) + (__floatuntidf, double (int64_t, int64_t) ) (__floatsidf, double(int) ) - (__extendsftf2, void(int, int) ) + (__extendsftf2, void(int, float) ) (__extenddftf2, void(int, double) ) + (__fixtfti, void(int, int64_t, int64_t) ) (__fixtfdi, int64_t(int64_t, int64_t) ) (__fixtfsi, int(int64_t, int64_t) ) + (__fixunstfti, void(int, int64_t, int64_t) ) (__fixunstfdi, int64_t(int64_t, int64_t) ) (__fixunstfsi, int(int64_t, int64_t) ) - (__trunctfdf2, int64_t(int64_t, int64_t) ) - (__trunctfsf2, int(int64_t, int64_t) ) + (__fixsfti, void(int, float) ) + (__fixdfti, void(int, double) ) + (__fixunssfti, void(int, float) ) + (__fixunsdfti, void(int, double) ) + (__trunctfdf2, double(int64_t, int64_t) ) + (__trunctfsf2, float(int64_t, int64_t) ) ); REGISTER_INTRINSICS(privileged_api, @@ -1590,14 +1623,14 @@ REGISTER_INTRINSICS(privileged_api, (activate_feature, void(int64_t) ) (get_resource_limits, void(int64_t,int,int,int) ) (set_resource_limits, void(int64_t,int64_t,int64_t,int64_t) ) - (set_active_producers, void(int,int) ) + (set_active_producers, int(int,int) ) (get_blockchain_parameters_packed, int(int, int) ) (set_blockchain_parameters_packed, void(int,int) ) (is_privileged, int(int64_t) ) (set_privileged, void(int64_t, int) ) ); -REGISTER_INJECTED_INTRINSICS(checktime_api, +REGISTER_INJECTED_INTRINSICS(apply_context, (checktime, void(int)) ); @@ -1661,40 +1694,40 @@ REGISTER_INTRINSICS(crypto_api, (ripemd160, void(int, int, int) ) ); + REGISTER_INTRINSICS(permission_api, - (check_authorization, int(int64_t, int64_t, int, int)) + (check_transaction_authorization, int(int, int, int, int, int, int) ) + (check_permission_authorization, int(int64_t, int64_t, int, int, int, int, int64_t) ) + (get_permission_last_used, int64_t(int64_t, int64_t) ) + (get_account_creation_time, int64_t(int64_t) ) ); -REGISTER_INTRINSICS(string_api, - (assert_is_utf8, void(int, int, int) ) -); REGISTER_INTRINSICS(system_api, - (abort, void()) - (eosio_assert, void(int, int)) - (eosio_exit, void(int )) - (now, int()) + (current_time, int64_t() ) + (publication_time, int64_t() ) +); + +REGISTER_INTRINSICS(context_free_system_api, + (abort, void() ) + (eosio_assert, void(int, int) ) + (eosio_exit, void(int) ) ); REGISTER_INTRINSICS(action_api, (read_action_data, int(int, int) ) (action_data_size, int() ) - (publication_time, int32_t() ) - (current_sender, int64_t() ) (current_receiver, int64_t() ) ); -REGISTER_INTRINSICS(apply_context, - (require_write_lock, void(int64_t) ) - (require_read_lock, void(int64_t, int64_t) ) +REGISTER_INTRINSICS(authorization_api, (require_recipient, void(int64_t) ) - (require_authorization, void(int64_t), "require_auth", void(apply_context::*)(const account_name&)) - (require_authorization, void(int64_t, int64_t), "require_auth2", void(apply_context::*)(const account_name&, const permission_name& permission)) - (has_authorization, int(int64_t), "has_auth", bool(apply_context::*)(const account_name&)const) + (require_authorization, void(int64_t), "require_auth", void(authorization_api::*)(const account_name&) ) + (require_authorization, void(int64_t, int64_t), "require_auth2", void(authorization_api::*)(const account_name&, const permission_name& permission) ) + (has_authorization, int(int64_t), "has_auth", bool(authorization_api::*)(const account_name&)const ) (is_account, int(int64_t) ) ); - //(printdi, void(int64_t) ) REGISTER_INTRINSICS(console_api, (prints, void(int) ) (prints_l, void(int, int) ) @@ -1716,7 +1749,6 @@ REGISTER_INTRINSICS(context_free_transaction_api, (tapos_block_prefix, int() ) (tapos_block_num, int() ) (get_action, int (int, int, int, int) ) - (check_auth, void(int, int, int, int) ) ); REGISTER_INTRINSICS(transaction_api, diff --git a/libraries/chainbase b/libraries/chainbase index 4ebab558f50..cb51952ae12 160000 --- a/libraries/chainbase +++ b/libraries/chainbase @@ -1 +1 @@ -Subproject commit 4ebab558f506f13457317fe9f0ca5b79511b76a1 +Subproject commit cb51952ae12e550959fa5c4cf9bec5fa5c454b5a diff --git a/libraries/fc/include/fc/crypto/elliptic.hpp b/libraries/fc/include/fc/crypto/elliptic.hpp index 0b484525764..26128682b5c 100644 --- a/libraries/fc/include/fc/crypto/elliptic.hpp +++ b/libraries/fc/include/fc/crypto/elliptic.hpp @@ -149,7 +149,6 @@ namespace fc { static fc::sha256 get_secret( const EC_KEY * const k ); fc::fwd my; }; - ; struct range_proof_info { @@ -189,6 +188,10 @@ namespace fc { */ struct public_key_shim : public crypto::shim { using crypto::shim::shim; + + bool valid()const { + return public_key(_data).valid(); + } }; struct signature_shim : public crypto::shim { diff --git a/libraries/fc/include/fc/crypto/elliptic_r1.hpp b/libraries/fc/include/fc/crypto/elliptic_r1.hpp index c2c1a44a6e6..a25f11b6c0e 100644 --- a/libraries/fc/include/fc/crypto/elliptic_r1.hpp +++ b/libraries/fc/include/fc/crypto/elliptic_r1.hpp @@ -132,6 +132,10 @@ namespace fc { */ struct public_key_shim : public crypto::shim { using crypto::shim::shim; + + bool valid()const { + return public_key(_data).valid(); + } }; struct signature_shim : public crypto::shim { @@ -215,4 +219,4 @@ FC_REFLECT_TYPENAME( fc::crypto::r1::private_key ) FC_REFLECT_TYPENAME( fc::crypto::r1::public_key ) FC_REFLECT_DERIVED( fc::crypto::r1::public_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) FC_REFLECT_DERIVED( fc::crypto::r1::signature_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) -FC_REFLECT_DERIVED( fc::crypto::r1::private_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) \ No newline at end of file +FC_REFLECT_DERIVED( fc::crypto::r1::private_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) diff --git a/libraries/fc/include/fc/crypto/public_key.hpp b/libraries/fc/include/fc/crypto/public_key.hpp index c6663e7816a..be0655251a5 100644 --- a/libraries/fc/include/fc/crypto/public_key.hpp +++ b/libraries/fc/include/fc/crypto/public_key.hpp @@ -28,6 +28,8 @@ namespace fc { namespace crypto { public_key( const signature& c, const sha256& digest, bool check_canonical = true ); + bool valid()const; + // serialize to/from string explicit public_key(const string& base58str); explicit operator string() const; diff --git a/libraries/fc/include/fc/exception/exception.hpp b/libraries/fc/include/fc/exception/exception.hpp index cab8dc70e1a..d02d53c2dec 100644 --- a/libraries/fc/include/fc/exception/exception.hpp +++ b/libraries/fc/include/fc/exception/exception.hpp @@ -444,6 +444,23 @@ namespace fc wdump( __VA_ARGS__ ); \ } +#define FC_LOG_AND_DROP( ... ) \ + catch( fc::exception& er ) { \ + wlog( "${details}", ("details",er.to_detail_string()) ); \ + } catch( const std::exception& e ) { \ + fc::exception fce( \ + FC_LOG_MESSAGE( warn, "rethrow ${what}: ",FC_FORMAT_ARG_PARAMS( __VA_ARGS__ )("what",e.what()) ), \ + fc::std_exception_code,\ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ) ; \ + wlog( "${details}", ("details",fce.to_detail_string()) ); \ + } catch( ... ) { \ + fc::unhandled_exception e( \ + FC_LOG_MESSAGE( warn, "rethrow", FC_FORMAT_ARG_PARAMS( __VA_ARGS__) ), \ + std::current_exception() ); \ + wlog( "${details}", ("details",e.to_detail_string()) ); \ + } + /** * @def FC_RETHROW_EXCEPTIONS(LOG_LEVEL,FORMAT,...) diff --git a/libraries/fc/include/fc/io/raw.hpp b/libraries/fc/include/fc/io/raw.hpp index 277192fee70..40a4a447c28 100644 --- a/libraries/fc/include/fc/io/raw.hpp +++ b/libraries/fc/include/fc/io/raw.hpp @@ -187,14 +187,15 @@ namespace fc { template inline void pack( Stream& s, const std::shared_ptr& v) { - fc::raw::pack( s, *v ); + fc::raw::pack( s, bool(!!v) ); + if( !!v ) fc::raw::pack( s, *v ); } template inline void unpack( Stream& s, std::shared_ptr& v) { try { - v = std::make_shared(); - fc::raw::unpack( s, *v ); + bool b; fc::raw::unpack( s, b ); + if( b ) { v = std::make_shared(); fc::raw::unpack( s, *v ); } } FC_RETHROW_EXCEPTIONS( warn, "std::shared_ptr", ("type",fc::get_typename::name()) ) } template inline void pack( Stream& s, const signed_int& v ) { @@ -639,20 +640,16 @@ namespace fc { inline T unpack( const std::vector& s ) { try { T tmp; - if( s.size() ) { - datastream ds( s.data(), size_t(s.size()) ); - fc::raw::unpack(ds,tmp); - } + datastream ds( s.data(), size_t(s.size()) ); + fc::raw::unpack(ds,tmp); return tmp; } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } template inline void unpack( const std::vector& s, T& tmp ) { try { - if( s.size() ) { - datastream ds( s.data(), size_t(s.size()) ); - fc::raw::unpack(ds,tmp); - } + datastream ds( s.data(), size_t(s.size()) ); + fc::raw::unpack(ds,tmp); } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } template diff --git a/plugins/net_plugin/include/eosio/net_plugin/message_buffer.hpp b/libraries/fc/include/fc/network/message_buffer.hpp similarity index 99% rename from plugins/net_plugin/include/eosio/net_plugin/message_buffer.hpp rename to libraries/fc/include/fc/network/message_buffer.hpp index 8e46fe4ce0c..702beacd3e5 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/message_buffer.hpp +++ b/libraries/fc/include/fc/network/message_buffer.hpp @@ -9,7 +9,7 @@ #include #include -namespace eosio { +namespace fc { template class mb_datastream; @@ -299,4 +299,4 @@ namespace eosio { return mb_datastream(*this); } -} // namespace eosio +} // namespace fc diff --git a/libraries/fc/include/fc/scoped_exit.hpp b/libraries/fc/include/fc/scoped_exit.hpp index 2ae0db18df9..6bea3cf1091 100644 --- a/libraries/fc/include/fc/scoped_exit.hpp +++ b/libraries/fc/include/fc/scoped_exit.hpp @@ -7,13 +7,16 @@ namespace fc { public: template scoped_exit( C&& c ):callback( std::forward(c) ){} - scoped_exit( scoped_exit&& mv ):callback( std::move( mv.callback ) ){} + + scoped_exit( scoped_exit&& mv ) + :callback( std::move( mv.callback ) ),canceled(mv.canceled) + { + mv.canceled = true; + } scoped_exit( const scoped_exit& ) = delete; scoped_exit& operator=( const scoped_exit& ) = delete; - void cancel() { canceled = true; } - ~scoped_exit() { if (!canceled) try { callback(); } catch( ... ) {} @@ -29,6 +32,9 @@ namespace fc { return *this; } + + void cancel() { canceled = true; } + private: Callback callback; bool canceled = false; diff --git a/libraries/fc/include/fc/utility.hpp b/libraries/fc/include/fc/utility.hpp index d736ca44dde..fdaf274638e 100644 --- a/libraries/fc/include/fc/utility.hpp +++ b/libraries/fc/include/fc/utility.hpp @@ -31,11 +31,9 @@ namespace fc { template struct deduce { typedef T type; }; template struct deduce{ typedef T type; }; - template - typename fc::remove_reference::type&& move( T&& t ) { return static_cast::type&&>(t); } - template - inline T&& forward( U&& u ) { return static_cast(u); } + using std::move; + using std::forward; struct true_type { enum _value { value = 1 }; }; struct false_type { enum _value { value = 0 }; }; diff --git a/libraries/fc/src/crypto/public_key.cpp b/libraries/fc/src/crypto/public_key.cpp index a258c7f37b9..4b9f2939714 100644 --- a/libraries/fc/src/crypto/public_key.cpp +++ b/libraries/fc/src/crypto/public_key.cpp @@ -56,6 +56,18 @@ namespace fc { namespace crypto { :_storage(parse_base58(base58str)) {} + struct is_valid_visitor : public fc::visitor { + template< typename KeyType > + bool operator()( const KeyType& key )const { + return key.valid(); + } + }; + + bool public_key::valid()const + { + return _storage.visit(is_valid_visitor()); + } + public_key::operator std::string() const { auto data_str = _storage.visit(base58str_visitor()); diff --git a/libraries/testing/CMakeLists.txt b/libraries/testing/CMakeLists.txt index 5ccacde916a..408c92f0644 100644 --- a/libraries/testing/CMakeLists.txt +++ b/libraries/testing/CMakeLists.txt @@ -7,7 +7,7 @@ add_library( eosio_testing ${HEADERS} ) -target_link_libraries( eosio_testing eosio_chain chain_plugin eos_utilities fc chainbase Logging IR WAST WASM Runtime ) +target_link_libraries( eosio_testing eosio_chain eos_utilities fc chainbase Logging IR WAST WASM Runtime ) target_include_directories( eosio_testing PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../wasm-jit/Include" @@ -19,3 +19,7 @@ add_dependencies( eosio_testing eosio.bios ) if(MSVC) set_source_files_properties( db_init.cpp db_block.cpp database.cpp block_log.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) + +add_executable( chain_tester test.cpp ) +target_link_libraries( chain_tester + PRIVATE eosio_testing eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ${Intl_LIBRARIES} ) diff --git a/tests/include/eosio/chain/test/chainbase_fixture.hpp b/libraries/testing/include/eosio/testing/chainbase_fixture.hpp similarity index 88% rename from tests/include/eosio/chain/test/chainbase_fixture.hpp rename to libraries/testing/include/eosio/testing/chainbase_fixture.hpp index 4cdf47b2097..a876923b69b 100644 --- a/tests/include/eosio/chain/test/chainbase_fixture.hpp +++ b/libraries/testing/include/eosio/testing/chainbase_fixture.hpp @@ -3,7 +3,7 @@ #include -namespace eosio { namespace chain { namespace test { +namespace eosio { namespace testing { /** * Utility class to create and tear down a temporary chainbase::database using RAII @@ -28,4 +28,4 @@ struct chainbase_fixture { std::unique_ptr _db; }; -} } } // eosio::chain::test \ No newline at end of file +} } // eosio::testing \ No newline at end of file diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 7799bfe0425..7ce0b0f3799 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -1,8 +1,10 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include #include -#include #include #include @@ -56,6 +58,10 @@ namespace eosio { namespace testing { fc::variant_object filter_fields(const fc::variant_object& filter, const fc::variant_object& value); + void copy_row(const chain::key_value_object& obj, vector& data); + + bool expect_assert_message(const fc::exception& ex, string expected); + /** * @class tester * @brief provides utility function to simplify the creation of unit tests @@ -66,38 +72,66 @@ namespace eosio { namespace testing { static const uint32_t DEFAULT_EXPIRATION_DELTA = 6; - void init(bool push_genesis = true, chain_controller::runtime_limits limits = chain_controller::runtime_limits()); - void init(chain_controller::controller_config config); + static const uint32_t DEFAULT_BILLED_CPU_TIME_US = 2000; + + void init(bool push_genesis = true); + void init(controller::config config); void close(); void open(); bool is_same_chain( base_tester& other ); - virtual signed_block produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = skip_missed_block_penalty ) = 0; - void produce_blocks( uint32_t n = 1 ); + virtual signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ ) = 0; + virtual signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ ) = 0; + void produce_blocks( uint32_t n = 1, bool empty = false ); void produce_blocks_until_end_of_round(); - signed_block push_block(signed_block b); - transaction_trace push_transaction( packed_transaction& trx, uint32_t skip_flag = skip_nothing ); - transaction_trace push_transaction( signed_transaction& trx, uint32_t skip_flag = skip_nothing ); - action_result push_action(action&& cert_act, uint64_t authorizer); - - transaction_trace push_action( const account_name& code, const action_name& acttype, const account_name& actor, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 ); - transaction_trace push_action( const account_name& code, const action_name& acttype, const vector& actors, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 ); - transaction_trace push_action( const account_name& code, const action_name& acttype, const vector& auths, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 ); + signed_block_ptr push_block(signed_block_ptr b); + + transaction_trace_ptr push_transaction( packed_transaction& trx, fc::time_point deadline = fc::time_point::maximum(), uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US ); + transaction_trace_ptr push_transaction( signed_transaction& trx, fc::time_point deadline = fc::time_point::maximum(), uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US ); + action_result push_action(action&& cert_act, uint64_t authorizer); // TODO/QUESTION: Is this needed? + + transaction_trace_ptr push_action( const account_name& code, + const action_name& acttype, + const account_name& actor, + const variant_object& data, + uint32_t expiration = DEFAULT_EXPIRATION_DELTA, + uint32_t delay_sec = 0 ); + transaction_trace_ptr push_action( const account_name& code, + const action_name& acttype, + const vector& actors, + const variant_object& data, + uint32_t expiration = DEFAULT_EXPIRATION_DELTA, + uint32_t delay_sec = 0 ); + transaction_trace_ptr push_action( const account_name& code, + const action_name& acttype, + const vector& auths, + const variant_object& data, + uint32_t expiration = DEFAULT_EXPIRATION_DELTA, + uint32_t delay_sec = 0 ); + + + action get_action( account_name code, action_name acttype, vector auths, + const variant_object& data )const; void set_transaction_headers(signed_transaction& trx, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0)const; - vector create_accounts( vector names, bool multisig = false ) { - vector traces; + vector create_accounts( vector names, + bool multisig = false, + bool include_code = true + ) + { + vector traces; traces.reserve(names.size()); - for( auto n : names ) traces.emplace_back(create_account(n, config::system_account_name, multisig )); + for( auto n : names ) traces.emplace_back( create_account( n, config::system_account_name, multisig, include_code ) ); return traces; } - void push_genesis_block(); - producer_schedule_type set_producers(const vector& producer_names, const uint32_t version = 0); + void push_genesis_block(); + vector get_producer_keys( const vector& producer_names )const; + transaction_trace_ptr set_producers(const vector& producer_names); void link_authority( account_name account, account_name code, permission_name req, action_name type = "" ); void unlink_authority( account_name account, account_name code, action_name type = "" ); @@ -108,29 +142,33 @@ namespace eosio { namespace testing { void delete_authority( account_name account, permission_name perm, const vector& auths, const vector& keys ); void delete_authority( account_name account, permission_name perm ); - transaction_trace create_account( account_name name, account_name creator = config::system_account_name, bool multisig = false ); + transaction_trace_ptr create_account( account_name name, + account_name creator = config::system_account_name, + bool multisig = false, + bool include_code = true + ); - transaction_trace push_reqauth( account_name from, const vector& auths, const vector& keys ); - transaction_trace push_reqauth(account_name from, string role, bool multi_sig = false); + transaction_trace_ptr push_reqauth( account_name from, const vector& auths, const vector& keys ); + transaction_trace_ptr push_reqauth(account_name from, string role, bool multi_sig = false); // use when just want any old non-context free action - transaction_trace push_dummy(account_name from, const string& v = "blah"); - transaction_trace transfer( account_name from, account_name to, asset amount, string memo, account_name currency ); - transaction_trace transfer( account_name from, account_name to, string amount, string memo, account_name currency ); - transaction_trace issue( account_name to, string amount, account_name currency ); + transaction_trace_ptr push_dummy(account_name from, const string& v = "blah"); + transaction_trace_ptr transfer( account_name from, account_name to, asset amount, string memo, account_name currency ); + transaction_trace_ptr transfer( account_name from, account_name to, string amount, string memo, account_name currency ); + transaction_trace_ptr issue( account_name to, string amount, account_name currency ); template const auto& get(const chainbase::oid< ObjectType >& key) { - return control->get_database().get(key); + return control->db().get(key); } template const auto& get( Args&&... args ) { - return control->get_database().get( forward(args)... ); + return control->db().get( forward(args)... ); } template const auto* find( Args&&... args ) { - return control->get_database().find( forward(args)... ); + return control->db().find( forward(args)... ); } template< typename KeyType = fc::ecc::private_key_shim > @@ -143,13 +181,9 @@ namespace eosio { namespace testing { return get_private_key( keyname, role ).get_public_key(); } - void set_code( account_name name, const char* wast ); - void set_code( account_name name, const vector wasm ); - void set_abi( account_name name, const char* abi_json ); - - - unique_ptr control; - std::map block_signing_private_keys; + void set_code( account_name name, const char* wast, const private_key_type* signer = nullptr ); + void set_code( account_name name, const vector wasm, const private_key_type* signer = nullptr ); + void set_abi( account_name name, const char* abi_json, const private_key_type* signer = nullptr ); bool chain_has_transaction( const transaction_id_type& txid ) const; const transaction_receipt& get_transaction_receipt( const transaction_id_type& txid ) const; @@ -158,78 +192,97 @@ namespace eosio { namespace testing { const symbol& asset_symbol, const account_name& account ) const; - vector get_row_by_account( uint64_t code, uint64_t scope, uint64_t table, const account_name& act ); + vector get_row_by_account( uint64_t code, uint64_t scope, uint64_t table, const account_name& act ); - static vector to_uint8_vector(const string& s); + static vector to_uint8_vector(const string& s); - static vector to_uint8_vector(uint64_t x); + static vector to_uint8_vector(uint64_t x); - static uint64_t to_uint64(fc::variant x); + static uint64_t to_uint64(fc::variant x); - static string to_string(fc::variant x); + static string to_string(fc::variant x); - static action_result success() { return string(); } + static action_result success() { return string(); } - static action_result error(const string& msg) { return msg; } + static action_result error(const string& msg) { return msg; } + + auto get_resolver() { + return [this]( const account_name& name ) -> optional { + try { + const auto& accnt = control->db().get( name ); + abi_def abi; + if( abi_serializer::to_abi( accnt.abi, abi )) { + return abi_serializer( abi ); + } + return optional(); + } FC_RETHROW_EXCEPTIONS( error, "Failed to find or parse ABI for ${name}", ("name", name)) + }; + } - auto get_resolver() { - return [this](const account_name &name) -> optional { - try { - const auto &accnt = control->get_database().get(name); - contracts::abi_def abi; - if (contracts::abi_serializer::to_abi(accnt.abi, abi)) { - return contracts::abi_serializer(abi); - } - return optional(); - } FC_RETHROW_EXCEPTIONS(error, "Failed to find or parse ABI for ${name}", ("name", name)) - }; - } + void sync_with(base_tester& other); - void sync_with(base_tester& other); + const table_id_object* find_table( name code, name scope, name table ); - const contracts::table_id_object* find_table( name code, name scope, name table ); + // method treats key as a name type, if this is not appropriate in your case, pass require == false and report the correct behavior + template + bool get_table_entry(Object& obj, account_name code, account_name scope, account_name table, uint64_t key, bool require = true) { + auto* maybe_tid = find_table(code, scope, table); + if( maybe_tid == nullptr ) { + BOOST_FAIL( "table for code=\"" + code.to_string() + + "\" scope=\"" + scope.to_string() + + "\" table=\"" + table.to_string() + + "\" does not exist" ); + } - // method treats key as a name type, if this is not appropriate in your case, pass require == false and report the correct behavior - template - bool get_table_entry(Object& obj, account_name code, account_name scope, account_name table, uint64_t key, bool require = true) { - auto* maybe_tid = find_table(code, scope, table); - if(maybe_tid == nullptr) - BOOST_FAIL("table for code=\"" + code.to_string() + "\" scope=\"" + scope.to_string() + "\" table=\"" + table.to_string() + "\" does not exist"); + auto* o = control->db().find(boost::make_tuple(maybe_tid->id, key)); + if( o == nullptr ) { + if( require ) + BOOST_FAIL("object does not exist for primary_key=\"" + name(key).to_string() + "\""); - auto* o = control->get_database().find(boost::make_tuple(maybe_tid->id, key)); - if(o == nullptr) { - if (require) - BOOST_FAIL("object does not exist for primary_key=\"" + name(key).to_string() + "\""); + return false; + } - return false; - } + fc::raw::unpack(o->value.data(), o->value.size(), obj); + return true; + } - fc::raw::unpack(o->value.data(), o->value.size(), obj); - return true; - } + protected: + signed_block_ptr _produce_block( fc::microseconds skip_time, bool skip_pending_trxs = false, uint32_t skip_flag = 0 ); + void _start_block(fc::time_point block_time); - protected: - signed_block _produce_block( fc::microseconds skip_time, uint32_t skip_flag); + // Fields: + protected: + // tempdir field must come before control so that during destruction the tempdir is deleted only after controller finishes fc::temp_directory tempdir; - chain_controller::controller_config cfg; + public: + unique_ptr control; + std::map block_signing_private_keys; + protected: + controller::config cfg; map chain_transactions; + map last_produced_block; }; class tester : public base_tester { public: - tester(bool push_genesis, chain_controller::runtime_limits limits = chain_controller::runtime_limits()) { - init(push_genesis, limits); + tester(bool push_genesis) { + init(push_genesis); } - tester(chain_controller::runtime_limits limits = chain_controller::runtime_limits()) { - init(true, limits); + tester() { + init(true); } - tester(chain_controller::controller_config config) { + tester(controller::config config) { init(config); } - signed_block produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = skip_missed_block_penalty )override { - return _produce_block(skip_time, skip_flag); + signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ )override { + return _produce_block(skip_time, false, skip_flag); + } + + signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0/*skip_missed_block_penalty*/ )override { + control->abort_block(); + return _produce_block(skip_time, true, skip_flag); } bool validate() { return true; } @@ -238,18 +291,22 @@ namespace eosio { namespace testing { class validating_tester : public base_tester { public: virtual ~validating_tester() { - produce_block(); - BOOST_REQUIRE_EQUAL( validate(), true ); + try { + produce_block(); + BOOST_REQUIRE_EQUAL( validate(), true ); + } catch( const fc::exception& e ) { + wdump((e.to_detail_string())); + } } - validating_tester(chain_controller::runtime_limits limits = chain_controller::runtime_limits()) { - chain_controller::controller_config vcfg; - vcfg.block_log_dir = tempdir.path() / "blocklog"; - vcfg.shared_memory_dir = tempdir.path() / "shared"; + controller::config vcfg; + + validating_tester() { + vcfg.block_log_dir = tempdir.path() / "vblocklog"; + vcfg.shared_memory_dir = tempdir.path() / "vshared"; vcfg.shared_memory_size = 1024*1024*8; vcfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000"); vcfg.genesis.initial_key = get_public_key( config::system_account_name, "active" ); - vcfg.limits = limits; for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) { if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--binaryen")) @@ -258,67 +315,133 @@ namespace eosio { namespace testing { vcfg.wasm_runtime = chain::wasm_interface::vm_type::wavm; } - validating_node = std::make_unique(vcfg); - init(true, limits); + + validating_node = std::make_unique(vcfg); + validating_node->startup(); + + init(true); } - validating_tester(chain_controller::controller_config config) { - validating_node = std::make_unique(config); + /* + validating_tester(controller::config config) { + validating_node = std::make_unique(config); init(config); } + */ - signed_block produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = skip_missed_block_penalty )override { - auto sb = _produce_block(skip_time, skip_flag | 2); + signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0 /*skip_missed_block_penalty*/ )override { + auto sb = _produce_block(skip_time, false, skip_flag | 2); validating_node->push_block( sb ); + + return sb; + } + + signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0 /*skip_missed_block_penalty*/ )override { + control->abort_block(); + auto sb = _produce_block(skip_time, true, skip_flag | 2); + validating_node->push_block( sb ); + + + return sb; } bool validate() { - auto hbh = control->head_block_header(); - auto vn_hbh = validating_node->head_block_header(); - return control->head_block_id() == validating_node->head_block_id() && + + + auto hbh = control->head_block_state()->header; + auto vn_hbh = validating_node->head_block_state()->header; + bool ok = control->head_block_id() == validating_node->head_block_id() && hbh.previous == vn_hbh.previous && hbh.timestamp == vn_hbh.timestamp && hbh.transaction_mroot == vn_hbh.transaction_mroot && hbh.action_mroot == vn_hbh.action_mroot && - hbh.block_mroot == vn_hbh.block_mroot && hbh.producer == vn_hbh.producer; - } - unique_ptr validating_node; - }; + validating_node.reset(); + validating_node = std::make_unique(vcfg); + validating_node->startup(); - /** - * Utility predicate to check whether an FC_ASSERT message ends with a given string - */ - struct assert_message_ends_with { - assert_message_ends_with(string expected) - :expected(expected) - {} - - bool operator()( const fc::exception& ex ) { - auto message = ex.get_log().at(0).get_message(); - return boost::algorithm::ends_with(message, expected); + return ok; } - string expected; + unique_ptr validating_node; }; /** - * Utility predicate to check whether an FC_ASSERT message contains a given string + * Utility predicate to check whether an fc::exception message is equivalent to a given string */ - struct assert_message_contains { - assert_message_contains(string expected) - :expected(expected) - {} - - bool operator()( const fc::exception& ex ) { - auto message = ex.get_log().at(0).get_message(); - return boost::algorithm::contains(message, expected); - } + struct fc_exception_message_is { + fc_exception_message_is( const string& msg ) + : expected( msg ) {} + + bool operator()( const fc::exception& ex ); string expected; }; + /** + * Utility predicate to check whether an fc::exception message starts with a given string + */ + struct fc_exception_message_starts_with { + fc_exception_message_starts_with( const string& msg ) + : expected( msg ) {} + + bool operator()( const fc::exception& ex ); + + string expected; + }; + + /** + * Utility predicate to check whether an fc::assert_exception message is equivalent to a given string + */ + struct fc_assert_exception_message_is { + fc_assert_exception_message_is( const string& msg ) + : expected( msg ) {} + + bool operator()( const fc::assert_exception& ex ); + + string expected; + }; + + /** + * Utility predicate to check whether an fc::assert_exception message starts with a given string + */ + struct fc_assert_exception_message_starts_with { + fc_assert_exception_message_starts_with( const string& msg ) + : expected( msg ) {} + + bool operator()( const fc::assert_exception& ex ); + + string expected; + }; + + /** + * Utility predicate to check whether an eosio_assert message is equivalent to a given string + */ + struct eosio_assert_message_is { + eosio_assert_message_is( const string& msg ) + : expected( "assertion failed: " ) { + expected.append( msg ); + } + + bool operator()( const fc::assert_exception& ex ); + + string expected; + }; + + /** + * Utility predicate to check whether an eosio_assert message starts with a given string + */ + struct eosio_assert_message_starts_with { + eosio_assert_message_starts_with( const string& msg ) + : expected( "assertion failed: " ) { + expected.append( msg ); + } + + bool operator()( const fc::assert_exception& ex ); + + string expected; + }; } } /// eosio::testing diff --git a/libraries/testing/test.cpp b/libraries/testing/test.cpp new file mode 100644 index 00000000000..0aac67bdf40 --- /dev/null +++ b/libraries/testing/test.cpp @@ -0,0 +1,203 @@ +#include +#include + +#include +#include + +using namespace eosio::chain; +using namespace eosio::testing; + +private_key_type get_private_key( name keyname, string role ) { + return private_key_type::regenerate(fc::sha256::hash(string(keyname)+role)); +} + +public_key_type get_public_key( name keyname, string role ){ + return get_private_key( keyname, role ).get_public_key(); +} + +int main( int argc, char** argv ) { + try { try { + tester c; + c.produce_block(); + c.produce_block(); + auto r = c.create_accounts( {N(dan),N(sam),N(pam)} ); + wdump((fc::json::to_pretty_string(r))); + c.produce_block(); + auto res = c.set_producers( {N(dan),N(sam),N(pam)} ); + vector sch = { {N(dan),get_public_key(N(dan), "active")}, + {N(sam),get_public_key(N(sam), "active")}, + {N(pam),get_public_key(N(pam), "active")}}; + wdump((fc::json::to_pretty_string(res))); + wlog("set producer schedule to [dan,sam,pam]"); + c.produce_blocks(30); + + auto r2 = c.create_accounts( {N(eosio.token)} ); + wdump((fc::json::to_pretty_string(r2))); + c.set_code( N(eosio.token), eosio_token_wast ); + c.set_abi( N(eosio.token), eosio_token_abi ); + c.produce_blocks(10); + + + auto cr = c.push_action( N(eosio.token), N(create), N(eosio.token), mutable_variant_object() + ("issuer", "eosio" ) + ("maximum_supply", "10000000.0000 EOS") + ("can_freeze", 0) + ("can_recall", 0) + ("can_whitelist", 0) + ); + + wdump((fc::json::to_pretty_string(cr))); + + cr = c.push_action( N(eosio.token), N(issue), N(eosio), mutable_variant_object() + ("to", "dan" ) + ("quantity", "100.0000 EOS") + ("memo", "") + ); + + wdump((fc::json::to_pretty_string(cr))); + + + tester c2; + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.control->push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + wlog( "c1 blocks:" ); + c.produce_blocks(3); + signed_block_ptr b; + b = c.produce_block(); + account_name expected_producer = N(dan); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + + b = c.produce_block(); + expected_producer = N(sam); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + c.produce_blocks(10); + c.create_accounts( {N(cam)} ); + c.set_producers( {N(dan),N(sam),N(pam),N(cam)} ); + wlog("set producer schedule to [dan,sam,pam,cam]"); + c.produce_block(); + // The next block should be produced by pam. + + // Sync second chain with first chain. + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.control->push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + // Now sam and pam go on their own fork while dan is producing blocks by himself. + + wlog( "sam and pam go off on their own fork on c2 while dan produces blocks by himself in c1" ); + auto fork_block_num = c.control->head_block_num(); + + wlog( "c2 blocks:" ); + c2.produce_blocks(12); // pam produces 12 blocks + b = c2.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // sam skips over dan's blocks + expected_producer = N(sam); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + c2.produce_blocks(11 + 12); + + + wlog( "c1 blocks:" ); + b = c.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // dan skips over pam's blocks + expected_producer = N(dan); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + c.produce_blocks(11); + + // dan on chain 1 now gets all of the blocks from chain 2 which should cause fork switch + wlog( "push c2 blocks to c1" ); + for( uint32_t start = fork_block_num + 1, end = c2.control->head_block_num(); start <= end; ++start ) { + auto fb = c2.control->fetch_block_by_number( start ); + c.control->push_block( fb ); + } + wlog( "end push c2 blocks to c1" ); + + wlog( "c1 blocks:" ); + c.produce_blocks(24); + + b = c.produce_block(); // Switching active schedule to version 2 happens in this block. + expected_producer = N(pam); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + + b = c.produce_block(); + expected_producer = N(cam); + FC_ASSERT( b->producer == expected_producer, + "expected block ${n} to be produced by ${expected_producer} but was instead produced by ${actual_producer}", + ("n", b->block_num())("expected_producer", expected_producer.to_string())("actual_producer", b->producer.to_string()) ); + c.produce_blocks(10); + + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.control->push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + // Now with four block producers active and two identical chains (for now), + // we can test out the case that would trigger the bug in the old fork db code: + fork_block_num = c.control->head_block_num(); + wlog( "cam and dan go off on their own fork on c1 while sam and pam go off on their own fork on c2" ); + wlog( "c1 blocks:" ); + c.produce_blocks(12); // dan produces 12 blocks + c.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // cam skips over sam and pam's blocks + c.produce_blocks(23); // cam finishes the remaining 11 blocks then dan produces his 12 blocks + wlog( "c2 blocks:" ); + c2.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // pam skips over dan and sam's blocks + c2.produce_blocks(11); // pam finishes the remaining 11 blocks + c2.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // sam skips over cam and dan's blocks + c2.produce_blocks(11); // sam finishes the remaining 11 blocks + + wlog( "now cam and dan rejoin sam and pam on c2" ); + c2.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // cam skips over pam's blocks (this block triggers a block on this branch to become irreversible) + c2.produce_blocks(11); // cam produces the remaining 11 blocks + b = c2.produce_block(); // dan produces a block + + // a node on chain 1 now gets all but the last block from chain 2 which should cause a fork switch + wlog( "push c2 blocks (except for the last block by dan) to c1" ); + for( uint32_t start = fork_block_num + 1, end = c2.control->head_block_num() - 1; start <= end; ++start ) { + auto fb = c2.control->fetch_block_by_number( start ); + c.control->push_block( fb ); + } + wlog( "end push c2 blocks to c1" ); + wlog( "now push dan's block to c1 but first corrupt it so it is a bad block" ); + auto bad_block = *b; + //bad_block.producer = N(sam); + //bad_block.schedule_version = 12; + bad_block.transaction_mroot = bad_block.previous; + try { + c.control->push_block( std::make_shared(bad_block) ); + } catch( const fc::exception& e ) { + elog(e.to_detail_string()); + } + + c.produce_blocks(3); + + cr = c.push_action( N(eosio.token), N(issue), N(eosio), mutable_variant_object() + ("to", "unregistered" ) + ("quantity", "100.0000 EOS") + ("memo", "") + ); + + wdump((fc::json::to_pretty_string(cr))); + + + } FC_CAPTURE_AND_RETHROW() + } catch ( const fc::exception& e ) { + edump((e.to_detail_string())); + } +} diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index ebab8860bcd..8ea7f94ecbb 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -1,28 +1,19 @@ #include +#include #include -#include #include -#include -#include -#include -#include +#include #include #include -#include -#include -#include - -#include "WAST/WAST.h" -#include "WASM/WASM.h" -#include "IR/Module.h" -#include "IR/Validate.h" - -using namespace eosio::chain::contracts; - namespace eosio { namespace testing { + bool expect_assert_message(const fc::exception& ex, string expected) { + BOOST_TEST_MESSAGE("LOG : " << "expected: " << expected << ", actual: " << ex.get_log().at(0).get_message()); + return (ex.get_log().at(0).get_message().find(expected) != std::string::npos); + } + fc::variant_object filter_fields(const fc::variant_object& filter, const fc::variant_object& value) { fc::mutable_variant_object res; for( auto& entry : filter ) { @@ -32,31 +23,30 @@ namespace eosio { namespace testing { return res; } + void copy_row(const chain::key_value_object& obj, vector& data) { + data.resize( obj.value.size() ); + memcpy( data.data(), obj.value.data(), obj.value.size() ); + } + bool base_tester::is_same_chain( base_tester& other ) { - auto hbh = control->head_block_header(); - auto vn_hbh = other.control->head_block_header(); - return control->head_block_id() == other.control->head_block_id() && - hbh.previous == vn_hbh.previous && - hbh.timestamp == vn_hbh.timestamp && - hbh.transaction_mroot == vn_hbh.transaction_mroot && - hbh.action_mroot == vn_hbh.action_mroot && - hbh.block_mroot == vn_hbh.block_mroot && - hbh.producer == vn_hbh.producer; - } - void base_tester::init(bool push_genesis, chain_controller::runtime_limits limits) { + return control->head_block_id() == other.control->head_block_id(); + } + + void base_tester::init(bool push_genesis) { cfg.block_log_dir = tempdir.path() / "blocklog"; cfg.shared_memory_dir = tempdir.path() / "shared"; cfg.shared_memory_size = 1024*1024*8; cfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000"); cfg.genesis.initial_key = get_public_key( config::system_account_name, "active" ); - cfg.limits = limits; for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) { if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--binaryen")) cfg.wasm_runtime = chain::wasm_interface::vm_type::binaryen; else if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--wavm")) cfg.wasm_runtime = chain::wasm_interface::vm_type::wavm; + else + cfg.wasm_runtime = chain::wasm_interface::vm_type::binaryen; } open(); @@ -66,7 +56,7 @@ namespace eosio { namespace testing { } - void base_tester::init(chain_controller::controller_config config) { + void base_tester::init(controller::config config) { cfg = config; open(); } @@ -79,58 +69,119 @@ namespace eosio { namespace testing { void base_tester::open() { - control.reset( new chain_controller(cfg) ); + control.reset( new controller(cfg) ); + control->startup(); chain_transactions.clear(); - control->applied_block.connect([this]( const block_trace& trace ){ - for( const auto& region : trace.block.regions) { - for( const auto& cycle : region.cycles_summary ) { - for ( const auto& shard : cycle ) { - for( const auto& receipt: shard.transactions ) { - chain_transactions.emplace(receipt.id, receipt); - } - } - } - } + control->accepted_block.connect([this]( const block_state_ptr& block_state ){ + FC_ASSERT( block_state->block ); + for( const auto& receipt : block_state->block->transactions ) { + if( receipt.trx.contains() ) { + auto &pt = receipt.trx.get(); + chain_transactions[pt.get_transaction().id()] = receipt; + } else { + auto& id = receipt.trx.get(); + chain_transactions[id] = receipt; + } + } }); } - signed_block base_tester::push_block(signed_block b) { - control->push_block(b, 2); + signed_block_ptr base_tester::push_block(signed_block_ptr b) { + control->abort_block(); + control->push_block(b); + + auto itr = last_produced_block.find(b->producer); + if (itr == last_produced_block.end() || block_header::num_from_id(b->id()) > block_header::num_from_id(itr->second)) { + last_produced_block[b->producer] = b->id(); + } + return b; } - signed_block base_tester::_produce_block( fc::microseconds skip_time, uint32_t skip_flag) { + signed_block_ptr base_tester::_produce_block( fc::microseconds skip_time, bool skip_pending_trxs, uint32_t skip_flag) { + auto head = control->head_block_state(); auto head_time = control->head_block_time(); auto next_time = head_time + skip_time; - uint32_t slot = control->get_slot_at_time( next_time ); - auto sch_pro = control->get_scheduled_producer(slot); - const auto& sch_pro_signing_key = control->get_producer(sch_pro).signing_key; + if( !control->pending_block_state() || control->pending_block_state()->header.timestamp != next_time ) { + _start_block( next_time ); + } + + auto producer = control->head_block_state()->get_scheduled_producer(next_time); private_key_type priv_key; // Check if signing private key exist in the list - auto private_key_itr = block_signing_private_keys.find( sch_pro_signing_key ); + auto private_key_itr = block_signing_private_keys.find( producer.block_signing_key ); if( private_key_itr == block_signing_private_keys.end() ) { // If it's not found, default to active k1 key - priv_key = get_private_key( sch_pro, "active" ); + priv_key = get_private_key( producer.producer_name, "active" ); } else { priv_key = private_key_itr->second; } - return control->generate_block( next_time, sch_pro, priv_key, skip_flag ); + if( !skip_pending_trxs ) { + auto unapplied_trxs = control->get_unapplied_transactions(); + for (const auto& trx : unapplied_trxs ) { + auto trace = control->push_transaction(trx, fc::time_point::maximum()); + if(trace->except) { + trace->except->dynamic_rethrow_exception(); + } + } + + vector scheduled_trxs; + while( (scheduled_trxs = control->get_scheduled_transactions() ).size() > 0 ) { + for (const auto& trx : scheduled_trxs ) { + auto trace = control->push_scheduled_transaction(trx, fc::time_point::maximum()); + if(trace->except) { + trace->except->dynamic_rethrow_exception(); + } + } + } + } + + + + control->finalize_block(); + control->sign_block( [&]( digest_type d ) { + return priv_key.sign(d); + }); + + control->commit_block(); + control->log_irreversible_blocks(); + last_produced_block[control->head_block_state()->header.producer] = control->head_block_state()->id; + + _start_block( next_time + fc::microseconds(config::block_interval_us)); + return control->head_block_state()->block; } - void base_tester::produce_blocks( uint32_t n ) { - for( uint32_t i = 0; i < n; ++i ) - produce_block(); + void base_tester::_start_block(fc::time_point block_time) { + auto head_block_number = control->head_block_num(); + auto producer = control->head_block_state()->get_scheduled_producer(block_time); + + auto last_produced_block_num = control->last_irreversible_block_num(); + auto itr = last_produced_block.find(producer.producer_name); + if (itr != last_produced_block.end()) { + last_produced_block_num = block_header::num_from_id(itr->second); + } + + control->abort_block(); + control->start_block( block_time, head_block_number - last_produced_block_num ); + } + + + void base_tester::produce_blocks( uint32_t n, bool empty ) { + if( empty ) { + for( uint32_t i = 0; i < n; ++i ) + produce_empty_block(); + } else { + for( uint32_t i = 0; i < n; ++i ) + produce_block(); + } } void base_tester::produce_blocks_until_end_of_round() { - uint64_t blocks_per_round; - while(true) { - blocks_per_round = control->get_global_properties().active_producers.producers.size() * config::producer_repetitions; + while( control->pending_block_state()->has_pending_producers() ) { produce_block(); - if (control->head_block_num() % blocks_per_round == (blocks_per_round - 1) ) break; } } @@ -140,30 +191,50 @@ namespace eosio { namespace testing { trx.set_reference_block( control->head_block_id() ); trx.max_net_usage_words = 0; // No limit - trx.max_kcpu_usage = 0; // No limit + trx.max_cpu_usage_ms = 0; // No limit trx.delay_sec = delay_sec; } - transaction_trace base_tester::create_account( account_name a, account_name creator, bool multisig ) { + transaction_trace_ptr base_tester::create_account( account_name a, account_name creator, bool multisig, bool include_code ) { signed_transaction trx; set_transaction_headers(trx); authority owner_auth; - if (multisig) { + if( multisig ) { // multisig between account's owner key and creators active permission owner_auth = authority(2, {key_weight{get_public_key( a, "owner" ), 1}}, {permission_level_weight{{creator, config::active_name}, 1}}); } else { owner_auth = authority( get_public_key( a, "owner" ) ); } + authority active_auth( get_public_key( a, "active" ) ); + + auto sort_permissions = []( authority& auth ) { + std::sort( auth.accounts.begin(), auth.accounts.end(), + []( const permission_level_weight& lhs, const permission_level_weight& rhs ) { + return lhs.permission < rhs.permission; + } + ); + }; + + if( include_code ) { + FC_ASSERT( owner_auth.threshold <= std::numeric_limits::max(), "threshold is too high" ); + FC_ASSERT( active_auth.threshold <= std::numeric_limits::max(), "threshold is too high" ); + owner_auth.accounts.push_back( permission_level_weight{ {a, config::eosio_code_name}, + static_cast(owner_auth.threshold) } ); + sort_permissions(owner_auth); + active_auth.accounts.push_back( permission_level_weight{ {a, config::eosio_code_name}, + static_cast(active_auth.threshold) } ); + sort_permissions(active_auth); + } + trx.actions.emplace_back( vector{{creator,config::active_name}}, - contracts::newaccount{ + newaccount{ .creator = creator, .name = a, .owner = owner_auth, - .active = authority( get_public_key( a, "active" ) ), - .recovery = authority( get_public_key( a, "recovery" ) ), + .active = active_auth, }); set_transaction_headers(trx); @@ -171,15 +242,39 @@ namespace eosio { namespace testing { return push_transaction( trx ); } - transaction_trace base_tester::push_transaction( packed_transaction& trx, uint32_t skip_flag ) { try { - return control->push_transaction( trx, skip_flag ); + transaction_trace_ptr base_tester::push_transaction( packed_transaction& trx, + fc::time_point deadline, + uint32_t billed_cpu_time_us + ) + { try { + if( !control->pending_block_state() ) + _start_block(control->head_block_time() + fc::microseconds(config::block_interval_us)); + auto r = control->push_transaction( std::make_shared(trx), deadline, billed_cpu_time_us ); + if( r->except_ptr ) std::rethrow_exception( r->except_ptr ); + if( r->except ) throw *r->except; + return r; } FC_CAPTURE_AND_RETHROW( (transaction_header(trx.get_transaction())) ) } - transaction_trace base_tester::push_transaction( signed_transaction& trx, uint32_t skip_flag ) { try { - auto ptrx = packed_transaction(trx); - return push_transaction( ptrx, skip_flag ); - } FC_CAPTURE_AND_RETHROW( (transaction_header(trx)) ) } + transaction_trace_ptr base_tester::push_transaction( signed_transaction& trx, + fc::time_point deadline, + uint32_t billed_cpu_time_us + ) + { try { + if( !control->pending_block_state() ) + _start_block(control->head_block_time() + fc::microseconds(config::block_interval_us)); + auto c = packed_transaction::none; + + if( fc::raw::pack_size(trx) > 1000 ) + { + wdump((fc::raw::pack_size(trx))); + c = packed_transaction::zlib; + } + auto r = control->push_transaction( std::make_shared(trx,c), deadline, billed_cpu_time_us ); + if( r->except_ptr ) std::rethrow_exception( r->except_ptr ); + if( r->except) throw *r->except; + return r; + } FC_CAPTURE_AND_RETHROW( (transaction_header(trx))(billed_cpu_time_us) ) } typename base_tester::action_result base_tester::push_action(action&& act, uint64_t authorizer) { signed_transaction trx; @@ -194,55 +289,70 @@ namespace eosio { namespace testing { try { push_transaction(trx); } catch (const fc::exception& ex) { - return error(ex.top_message()); + edump((ex.to_detail_string())); + return error(ex.top_message()); // top_message() is assumed by many tests; otherwise they fail + //return error(ex.to_detail_string()); } produce_block(); BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); return success(); } + transaction_trace_ptr base_tester::push_action( const account_name& code, + const action_name& acttype, + const account_name& actor, + const variant_object& data, + uint32_t expiration, + uint32_t delay_sec + ) - transaction_trace base_tester::push_action( const account_name& code, - const action_name& acttype, - const account_name& actor, - const variant_object& data, - uint32_t expiration, - uint32_t delay_sec) - - { try { + { vector auths; - auths.push_back(permission_level{actor, config::active_name}); - return push_action(code, acttype, auths, data, expiration, delay_sec); - } FC_CAPTURE_AND_RETHROW( (code)(acttype)(actor)(data)(expiration) ) } - + auths.push_back( permission_level{actor, config::active_name} ); + return push_action( code, acttype, auths, data, expiration, delay_sec ); + } - transaction_trace base_tester::push_action( const account_name& code, - const action_name& acttype, - const vector& actors, - const variant_object& data, - uint32_t expiration, - uint32_t delay_sec) + transaction_trace_ptr base_tester::push_action( const account_name& code, + const action_name& acttype, + const vector& actors, + const variant_object& data, + uint32_t expiration, + uint32_t delay_sec + ) - { try { + { vector auths; for (const auto& actor : actors) { - auths.push_back(permission_level{actor, config::active_name}); + auths.push_back( permission_level{actor, config::active_name} ); } - return push_action(code, acttype, auths, data, expiration, delay_sec); - } FC_CAPTURE_AND_RETHROW( (code)(acttype)(actors)(data)(expiration) ) } + return push_action( code, acttype, auths, data, expiration, delay_sec ); + } - transaction_trace base_tester::push_action( const account_name& code, - const action_name& acttype, - const vector& auths, - const variant_object& data, - uint32_t expiration, - uint32_t delay_sec) + transaction_trace_ptr base_tester::push_action( const account_name& code, + const action_name& acttype, + const vector& auths, + const variant_object& data, + uint32_t expiration, + uint32_t delay_sec + ) { try { - const auto& acnt = control->get_database().get(code); + signed_transaction trx; + trx.actions.emplace_back( get_action( code, acttype, auths, data ) ); + set_transaction_headers( trx, expiration, delay_sec ); + for (const auto& auth : auths) { + trx.sign( get_private_key( auth.actor, auth.permission.to_string() ), chain_id_type() ); + } + + return push_transaction( trx ); + } FC_CAPTURE_AND_RETHROW( (code)(acttype)(auths)(data)(expiration)(delay_sec) ) } + + action base_tester::get_action( account_name code, action_name acttype, vector auths, + const variant_object& data )const { try { + const auto& acnt = control->get_account(code); auto abi = acnt.get_abi(); - chain::contracts::abi_serializer abis(abi); - auto a = control->get_database().get(code).get_abi(); + chain::abi_serializer abis(abi); + // auto a = control->get_account(code).get_abi(); string action_type_name = abis.get_action_type(acttype); FC_ASSERT( action_type_name != string(), "unknown action type ${a}", ("a",acttype) ); @@ -253,18 +363,10 @@ namespace eosio { namespace testing { act.name = acttype; act.authorization = auths; act.data = abis.variant_to_binary(action_type_name, data); + return act; + } FC_CAPTURE_AND_RETHROW() } - signed_transaction trx; - trx.actions.emplace_back(std::move(act)); - set_transaction_headers(trx, expiration, delay_sec); - for (const auto& auth : auths) { - trx.sign(get_private_key(auth.actor, auth.permission.to_string()), chain_id_type()); - } - - return push_transaction(trx); - } FC_CAPTURE_AND_RETHROW( (code)(acttype)(auths)(data)(expiration) ) } - - transaction_trace base_tester::push_reqauth( account_name from, const vector& auths, const vector& keys ) { + transaction_trace_ptr base_tester::push_reqauth( account_name from, const vector& auths, const vector& keys ) { variant pretty_trx = fc::mutable_variant_object() ("actions", fc::variants({ fc::mutable_variant_object() @@ -278,16 +380,15 @@ namespace eosio { namespace testing { ); signed_transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); set_transaction_headers(trx); - wdump((trx)); for(auto iter = keys.begin(); iter != keys.end(); iter++) trx.sign( *iter, chain_id_type() ); return push_transaction( trx ); } - transaction_trace base_tester::push_reqauth(account_name from, string role, bool multi_sig) { + transaction_trace_ptr base_tester::push_reqauth(account_name from, string role, bool multi_sig) { if (!multi_sig) { return push_reqauth(from, vector{{from, config::owner_name}}, {get_private_key(from, role)}); @@ -298,7 +399,7 @@ namespace eosio { namespace testing { } - transaction_trace base_tester::push_dummy(account_name from, const string& v) { + transaction_trace_ptr base_tester::push_dummy(account_name from, const string& v) { // use reqauth for a normal action, this could be anything variant pretty_trx = fc::mutable_variant_object() ("actions", fc::variants({ @@ -318,16 +419,14 @@ namespace eosio { namespace testing { // lets also push a context free action, the multi chain test will then also include a context free action ("context_free_actions", fc::variants({ fc::mutable_variant_object() - ("account", name(config::system_account_name)) + ("account", name(config::null_account_name)) ("name", "nonce") - ("data", fc::mutable_variant_object() - ("value", v) - ) + ("data", fc::raw::pack(v)) }) ); signed_transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); set_transaction_headers(trx); trx.sign( get_private_key( from, "active" ), chain_id_type() ); @@ -335,12 +434,12 @@ namespace eosio { namespace testing { } - transaction_trace base_tester::transfer( account_name from, account_name to, string amount, string memo, account_name currency ) { + transaction_trace_ptr base_tester::transfer( account_name from, account_name to, string amount, string memo, account_name currency ) { return transfer( from, to, asset::from_string(amount), memo, currency ); } - transaction_trace base_tester::transfer( account_name from, account_name to, asset amount, string memo, account_name currency ) { + transaction_trace_ptr base_tester::transfer( account_name from, account_name to, asset amount, string memo, account_name currency ) { variant pretty_trx = fc::mutable_variant_object() ("actions", fc::variants({ fc::mutable_variant_object() @@ -361,7 +460,7 @@ namespace eosio { namespace testing { ); signed_transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); set_transaction_headers(trx); trx.sign( get_private_key( from, name(config::active_name).to_string() ), chain_id_type() ); @@ -369,7 +468,7 @@ namespace eosio { namespace testing { } - transaction_trace base_tester::issue( account_name to, string amount, account_name currency ) { + transaction_trace_ptr base_tester::issue( account_name to, string amount, account_name currency ) { variant pretty_trx = fc::mutable_variant_object() ("actions", fc::variants({ fc::mutable_variant_object() @@ -388,7 +487,7 @@ namespace eosio { namespace testing { ); signed_transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); set_transaction_headers(trx); trx.sign( get_private_key( currency, name(config::active_name).to_string() ), chain_id_type() ); @@ -400,7 +499,7 @@ namespace eosio { namespace testing { signed_transaction trx; trx.actions.emplace_back( vector{{account, config::active_name}}, - contracts::linkauth(account, code, type, req)); + linkauth(account, code, type, req)); set_transaction_headers(trx); trx.sign( get_private_key( account, "active" ), chain_id_type() ); @@ -412,7 +511,7 @@ namespace eosio { namespace testing { signed_transaction trx; trx.actions.emplace_back( vector{{account, config::active_name}}, - contracts::unlinkauth(account, code, type )); + unlinkauth(account, code, type )); set_transaction_headers(trx); trx.sign( get_private_key( account, "active" ), chain_id_type() ); @@ -429,11 +528,11 @@ namespace eosio { namespace testing { signed_transaction trx; trx.actions.emplace_back( auths, - contracts::updateauth{ + updateauth{ .account = account, .permission = perm, .parent = parent, - .data = move(auth), + .auth = move(auth), }); set_transaction_headers(trx); @@ -460,7 +559,7 @@ namespace eosio { namespace testing { const vector& keys ) { try { signed_transaction trx; trx.actions.emplace_back( auths, - contracts::deleteauth(account, perm) ); + deleteauth(account, perm) ); set_transaction_headers(trx); for (const auto& key: keys) { @@ -477,15 +576,15 @@ namespace eosio { namespace testing { } - void base_tester::set_code( account_name account, const char* wast ) try { - set_code(account, wast_to_wasm(wast)); + void base_tester::set_code( account_name account, const char* wast, const private_key_type* signer ) try { + set_code(account, wast_to_wasm(wast), signer); } FC_CAPTURE_AND_RETHROW( (account) ) - void base_tester::set_code( account_name account, const vector wasm ) try { + void base_tester::set_code( account_name account, const vector wasm, const private_key_type* signer ) try { signed_transaction trx; trx.actions.emplace_back( vector{{account,config::active_name}}, - contracts::setcode{ + setcode{ .account = account, .vmtype = 0, .vmversion = 0, @@ -493,22 +592,30 @@ namespace eosio { namespace testing { }); set_transaction_headers(trx); - trx.sign( get_private_key( account, "active" ), chain_id_type() ); + if( signer ) { + trx.sign( *signer, chain_id_type() ); + } else { + trx.sign( get_private_key( account, "active" ), chain_id_type() ); + } push_transaction( trx ); } FC_CAPTURE_AND_RETHROW( (account) ) - void base_tester::set_abi( account_name account, const char* abi_json) { - auto abi = fc::json::from_string(abi_json).template as(); + void base_tester::set_abi( account_name account, const char* abi_json, const private_key_type* signer ) { + auto abi = fc::json::from_string(abi_json).template as(); signed_transaction trx; trx.actions.emplace_back( vector{{account,config::active_name}}, - contracts::setabi{ + setabi{ .account = account, .abi = abi }); set_transaction_headers(trx); - trx.sign( get_private_key( account, "active" ), chain_id_type() ); + if( signer ) { + trx.sign( *signer, chain_id_type() ); + } else { + trx.sign( get_private_key( account, "active" ), chain_id_type() ); + } push_transaction( trx ); } @@ -529,13 +636,13 @@ namespace eosio { namespace testing { asset base_tester::get_currency_balance( const account_name& code, const symbol& asset_symbol, const account_name& account ) const { - const auto& db = control->get_database(); - const auto* tbl = db.template find(boost::make_tuple(code, account, N(accounts))); + const auto& db = control->db(); + const auto* tbl = db.template find(boost::make_tuple(code, account, N(accounts))); share_type result = 0; // the balance is implied to be 0 if either the table or row does not exist if (tbl) { - const auto *obj = db.template find(boost::make_tuple(tbl->id, asset_symbol.to_symbol_code().value)); + const auto *obj = db.template find(boost::make_tuple(tbl->id, asset_symbol.to_symbol_code().value)); if (obj) { //balance is the first field in the serialization fc::datastream ds(obj->value.data(), obj->value.size()); @@ -548,21 +655,22 @@ namespace eosio { namespace testing { vector base_tester::get_row_by_account( uint64_t code, uint64_t scope, uint64_t table, const account_name& act ) { vector data; - const auto& db = control->get_database(); - const auto* t_id = db.find( boost::make_tuple( code, scope, table ) ); + const auto& db = control->db(); + const auto* t_id = db.find( boost::make_tuple( code, scope, table ) ); if ( !t_id ) { return data; } //FC_ASSERT( t_id != 0, "object not found" ); - const auto& idx = db.get_index(); + const auto& idx = db.get_index(); auto itr = idx.lower_bound( boost::make_tuple( t_id->id, act ) ); if ( itr == idx.end() || itr->t_id != t_id->id || act.value != itr->primary_key ) { return data; } - chain_apis::read_only::copy_inline_row( *itr, data ); + data.resize( itr->value.size() ); + memcpy( data.data(), itr->value.data(), data.size() ); return data; } @@ -607,10 +715,11 @@ namespace eosio { namespace testing { return other.sync_with(*this); auto sync_dbs = [](base_tester& a, base_tester& b) { - for (int i = 1; i <= a.control->head_block_num(); ++i) { + for( int i = 1; i <= a.control->head_block_num(); ++i ) { auto block = a.control->fetch_block_by_number(i); - if (block && !b.control->is_known_block(block->id())) { - b.control->push_block(*block, eosio::chain::validation_steps::created_block); + if( block ) { //&& !b.control->is_known_block(block->id()) ) { + b.control->abort_block(); + b.control->push_block(block); //, eosio::chain::validation_steps::created_block); } } }; @@ -621,31 +730,108 @@ namespace eosio { namespace testing { void base_tester::push_genesis_block() { set_code(config::system_account_name, eosio_bios_wast); + set_abi(config::system_account_name, eosio_bios_abi); //produce_block(); } - producer_schedule_type base_tester::set_producers(const vector& producer_names, const uint32_t version) { - // Create producer schedule - producer_schedule_type schedule; - schedule.version = version; - for (auto& producer_name: producer_names) { - producer_key pk = { producer_name, get_public_key( producer_name, "active" )}; - schedule.producers.emplace_back(pk); - } + vector base_tester::get_producer_keys( const vector& producer_names )const { + // Create producer schedule + vector schedule; + for (auto& producer_name: producer_names) { + producer_key pk = { producer_name, get_public_key( producer_name, "active" )}; + schedule.emplace_back(pk); + } + return schedule; + } - push_action(N(eosio), N(setprods), N(eosio), - fc::mutable_variant_object()("version", schedule.version)("producers", schedule.producers)); + transaction_trace_ptr base_tester::set_producers(const vector& producer_names) { + auto schedule = get_producer_keys( producer_names ); - return schedule; + return push_action( N(eosio), N(setprods), N(eosio), + fc::mutable_variant_object()("schedule", schedule)); } - const contracts::table_id_object* base_tester::find_table( name code, name scope, name table ) { - auto tid = control->get_database().find(boost::make_tuple(code, scope, table)); + const table_id_object* base_tester::find_table( name code, name scope, name table ) { + auto tid = control->db().find(boost::make_tuple(code, scope, table)); return tid; } -} } /// eosio::test + bool fc_exception_message_is::operator()( const fc::exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = (message == expected); + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + + bool fc_exception_message_starts_with::operator()( const fc::exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = boost::algorithm::starts_with( message, expected ); + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + + bool fc_assert_exception_message_is::operator()( const fc::assert_exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = false; + auto pos = message.find( ": " ); + if( pos != std::string::npos ) { + message = message.substr( pos + 2 ); + match = (message == expected); + } + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + + bool fc_assert_exception_message_starts_with::operator()( const fc::assert_exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = false; + auto pos = message.find( ": " ); + if( pos != std::string::npos ) { + message = message.substr( pos + 2 ); + match = boost::algorithm::starts_with( message, expected ); + } + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + + bool eosio_assert_message_is::operator()( const fc::assert_exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = false; + auto pos = message.find( ": " ); + if( pos != std::string::npos ) { + message = message.substr( pos + 2 ); + match = (message == expected); + } + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + + bool eosio_assert_message_starts_with::operator()( const fc::assert_exception& ex ) { + auto message = ex.get_log().at( 0 ).get_message(); + bool match = false; + auto pos = message.find( ": " ); + if( pos != std::string::npos ) { + message = message.substr( pos + 2 ); + match = boost::algorithm::starts_with( message, expected ); + } + if( !match ) { + BOOST_TEST_MESSAGE( "LOG: expected: " << expected << ", actual: " << message ); + } + return match; + } + +} } /// eosio::testing std::ostream& operator<<( std::ostream& osm, const fc::variant& v ) { //fc::json::to_stream( osm, v ); diff --git a/libraries/testing/tester_network.cpp b/libraries/testing/tester_network.cpp index 3284dc80c34..023b3c8416b 100644 --- a/libraries/testing/tester_network.cpp +++ b/libraries/testing/tester_network.cpp @@ -14,10 +14,15 @@ namespace eosio { namespace testing { } // The new blockchain is now in sync with any old ones; go ahead and connect the propagation signal. + +/// TODO restore this + + /* blockchains[&new_blockchain] = new_blockchain.control->applied_block.connect( [this, &new_blockchain](const chain::block_trace& bt) { propagate_block(bt.block, new_blockchain); }); + */ } void tester_network::disconnect_blockchain(base_tester &leaving_blockchain) { @@ -29,11 +34,11 @@ namespace eosio { namespace testing { } void tester_network::propagate_block(const signed_block &block, const base_tester &skip_blockchain) { - for (const auto &pair : blockchains) { - if (pair.first == &skip_blockchain) continue; - boost::signals2::shared_connection_block blocker(pair.second); - pair.first->control->push_block(block, eosio::chain::validation_steps::created_block); - } + // for (const auto &pair : blockchains) { + // if (pair.first == &skip_blockchain) continue; + // boost::signals2::shared_connection_block blocker(pair.second); + // pair.first->control->push_block(block, eosio::chain::validation_steps::created_block); + // } } } } /// eosio::testing diff --git a/libraries/utilities/include/eosio/utilities/exception_macros.hpp b/libraries/utilities/include/eosio/utilities/exception_macros.hpp deleted file mode 100644 index 50112257827..00000000000 --- a/libraries/utilities/include/eosio/utilities/exception_macros.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#define EOS_ASSERT( expr, exc_type, FORMAT, ... ) \ - FC_MULTILINE_MACRO_BEGIN \ - if( !(expr) ) \ - FC_THROW_EXCEPTION( exc_type, FORMAT, __VA_ARGS__ ); \ - FC_MULTILINE_MACRO_END - -#define EOS_THROW( exc_type, FORMAT, ... ) \ - throw exc_type( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ); - -/** - * Macro inspired from FC_RETHROW_EXCEPTIONS - * The main difference here is that if the exception caught isn't of type "eosio::chain::chain_exception" - * This macro will rethrow the exception as the specified "exception_type" - */ -#define EOS_RETHROW_EXCEPTIONS(exception_type, FORMAT, ... ) \ - catch (eosio::chain::chain_exception& e) { \ - FC_RETHROW_EXCEPTION( e, warn, FORMAT, __VA_ARGS__ ); \ - } catch (fc::exception& e) { \ - exception_type new_exception(FC_LOG_MESSAGE( warn, FORMAT, __VA_ARGS__ )); \ - for (const auto& log: e.get_log()) { \ - new_exception.append_log(log); \ - } \ - throw new_exception; \ - } catch( const std::exception& e ) { \ - exception_type fce(FC_LOG_MESSAGE( warn, FORMAT" (${what})" ,__VA_ARGS__("what",e.what()))); \ - throw fce;\ - } catch( ... ) { \ - throw fc::unhandled_exception( \ - FC_LOG_MESSAGE( warn, FORMAT,__VA_ARGS__), \ - std::current_exception() ); \ - } - -/** - * Macro inspired from FC_CAPTURE_AND_RETHROW - * The main difference here is that if the exception caught isn't of type "eosio::chain::chain_exception" - * This macro will rethrow the exception as the specified "exception_type" - */ -#define EOS_CAPTURE_AND_RETHROW( exception_type, ... ) \ - catch (eosio::chain::chain_exception& e) { \ - FC_RETHROW_EXCEPTION( e, warn, "", FC_FORMAT_ARG_PARAMS(__VA_ARGS__) ); \ - } catch (fc::exception& e) { \ - exception_type new_exception(e.get_log()); \ - throw new_exception; \ - } catch( const std::exception& e ) { \ - exception_type fce( \ - FC_LOG_MESSAGE( warn, "${what}: ",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)("what",e.what())), \ - fc::std_exception_code,\ - BOOST_CORE_TYPEID(decltype(e)).name(), \ - e.what() ) ; throw fce;\ - } catch( ... ) { \ - throw fc::unhandled_exception( \ - FC_LOG_MESSAGE( warn, "",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)), \ - std::current_exception() ); \ - } - -#define EOS_DECLARE_OP_BASE_EXCEPTIONS( op_name ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _validate_exception, \ - eosio::chain::message_validate_exception, \ - 3040000 + 100 * operation::tag< op_name ## _operation >::value, \ - #op_name "_operation validation exception" \ - ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _evaluate_exception, \ - eosio::chain::message_evaluate_exception, \ - 3050000 + 100 * operation::tag< op_name ## _operation >::value, \ - #op_name "_operation evaluation exception" \ - ) - -#define EOS_DECLARE_OP_VALIDATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _ ## exc_name, \ - eosio::chain::op_name ## _validate_exception, \ - 3040000 + 100 * operation::tag< op_name ## _operation >::value \ - + seqnum, \ - msg \ - ) - -#define EOS_DECLARE_OP_EVALUATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _ ## exc_name, \ - eosio::chain::op_name ## _evaluate_exception, \ - 3050000 + 100 * operation::tag< op_name ## _operation >::value \ - + seqnum, \ - msg \ - ) - -#define EOS_RECODE_EXC( cause_type, effect_type ) \ - catch( const cause_type& e ) \ - { throw( effect_type( e.what(), e.get_log() ) ); } - - diff --git a/libraries/wasm-jit/Source/Programs/Assemble.cpp b/libraries/wasm-jit/Source/Programs/Assemble.cpp index 60ca42cf0f9..a3328794ddd 100644 --- a/libraries/wasm-jit/Source/Programs/Assemble.cpp +++ b/libraries/wasm-jit/Source/Programs/Assemble.cpp @@ -7,7 +7,7 @@ int commandMain(int argc,char** argv) { if(argc < 3) { - std::cerr << "Usage: Assemble in.wast out.wasm [switches]" << std::endl; + std::cerr << "Usage: eosio-wast2wasm in.wast out.wasm [switches]" << std::endl; std::cerr << " -n|--omit-names\t\tOmits WAST function and local names from the output" << std::endl; return EXIT_FAILURE; } diff --git a/libraries/wasm-jit/Source/Programs/CMakeLists.txt b/libraries/wasm-jit/Source/Programs/CMakeLists.txt index 260f4c1092c..27a3aa427b4 100644 --- a/libraries/wasm-jit/Source/Programs/CMakeLists.txt +++ b/libraries/wasm-jit/Source/Programs/CMakeLists.txt @@ -1,6 +1,7 @@ -add_executable(Assemble Assemble.cpp CLI.h) -target_link_libraries(Assemble Logging IR WAST WASM) -set_target_properties(Assemble PROPERTIES FOLDER Programs) +add_executable(eosio-wast2wasm Assemble.cpp CLI.h) +target_link_libraries(eosio-wast2wasm Logging IR WAST WASM) +set_target_properties(eosio-wast2wasm PROPERTIES FOLDER Programs) +INSTALL(TARGETS eosio-wast2wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) add_executable(Disassemble Disassemble.cpp CLI.h) target_link_libraries(Disassemble Logging IR WAST WASM) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 47c9f8a1ce4..9aeb86e0659 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -4,13 +4,15 @@ add_subdirectory(http_plugin) add_subdirectory(chain_plugin) add_subdirectory(chain_api_plugin) add_subdirectory(producer_plugin) -add_subdirectory(account_history_plugin) -add_subdirectory(account_history_api_plugin) +add_subdirectory(history_plugin) +add_subdirectory(history_api_plugin) + +#add_subdirectory(account_history_api_plugin) add_subdirectory(wallet_plugin) add_subdirectory(wallet_api_plugin) add_subdirectory(txn_test_gen_plugin) -add_subdirectory(faucet_testnet_plugin) -add_subdirectory(mongo_db_plugin) +#add_subdirectory(faucet_testnet_plugin) +#add_subdirectory(mongo_db_plugin) # Forward variables to top level so packaging picks them up set(CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_DEPENDS} PARENT_SCOPE) diff --git a/plugins/account_history_plugin/account_history_plugin.cpp b/plugins/account_history_plugin/account_history_plugin.cpp index 06dc06c0c74..8faeb8e951d 100644 --- a/plugins/account_history_plugin/account_history_plugin.cpp +++ b/plugins/account_history_plugin/account_history_plugin.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,7 +60,7 @@ class account_history_plugin_impl { void check_init_db() { if( !init_db ) { init_db = true; - auto& db = chain_plug->chain().get_mutable_database(); + auto& db = chain_plug->chain().db(); db.add_index(); db.add_index(); db.add_index(); @@ -134,9 +134,11 @@ optional account_history_plugin_impl::find_block_id(const chainba packed_transaction account_history_plugin_impl::find_transaction(const chain::transaction_id_type& transaction_id, const chain::signed_block& block) const { + /* TODO: fix this for (const packed_transaction& trx : block.input_transactions) if (trx.get_transaction().id() == transaction_id) return trx; + */ // ERROR in indexing logic FC_THROW("Transaction with ID ${tid} was indexed as being in block ID ${bid}, but was not found in that block", ("tid", transaction_id)("bid", block.id())); @@ -144,7 +146,7 @@ packed_transaction account_history_plugin_impl::find_transaction(const chain::tr packed_transaction account_history_plugin_impl::get_transaction(const chain::transaction_id_type& transaction_id) const { - const auto& db = chain_plug->chain().get_database(); + const auto& db = chain_plug->chain().db(); optional block_id; db.with_read_lock( [&]() { block_id = find_block_id(db, transaction_id); @@ -163,7 +165,7 @@ packed_transaction account_history_plugin_impl::get_transaction(const chain::tra get_transactions_results account_history_plugin_impl::get_transactions(const account_name& account_name, const optional& skip_seq, const optional& num_seq) const { fc::time_point start_time = fc::time_point::now(); - const auto& db = chain_plug->chain().get_database(); + const auto& db = chain_plug->chain().db(); block_transaction_id_map block_transaction_ids; db.with_read_lock( [&]() { @@ -276,7 +278,7 @@ bool account_history_plugin_impl::time_exceeded(const fc::time_point& start_time vector account_history_plugin_impl::get_key_accounts(const public_key_type& public_key) const { std::set accounts; - const auto& db = chain_plug->chain().get_database(); + const auto& db = chain_plug->chain().db(); db.with_read_lock( [&]() { const auto& pub_key_idx = db.get_index(); auto range = pub_key_idx.equal_range( public_key ); @@ -291,7 +293,7 @@ vector account_history_plugin_impl::get_key_accounts(const public_ vector account_history_plugin_impl::get_controlled_accounts(const account_name& controlling_account) const { std::set accounts; - const auto& db = chain_plug->chain().get_database(); + const auto& db = chain_plug->chain().db(); db.with_read_lock( [&]() { const auto& account_control_idx = db.get_index(); auto range = account_control_idx.equal_range( controlling_account ); @@ -310,7 +312,7 @@ static vector generated_affected_accounts(const chain::transaction result.emplace_back(auth.actor); } - result.emplace_back(at.receiver); + result.emplace_back(at.receipt.receiver); } fc::deduplicate(result); @@ -321,7 +323,7 @@ void account_history_plugin_impl::applied_block(const chain::block_trace& trace) { const auto& block = trace.block; const auto block_id = block.id(); - auto& db = chain_plug->chain().get_mutable_database(); + auto& db = chain_plug->chain().db(); const bool check_relevance = filter_on.size(); auto process_one = [&](const chain::transaction_trace& trx_trace ) { @@ -351,11 +353,11 @@ void account_history_plugin_impl::applied_block(const chain::block_trace& trace) for (const auto& act_trace : trx_trace.action_traces) { - if (act_trace.receiver == chain::config::system_account_name) + if (act_trace.receipt.receiver == chain::config::system_account_name) { if (act_trace.act.name == NEW_ACCOUNT) { - const auto create = act_trace.act.data_as(); + const auto create = act_trace.act.data_as(); add(db, create.owner.keys, create.name, OWNER); add(db, create.active.keys, create.name, ACTIVE); add(db, create.recovery.keys, create.name, RECOVERY); @@ -366,7 +368,7 @@ void account_history_plugin_impl::applied_block(const chain::block_trace& trace) } else if (act_trace.act.name == UPDATE_AUTH) { - const auto update = act_trace.act.data_as(); + const auto update = act_trace.act.data_as(); remove(db, update.account, update.permission); add(db, update.data.keys, update.account, update.permission); @@ -375,7 +377,7 @@ void account_history_plugin_impl::applied_block(const chain::block_trace& trace) } else if (act_trace.act.name == DELETE_AUTH) { - const auto del = act_trace.act.data_as(); + const auto del = act_trace.act.data_as(); remove(db, del.account, del.permission); remove(db, del.account, del.permission); @@ -427,7 +429,7 @@ bool account_history_plugin_impl::is_scope_relevant(const vector& fc::variant account_history_plugin_impl::transaction_to_variant(const packed_transaction& ptrx) const { - const chainbase::database& database = chain_plug->chain().get_database(); + const chainbase::database& database = chain_plug->chain().db(); auto resolver = [&database]( const account_name& name ) -> optional { const auto* accnt = database.find( name ); if (accnt != nullptr) { @@ -477,7 +479,7 @@ void account_history_plugin::plugin_initialize(const variables_map& options) } my->chain_plug = app().find_plugin(); - my->chain_plug->chain_config().applied_block_callbacks.emplace_back( + my->chain_plug->chain_config().applied_block_callbacks.emplace_back( [&impl = my](const chain::block_trace& trace) { impl->check_init_db(); impl->applied_block(trace); @@ -488,7 +490,7 @@ void account_history_plugin::plugin_initialize(const variables_map& options) void account_history_plugin::plugin_startup() { /* - auto& db = my->chain_plug->chain().get_mutable_database(); + auto& db = my->chain_plug->chain().db(); db.add_index(); db.add_index(); db.add_index(); diff --git a/plugins/account_history_plugin/include/eosio/account_history_plugin/account_history_plugin.hpp b/plugins/account_history_plugin/include/eosio/account_history_plugin/account_history_plugin.hpp deleted file mode 100644 index 9aaa1035a90..00000000000 --- a/plugins/account_history_plugin/include/eosio/account_history_plugin/account_history_plugin.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include - -#include - -namespace fc { class variant; } - -namespace eosio { - using chain::transaction_id_type; - using std::shared_ptr; - using namespace appbase; - using chain::name; - using fc::optional; - using chain::uint128_t; - - typedef shared_ptr account_history_ptr; - typedef shared_ptr account_history_const_ptr; - -namespace account_history_apis { -struct empty{}; - -class read_only { - account_history_const_ptr account_history; - -public: - read_only(account_history_const_ptr&& account_history) - : account_history(account_history) {} - - - struct get_transaction_params { - chain::transaction_id_type transaction_id; - }; - struct get_transaction_results { - chain::transaction_id_type transaction_id; - fc::variant transaction; - }; - get_transaction_results get_transaction(const get_transaction_params& params) const; - - - struct get_transactions_params { - chain::account_name account_name; - optional skip_seq; - optional num_seq; - }; - struct ordered_transaction_results { - uint32_t seq_num; - chain::transaction_id_type transaction_id; - fc::variant transaction; - }; - struct get_transactions_results { - vector transactions; - optional time_limit_exceeded_error; - }; - - get_transactions_results get_transactions(const get_transactions_params& params) const; - - - struct get_key_accounts_params { - chain::public_key_type public_key; - }; - struct get_key_accounts_results { - vector account_names; - }; - get_key_accounts_results get_key_accounts(const get_key_accounts_params& params) const; - - - struct get_controlled_accounts_params { - chain::account_name controlling_account; - }; - struct get_controlled_accounts_results { - vector controlled_accounts; - }; - get_controlled_accounts_results get_controlled_accounts(const get_controlled_accounts_params& params) const; -}; - -class read_write { - account_history_ptr account_history; - -public: - read_write(account_history_ptr account_history) : account_history(account_history) {} -}; -} // namespace account_history_apis - -class account_history_plugin : public plugin { -public: - APPBASE_PLUGIN_REQUIRES((chain_plugin)) - - account_history_plugin(); - virtual ~account_history_plugin(); - - virtual void set_program_options(options_description& cli, options_description& cfg) override; - - void plugin_initialize(const variables_map& options); - void plugin_startup(); - void plugin_shutdown(); - - account_history_apis::read_only get_read_only_api() const { return account_history_apis::read_only(account_history_const_ptr(my)); } - account_history_apis::read_write get_read_write_api() { return account_history_apis::read_write(my); } - -private: - account_history_ptr my; -}; - -} - -FC_REFLECT(eosio::account_history_apis::empty, ) -FC_REFLECT(eosio::account_history_apis::read_only::get_transaction_params, (transaction_id) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_transaction_results, (transaction_id)(transaction) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_transactions_params, (account_name)(skip_seq)(num_seq) ) -FC_REFLECT(eosio::account_history_apis::read_only::ordered_transaction_results, (seq_num)(transaction_id)(transaction) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_transactions_results, (transactions)(time_limit_exceeded_error) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_key_accounts_params, (public_key) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_key_accounts_results, (account_names) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_controlled_accounts_params, (controlling_account) ) -FC_REFLECT(eosio::account_history_apis::read_only::get_controlled_accounts_results, (controlled_accounts) ) diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index a51cb21a19d..5d8ab1b1aa7 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -15,10 +15,10 @@ using namespace eosio; class chain_api_plugin_impl { public: - chain_api_plugin_impl(chain_controller& db) + chain_api_plugin_impl(controller& db) : db(db) {} - chain_controller& db; + controller& db; }; @@ -35,7 +35,7 @@ void chain_api_plugin::plugin_initialize(const variables_map&) {} if (body.empty()) body = "{}"; \ auto result = api_handle.call_name(fc::json::from_string(body).as()); \ cb(http_response_code, fc::json::to_string(result)); \ - } catch (chain::tx_missing_sigs& e) { \ + } catch (chain::unsatisfied_authorization& e) { \ error_results results{401, "UnAuthorized", e}; \ cb(401, fc::json::to_string(results)); \ } catch (chain::tx_duplicate& e) { \ @@ -51,7 +51,7 @@ void chain_api_plugin::plugin_initialize(const variables_map&) {} } catch (fc::exception& e) { \ error_results results{500, "Internal Service Error", e}; \ cb(500, fc::json::to_string(results)); \ - elog("Exception encountered while processing ${call}: ${e}", ("call", #api_name "." #call_name)("e", e)); \ + elog("Exception encountered while processing ${call}: ${e}", ("call", #api_name "." #call_name)("e", e.to_detail_string())); \ } \ }} diff --git a/plugins/chain_api_plugin/include/eosio/chain_api_plugin/chain_api_plugin.hpp b/plugins/chain_api_plugin/include/eosio/chain_api_plugin/chain_api_plugin.hpp index 108faa4a5f6..ad64fcaa1d3 100644 --- a/plugins/chain_api_plugin/include/eosio/chain_api_plugin/chain_api_plugin.hpp +++ b/plugins/chain_api_plugin/include/eosio/chain_api_plugin/chain_api_plugin.hpp @@ -7,10 +7,10 @@ #include #include -#include +#include namespace eosio { - using eosio::chain::chain_controller; + using eosio::chain::controller; using std::unique_ptr; using namespace appbase; diff --git a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp new file mode 100644 index 00000000000..858b92f6d48 --- /dev/null +++ b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp @@ -0,0 +1,58 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace eosio { namespace chain { namespace plugin_interface { + using namespace eosio::chain; + using namespace appbase; + + struct chain_plugin_interface; + + namespace channels { + using accepted_block_header = channel_decl; + using accepted_block = channel_decl; + using irreversible_block = channel_decl; + using accepted_transaction = channel_decl; + using applied_transaction = channel_decl; + using accepted_confirmation = channel_decl; + + } + + namespace methods { + using get_block_by_number = method_decl; + using get_block_by_id = method_decl; + using get_head_block_id = method_decl; + + using get_last_irreversible_block_number = method_decl; + } + + namespace incoming { + namespace channels { + using block = channel_decl; + using transaction = channel_decl; + } + + namespace methods { + // synchronously push a block/trx to a single provider + using block_sync = method_decl; + using transaction_sync = method_decl; + } + } + + namespace compat { + namespace channels { + using transaction_ack = channel_decl>; + } + } + +} } } \ No newline at end of file diff --git a/plugins/chain_plugin/CMakeLists.txt b/plugins/chain_plugin/CMakeLists.txt index f65f118770d..8fb2cfab301 100644 --- a/plugins/chain_plugin/CMakeLists.txt +++ b/plugins/chain_plugin/CMakeLists.txt @@ -4,4 +4,4 @@ add_library( chain_plugin ${HEADERS} ) target_link_libraries( chain_plugin eosio_chain appbase ) -target_include_directories( chain_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) +target_include_directories( chain_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" ) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 2159b427698..5ea59174f32 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -6,20 +6,21 @@ #include #include #include -#include +#include #include #include #include #include +#include -#include -#include -#include +#include #include #include #include +#include + #include #include @@ -28,32 +29,61 @@ namespace eosio { using namespace eosio; using namespace eosio::chain; using namespace eosio::chain::config; +using namespace eosio::chain::plugin_interface; using vm_type = wasm_interface::vm_type; using fc::flat_map; -//using txn_msg_rate_limits = chain_controller::txn_msg_rate_limits; +//using txn_msg_rate_limits = controller::txn_msg_rate_limits; class chain_plugin_impl { public: + chain_plugin_impl() + :accepted_block_header_channel(app().get_channel()) + ,accepted_block_channel(app().get_channel()) + ,irreversible_block_channel(app().get_channel()) + ,accepted_transaction_channel(app().get_channel()) + ,applied_transaction_channel(app().get_channel()) + ,accepted_confirmation_channel(app().get_channel()) + ,incoming_block_channel(app().get_channel()) + ,incoming_block_sync_method(app().get_method()) + ,incoming_transaction_sync_method(app().get_method()) + {} + bfs::path block_log_dir; bfs::path genesis_file; time_point genesis_timestamp; - uint32_t skip_flags = skip_nothing; bool readonly = false; uint64_t shared_memory_size; flat_map loaded_checkpoints; fc::optional fork_db; fc::optional block_logger; - fc::optional chain_config = chain_controller::controller_config(); - fc::optional chain; + fc::optional chain_config = controller::config(); + fc::optional chain; chain_id_type chain_id; - int32_t max_reversible_block_time_ms; - int32_t max_pending_transaction_time_ms; - int32_t max_deferred_transaction_time_ms; //txn_msg_rate_limits rate_limits; fc::optional wasm_runtime; + + // retained references to channels for easy publication + channels::accepted_block_header::channel_type& accepted_block_header_channel; + channels::accepted_block::channel_type& accepted_block_channel; + channels::irreversible_block::channel_type& irreversible_block_channel; + channels::accepted_transaction::channel_type& accepted_transaction_channel; + channels::applied_transaction::channel_type& applied_transaction_channel; + channels::accepted_confirmation::channel_type& accepted_confirmation_channel; + incoming::channels::block::channel_type& incoming_block_channel; + + // retained references to methods for easy calling + incoming::methods::block_sync::method_type& incoming_block_sync_method; + incoming::methods::transaction_sync::method_type& incoming_transaction_sync_method; + + // method provider handles + methods::get_block_by_number::method_type::handle get_block_by_number_provider; + methods::get_block_by_id::method_type::handle get_block_by_id_provider; + methods::get_head_block_id::method_type::handle get_head_block_id_provider; + methods::get_last_irreversible_block_number::method_type::handle get_last_irreversible_block_number_provider; + }; chain_plugin::chain_plugin() @@ -70,12 +100,6 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip ("block-log-dir", bpo::value()->default_value("blocks"), "the location of the block log (absolute path or relative to application data dir)") ("checkpoint,c", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") - ("max-reversible-block-time", bpo::value()->default_value(-1), - "Limits the maximum time (in milliseconds) that a reversible block is allowed to run before being considered invalid") - ("max-pending-transaction-time", bpo::value()->default_value(-1), - "Limits the maximum time (in milliseconds) that is allowed a pushed transaction's code to execute before being considered invalid") - ("max-deferred-transaction-time", bpo::value()->default_value(20), - "Limits the maximum time (in milliseconds) that is allowed a to push deferred transactions at the start of a block") ("wasm-runtime", bpo::value()->value_name("wavm/binaryen"), "Override default WASM runtime") ("shared-memory-size-mb", bpo::value()->default_value(config::default_shared_memory_size / (1024 * 1024)), "Maximum size MB of database shared memory file") @@ -94,8 +118,6 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip "clear chain database and replay all blocks") ("resync-blockchain", bpo::bool_switch()->default_value(false), "clear chain database and block log") - ("skip-transaction-signatures", bpo::bool_switch()->default_value(false), - "Disable transaction signature verification. ONLY for TESTING.") ; } @@ -145,20 +167,6 @@ void chain_plugin::plugin_initialize(const variables_map& options) { fc::remove_all(app().data_dir() / default_shared_memory_dir); fc::remove_all(my->block_log_dir); } - if (options.at("skip-transaction-signatures").as()) { - ilog("Setting skip_transaction_signatures"); - elog("Setting skip_transaction_signatures\n" - "\n" - "**************************************\n" - "* *\n" - "* -- EOSD IGNORING SIGNATURES -- *\n" - "* - TEST MODE - *\n" - "* ------------------------------ *\n" - "* *\n" - "**************************************\n"); - - my->skip_flags |= skip_transaction_signatures; - } if(options.count("checkpoint")) { @@ -171,50 +179,79 @@ void chain_plugin::plugin_initialize(const variables_map& options) { } } - my->max_reversible_block_time_ms = options.at("max-reversible-block-time").as(); - my->max_pending_transaction_time_ms = options.at("max-pending-transaction-time").as(); - my->max_deferred_transaction_time_ms = options.at("max-deferred-transaction-time").as(); - if(options.count("wasm-runtime")) my->wasm_runtime = options.at("wasm-runtime").as(); -} -void chain_plugin::plugin_startup() -{ try { if( !fc::exists( my->genesis_file ) ) { wlog( "\n generating default genesis file ${f}", ("f", my->genesis_file.generic_string() ) ); - contracts::genesis_state_type default_genesis; + genesis_state default_genesis; fc::json::save_to_file( default_genesis, my->genesis_file, true ); } my->chain_config->block_log_dir = my->block_log_dir; my->chain_config->shared_memory_dir = app().data_dir() / default_shared_memory_dir; my->chain_config->read_only = my->readonly; my->chain_config->shared_memory_size = my->shared_memory_size; - my->chain_config->genesis = fc::json::from_file(my->genesis_file).as(); + my->chain_config->genesis = fc::json::from_file(my->genesis_file).as(); if (my->genesis_timestamp.sec_since_epoch() > 0) { my->chain_config->genesis.initial_timestamp = my->genesis_timestamp; } - if (my->max_reversible_block_time_ms > 0) { - my->chain_config->limits.max_push_block_us = fc::milliseconds(my->max_reversible_block_time_ms); - } - - if (my->max_pending_transaction_time_ms > 0) { - my->chain_config->limits.max_push_transaction_us = fc::milliseconds(my->max_pending_transaction_time_ms); - } - - if (my->max_deferred_transaction_time_ms > 0 ) { - my->chain_config->limits.max_deferred_transactions_us = fc::milliseconds(my->max_deferred_transaction_time_ms); - } - if(my->wasm_runtime) my->chain_config->wasm_runtime = *my->wasm_runtime; my->chain.emplace(*my->chain_config); + // set up method providers + my->get_block_by_number_provider = app().get_method().register_provider([this](uint32_t block_num) -> signed_block_ptr { + return my->chain->fetch_block_by_number(block_num); + }); + + my->get_block_by_id_provider = app().get_method().register_provider([this](block_id_type id) -> signed_block_ptr { + return my->chain->fetch_block_by_id(id); + }); + + my->get_head_block_id_provider = app().get_method().register_provider([this](){ + return my->chain->head_block_id(); + }); + + my->get_last_irreversible_block_number_provider = app().get_method().register_provider([this](){ + return my->chain->last_irreversible_block_num(); + }); + + // relay signals to channels + my->chain->accepted_block_header.connect([this](const block_state_ptr& blk) { + my->accepted_block_header_channel.publish(blk); + }); + + my->chain->accepted_block.connect([this](const block_state_ptr& blk) { + my->accepted_block_channel.publish(blk); + }); + + my->chain->irreversible_block.connect([this](const block_state_ptr& blk) { + my->irreversible_block_channel.publish(blk); + }); + + my->chain->accepted_transaction.connect([this](const transaction_metadata_ptr& meta){ + my->accepted_transaction_channel.publish(meta); + }); + + my->chain->applied_transaction.connect([this](const transaction_trace_ptr& trace){ + my->applied_transaction_channel.publish(trace); + }); + + my->chain->accepted_confirmation.connect([this](const header_confirmation& conf){ + my->accepted_confirmation_channel.publish(conf); + }); + +} + +void chain_plugin::plugin_startup() +{ try { + my->chain->startup(); + if(!my->readonly) { ilog("starting chain in read/write mode"); - my->chain->add_checkpoints(my->loaded_checkpoints); + /// TODO: my->chain->add_checkpoints(my->loaded_checkpoints); } ilog("Blockchain started; head block is #${num}, genesis timestamp is ${ts}", @@ -229,69 +266,59 @@ void chain_plugin::plugin_shutdown() { } chain_apis::read_write chain_plugin::get_read_write_api() { - return chain_apis::read_write(chain(), my->skip_flags); + return chain_apis::read_write(chain()); } -bool chain_plugin::accept_block(const signed_block& block, bool currently_syncing) { - if (currently_syncing && block.block_num() % 10000 == 0) { - ilog("Syncing Blockchain --- Got block: #${n} time: ${t} producer: ${p}", - ("t", block.timestamp) - ("n", block.block_num()) - ("p", block.producer)); - } - -#warning TODO: This used to be sync now it isnt? - chain().push_block(block, my->skip_flags); - return true; +void chain_plugin::accept_block(const signed_block_ptr& block ) { + my->incoming_block_sync_method(block); } void chain_plugin::accept_transaction(const packed_transaction& trx) { - chain().push_transaction(trx, my->skip_flags); + my->incoming_transaction_sync_method(std::make_shared(trx)); } bool chain_plugin::block_is_on_preferred_chain(const block_id_type& block_id) { - // If it's not known, it's not preferred. - if (!chain().is_known_block(block_id)) return false; - // Extract the block number from block_id, and fetch that block number's ID from the database. - // If the database's block ID matches block_id, then block_id is on the preferred chain. Otherwise, it's on a fork. - return chain().get_block_id_for_num(block_header::num_from_id(block_id)) == block_id; -} - -bool chain_plugin::is_skipping_transaction_signatures() const { - return my->skip_flags & skip_transaction_signatures; + auto b = chain().fetch_block_by_number( block_header::num_from_id(block_id) ); + return b && b->id() == block_id; } -chain_controller::controller_config& chain_plugin::chain_config() { +controller::config& chain_plugin::chain_config() { // will trigger optional assert if called before/after plugin_initialize() return *my->chain_config; } -chain_controller& chain_plugin::chain() { return *my->chain; } -const chain_controller& chain_plugin::chain() const { return *my->chain; } +controller& chain_plugin::chain() { return *my->chain; } +const controller& chain_plugin::chain() const { return *my->chain; } - void chain_plugin::get_chain_id (chain_id_type &cid)const { - memcpy (cid.data(), my->chain_id.data(), cid.data_size()); - } +void chain_plugin::get_chain_id(chain_id_type &cid)const { + memcpy(cid.data(), my->chain_id.data(), cid.data_size()); +} namespace chain_apis { const string read_only::KEYi64 = "i64"; read_only::get_info_results read_only::get_info(const read_only::get_info_params&) const { + const auto& rm = db.get_resource_limits_manager(); return { eosio::utilities::common::itoh(static_cast(app().version())), db.head_block_num(), db.last_irreversible_block_num(), + db.last_irreversible_block_id(), db.head_block_id(), db.head_block_time(), db.head_block_producer(), + rm.get_virtual_block_cpu_limit(), + rm.get_virtual_block_net_limit(), + rm.get_block_cpu_limit(), + rm.get_block_net_limit() //std::bitset<64>(db.get_dynamic_global_properties().recent_slots_filled).to_string(), //__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0 }; } -abi_def get_abi( const chain_controller& db, const name& account ) { - const auto &d = db.get_database(); +abi_def get_abi( const controller& db, const name& account ) { + const auto &d = db.db(); const account_object *code_accnt = d.find(account); EOS_ASSERT(code_accnt != nullptr, chain::account_query_exception, "Fail to retrieve account for ${account}", ("account", account) ); abi_def abi; @@ -313,7 +340,7 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get auto table_type = get_table_type( abi, p.table ); if( table_type == KEYi64 ) { - return get_table_rows_ex(p,abi); + return get_table_rows_ex(p,abi); } EOS_ASSERT( false, chain::contract_table_query_exception, "Invalid table type ${type}", ("type",table_type)("abi",abi)); @@ -325,8 +352,7 @@ vector read_only::get_currency_balance( const read_only::get_currency_bal auto table_type = get_table_type( abi, "accounts" ); vector results; - walk_table(p.code, p.account, N(accounts), [&](const contracts::key_value_object& obj){ - + walk_table(p.code, p.account, N(accounts), [&](const key_value_object& obj){ EOS_ASSERT( obj.value.size() >= sizeof(asset), chain::asset_type_exception, "Invalid data on table"); asset cursor; @@ -354,8 +380,7 @@ fc::variant read_only::get_currency_stats( const read_only::get_currency_stats_p uint64_t scope = ( eosio::chain::string_to_symbol( 0, boost::algorithm::to_upper_copy(p.symbol).c_str() ) >> 8 ); - walk_table(p.code, scope, N(stat), [&](const contracts::key_value_object& obj){ - + walk_table(p.code, scope, N(stat), [&](const key_value_object& obj){ EOS_ASSERT( obj.value.size() >= sizeof(read_only::get_currency_stats_result), chain::asset_type_exception, "Invalid data on table"); fc::datastream ds(obj.value.data(), obj.value.size()); @@ -376,7 +401,7 @@ template struct resolver_factory { static auto make(const Api *api) { return [api](const account_name &name) -> optional { - const auto *accnt = api->db.get_database().template find(name); + const auto *accnt = api->db.db().template find(name); if (accnt != nullptr) { abi_def abi; if (abi_serializer::to_abi(accnt->abi, abi)) { @@ -395,7 +420,7 @@ auto make_resolver(const Api *api) { } fc::variant read_only::get_block(const read_only::get_block_params& params) const { - optional block; + signed_block_ptr block; try { block = db.fetch_block_by_id(fc::json::from_string(params.block_num_or_id).as()); if (!block) { @@ -404,9 +429,7 @@ fc::variant read_only::get_block(const read_only::get_block_params& params) cons } EOS_RETHROW_EXCEPTIONS(chain::block_id_type_exception, "Invalid block ID: ${block_num_or_id}", ("block_num_or_id", params.block_num_or_id)) - if (!block) - FC_THROW_EXCEPTION(unknown_block_exception, - "Could not find block: ${block}", ("block", params.block_num_or_id)); + EOS_ASSERT( block, unknown_block_exception, "Could not find block: ${block}", ("block", params.block_num_or_id)); fc::variant pretty_output; abi_serializer::to_variant(*block, pretty_output, make_resolver(this)); @@ -420,22 +443,22 @@ fc::variant read_only::get_block(const read_only::get_block_params& params) cons } read_write::push_block_results read_write::push_block(const read_write::push_block_params& params) { - db.push_block(params, skip_nothing); + db.push_block( std::make_shared(params) ); return read_write::push_block_results(); } read_write::push_transaction_results read_write::push_transaction(const read_write::push_transaction_params& params) { - packed_transaction pretty_input; + auto pretty_input = std::make_shared(); auto resolver = make_resolver(this); try { - abi_serializer::from_variant(params, pretty_input, resolver); + abi_serializer::from_variant(params, *pretty_input, resolver); } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction") - auto result = db.push_transaction(pretty_input, skip_flags); -#warning TODO: get transaction results asynchronously - fc::variant pretty_output; - abi_serializer::to_variant(result, pretty_output, resolver); - return read_write::push_transaction_results{ result.id, pretty_output }; + auto trx_trace_ptr = app().get_method()(pretty_input); + + fc::variant pretty_output = db.to_variant_with_abi( *trx_trace_ptr );; + //abi_serializer::to_variant(*trx_trace_ptr, pretty_output, resolver); + return read_write::push_transaction_results{ trx_trace_ptr->id, pretty_output }; } read_write::push_transactions_results read_write::push_transactions(const read_write::push_transactions_params& params) { @@ -457,7 +480,7 @@ read_write::push_transactions_results read_write::push_transactions(const read_w read_only::get_code_results read_only::get_code( const get_code_params& params )const { get_code_results result; result.account_name = params.account_name; - const auto& d = db.get_database(); + const auto& d = db.db(); const auto& accnt = d.get( params.account_name ); if( accnt.code.size() ) { @@ -475,12 +498,22 @@ read_only::get_code_results read_only::get_code( const get_code_params& params ) } read_only::get_account_results read_only::get_account( const get_account_params& params )const { - using namespace eosio::contracts; - get_account_results result; result.account_name = params.account_name; - const auto& d = db.get_database(); + const auto& d = db.db(); + const auto& rm = db.get_resource_limits_manager(); + rm.get_account_limits( result.account_name, result.ram_quota, result.net_weight, result.cpu_weight ); + + const auto& a = db.get_account(result.account_name); + + result.privileged = a.privileged; + result.last_code_update = a.last_code_update; + result.created = a.creation_date; + + result.net_limit = rm.get_account_net_limit_ex( result.account_name ); + result.cpu_limit = rm.get_account_cpu_limit_ex( result.account_name ); + result.ram_usage = rm.get_account_ram_usage( result.account_name ); const auto& permissions = d.get_index(); auto perm = permissions.lower_bound( boost::make_tuple( params.account_name ) ); @@ -501,13 +534,51 @@ read_only::get_account_results read_only::get_account( const get_account_params& ++perm; } + const auto& code_account = db.db().get( N(eosio) ); + //const abi_def abi = get_abi( db, N(eosio) ); + abi_def abi; + if( abi_serializer::to_abi(code_account.abi, abi) ) { + abi_serializer abis( abi ); + //get_table_rows_ex(p,abi); + const auto* t_id = d.find(boost::make_tuple( config::system_account_name, params.account_name, N(userres) )); + if (t_id != nullptr) { + const auto &idx = d.get_index(); + auto it = idx.lower_bound(boost::make_tuple( t_id->id, params.account_name )); + if ( it != idx.end() ) { + vector data; + copy_inline_row(*it, data); + result.total_resources = abis.binary_to_variant( "user_resources", data ); + } + } + + t_id = d.find(boost::make_tuple( config::system_account_name, params.account_name, N(delband) )); + if (t_id != nullptr) { + const auto &idx = d.get_index(); + auto it = idx.lower_bound(boost::make_tuple( t_id->id, params.account_name )); + if ( it != idx.end() ) { + vector data; + copy_inline_row(*it, data); + result.delegated_bandwidth = abis.binary_to_variant( "delegated_bandwidth", data ); + } + } + t_id = d.find(boost::make_tuple( config::system_account_name, config::system_account_name, N(voters) )); + if (t_id != nullptr) { + const auto &idx = d.get_index(); + auto it = idx.lower_bound(boost::make_tuple( t_id->id, params.account_name )); + if ( it != idx.end() ) { + vector data; + copy_inline_row(*it, data); + result.voter_info = abis.binary_to_variant( "voter_info", data ); + } + } + } return result; } read_only::abi_json_to_bin_result read_only::abi_json_to_bin( const read_only::abi_json_to_bin_params& params )const try { abi_json_to_bin_result result; - const auto code_account = db.get_database().find( params.code ); + const auto code_account = db.db().find( params.code ); EOS_ASSERT(code_account != nullptr, contract_query_exception, "Contract can't be found ${contract}", ("contract", params.code)); abi_def abi; @@ -524,7 +595,7 @@ read_only::abi_json_to_bin_result read_only::abi_json_to_bin( const read_only::a read_only::abi_bin_to_json_result read_only::abi_bin_to_json( const read_only::abi_bin_to_json_params& params )const { abi_bin_to_json_result result; - const auto& code_account = db.get_database().get( params.code ); + const auto& code_account = db.db().get( params.code ); abi_def abi; if( abi_serializer::to_abi(code_account.abi, abi) ) { abi_serializer abis( abi ); @@ -536,7 +607,7 @@ read_only::abi_bin_to_json_result read_only::abi_bin_to_json( const read_only::a read_only::get_required_keys_result read_only::get_required_keys( const get_required_keys_params& params )const { transaction pretty_input; from_variant(params.transaction, pretty_input); - auto required_keys_set = db.get_required_keys(pretty_input, params.available_keys); + auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys); get_required_keys_result result; result.required_keys = required_keys_set; return result; diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 86cffba57c2..8d21dff8140 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -8,10 +8,11 @@ #include #include #include -#include -#include +#include +#include +#include #include -#include +#include #include #include @@ -20,7 +21,7 @@ namespace fc { class variant; } namespace eosio { - using chain::chain_controller; + using chain::controller; using std::unique_ptr; using namespace appbase; using chain::name; @@ -31,8 +32,8 @@ namespace eosio { using chain::asset; using chain::authority; using chain::account_name; - using chain::contracts::abi_def; - using chain::contracts::abi_serializer; + using chain::abi_def; + using chain::abi_serializer; namespace chain_apis { struct empty{}; @@ -47,12 +48,12 @@ template struct resolver_factory; class read_only { - const chain_controller& db; + const controller& db; public: static const string KEYi64; - read_only(const chain_controller& db) + read_only(const controller& db) : db(db) {} using get_info_params = empty; @@ -61,9 +62,16 @@ class read_only { string server_version; uint32_t head_block_num = 0; uint32_t last_irreversible_block_num = 0; + chain::block_id_type last_irreversible_block_id; chain::block_id_type head_block_id; fc::time_point_sec head_block_time; account_name head_block_producer; + + uint64_t virtual_block_cpu_limit = 0; + uint64_t virtual_block_net_limit = 0; + + uint64_t block_cpu_limit = 0; + uint64_t block_net_limit = 0; //string recent_slots; //double participation_rate = 0; }; @@ -73,10 +81,27 @@ class read_only { name producer_name; }; + using account_resource_limit = chain::resource_limits::account_resource_limit; struct get_account_results { name account_name; + bool privileged = false; + fc::time_point last_code_update; + fc::time_point created; + + int64_t ram_quota = 0; + int64_t net_weight = 0; + int64_t cpu_weight = 0; + + account_resource_limit net_limit; + account_resource_limit cpu_limit; + int64_t ram_usage = 0; + vector permissions; + + fc::variant total_resources; + fc::variant delegated_bandwidth; + fc::variant voter_info; }; struct get_account_params { @@ -106,8 +131,6 @@ class read_only { }; struct abi_json_to_bin_result { vector binargs; - vector required_scope; - vector required_auth; }; abi_json_to_bin_result abi_json_to_bin( const abi_json_to_bin_params& params )const; @@ -185,7 +208,7 @@ class read_only { fc::variant get_currency_stats( const get_currency_stats_params& params )const; - static void copy_inline_row(const chain::contracts::key_value_object& obj, vector& data) { + static void copy_inline_row(const chain::key_value_object& obj, vector& data) { data.resize( obj.value.size() ); memcpy( data.data(), obj.value.data(), obj.value.size() ); } @@ -193,8 +216,8 @@ class read_only { template void walk_table(const name& code, const name& scope, const name& table, Function f) const { - const auto& d = db.get_database(); - const auto* t_id = d.find(boost::make_tuple(code, scope, table)); + const auto& d = db.db(); + const auto* t_id = d.find(boost::make_tuple(code, scope, table)); if (t_id != nullptr) { const auto &idx = d.get_index(); decltype(t_id->id) next_tid(t_id->id._id + 1); @@ -212,7 +235,7 @@ class read_only { template read_only::get_table_rows_result get_table_rows_ex( const read_only::get_table_rows_params& p, const abi_def& abi )const { read_only::get_table_rows_result result; - const auto& d = db.get_database(); + const auto& d = db.db(); uint64_t scope = 0; try { @@ -239,7 +262,7 @@ class read_only { abi_serializer abis; abis.set_abi(abi); - const auto* t_id = d.find(boost::make_tuple(p.code, scope, p.table)); + const auto* t_id = d.find(boost::make_tuple(p.code, scope, p.table)); if (t_id != nullptr) { const auto &idx = d.get_index(); decltype(t_id->id) next_tid(t_id->id._id + 1); @@ -285,10 +308,9 @@ class read_only { }; class read_write { - chain_controller& db; - uint32_t skip_flags; + controller& db; public: - read_write(chain_controller& db, uint32_t skip_flags) : db(db), skip_flags(skip_flags) {} + read_write(controller& db) : db(db) {} using push_block_params = chain::signed_block; using push_block_results = empty; @@ -326,22 +348,19 @@ class chain_plugin : public plugin { chain_apis::read_only get_read_only_api() const { return chain_apis::read_only(chain()); } chain_apis::read_write get_read_write_api(); - bool accept_block(const chain::signed_block& block, bool currently_syncing); + void accept_block( const chain::signed_block_ptr& block ); void accept_transaction(const chain::packed_transaction& trx); bool block_is_on_preferred_chain(const chain::block_id_type& block_id); - // return true if --skip-transaction-signatures passed to eosd - bool is_skipping_transaction_signatures() const; - - // Only call this in plugin_initialize() to modify chain_controller constructor configuration - chain_controller::controller_config& chain_config(); + // Only call this in plugin_initialize() to modify controller constructor configuration + controller::config& chain_config(); // Only call this after plugin_startup()! - chain_controller& chain(); + controller& chain(); // Only call this after plugin_startup()! - const chain_controller& chain() const; + const controller& chain() const; - void get_chain_id (chain::chain_id_type &cid) const; + void get_chain_id(chain::chain_id_type& cid) const; private: unique_ptr my; @@ -352,7 +371,7 @@ class chain_plugin : public plugin { FC_REFLECT( eosio::chain_apis::permission, (perm_name)(parent)(required_auth) ) FC_REFLECT(eosio::chain_apis::empty, ) FC_REFLECT(eosio::chain_apis::read_only::get_info_results, - (server_version)(head_block_num)(last_irreversible_block_num)(head_block_id)(head_block_time)(head_block_producer) ) +(server_version)(head_block_num)(last_irreversible_block_num)(last_irreversible_block_id)(head_block_id)(head_block_time)(head_block_producer)(virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit) ) FC_REFLECT(eosio::chain_apis::read_only::get_block_params, (block_num_or_id)) FC_REFLECT( eosio::chain_apis::read_write::push_transaction_results, (transaction_id)(processed) ) @@ -364,13 +383,13 @@ FC_REFLECT( eosio::chain_apis::read_only::get_currency_balance_params, (code)(ac FC_REFLECT( eosio::chain_apis::read_only::get_currency_stats_params, (code)(symbol)); FC_REFLECT( eosio::chain_apis::read_only::get_currency_stats_result, (supply)(max_supply)(issuer)); -FC_REFLECT( eosio::chain_apis::read_only::get_account_results, (account_name)(permissions) ) +FC_REFLECT( eosio::chain_apis::read_only::get_account_results, (account_name)(privileged)(last_code_update)(created)(ram_quota)(net_weight)(cpu_weight)(net_limit)(cpu_limit)(ram_usage)(permissions)(total_resources)(delegated_bandwidth)(voter_info) ) FC_REFLECT( eosio::chain_apis::read_only::get_code_results, (account_name)(code_hash)(wast)(abi) ) FC_REFLECT( eosio::chain_apis::read_only::get_account_params, (account_name) ) FC_REFLECT( eosio::chain_apis::read_only::get_code_params, (account_name) ) FC_REFLECT( eosio::chain_apis::read_only::producer_info, (producer_name) ) FC_REFLECT( eosio::chain_apis::read_only::abi_json_to_bin_params, (code)(action)(args) ) -FC_REFLECT( eosio::chain_apis::read_only::abi_json_to_bin_result, (binargs)(required_scope)(required_auth) ) +FC_REFLECT( eosio::chain_apis::read_only::abi_json_to_bin_result, (binargs) ) FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_params, (code)(action)(binargs) ) FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_result, (args)(required_scope)(required_auth) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_params, (transaction)(available_keys) ) diff --git a/plugins/faucet_testnet_plugin/faucet_testnet_plugin.cpp b/plugins/faucet_testnet_plugin/faucet_testnet_plugin.cpp index 6b7acb31eef..f21aebc36d8 100644 --- a/plugins/faucet_testnet_plugin/faucet_testnet_plugin.cpp +++ b/plugins/faucet_testnet_plugin/faucet_testnet_plugin.cpp @@ -221,7 +221,7 @@ struct faucet_testnet_plugin_impl { chain::chain_id_type chainid; auto& plugin = _app.get_plugin(); plugin.get_chain_id(chainid); - chain_controller& cc = plugin.chain(); + controller& cc = plugin.chain(); signed_transaction trx; auto memo = fc::variant(fc::time_point::now()).as_string() + " " + fc::variant(fc::time_point::now().time_since_epoch()).as_string(); @@ -232,14 +232,14 @@ struct faucet_testnet_plugin_impl { auto recovery_auth = chain::authority{1, {}, {{{_create_account_name, "active"}, 1}}}; trx.actions.emplace_back(vector{{_create_account_name,"active"}}, - contracts::newaccount{_create_account_name, new_account_name, owner_auth, active_auth, recovery_auth}); + newaccount{_create_account_name, new_account_name, owner_auth, active_auth, recovery_auth}); trx.expiration = cc.head_block_time() + fc::seconds(30); trx.set_reference_block(cc.head_block_id()); trx.sign(_create_account_private_key, chainid); try { - cc.push_transaction(packed_transaction(trx)); + cc.push_transaction( std::make_shared(trx) ); } catch (const account_name_exists_exception& ) { // another transaction ended up adding the account, so look for alternates return find_alternates(new_account_name); @@ -260,7 +260,7 @@ struct faucet_testnet_plugin_impl { const chainbase::database& database() { static const chainbase::database* db = nullptr; if (db == nullptr) - db = &_app.get_plugin().chain().get_database(); + db = &_app.get_plugin().chain().db(); return *db; } diff --git a/plugins/history_api_plugin/CMakeLists.txt b/plugins/history_api_plugin/CMakeLists.txt new file mode 100644 index 00000000000..1858af33b02 --- /dev/null +++ b/plugins/history_api_plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +file( GLOB HEADERS "include/eosio/history_api_plugin/*.hpp" ) +add_library( history_api_plugin + history_api_plugin.cpp + ${HEADERS} ) + +target_link_libraries( history_api_plugin history_plugin chain_plugin http_plugin appbase ) +target_include_directories( history_api_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/history_api_plugin/history_api_plugin.cpp b/plugins/history_api_plugin/history_api_plugin.cpp new file mode 100644 index 00000000000..9760c01bedb --- /dev/null +++ b/plugins/history_api_plugin/history_api_plugin.cpp @@ -0,0 +1,59 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include + +#include + +namespace eosio { + +using namespace eosio; + +static appbase::abstract_plugin& _history_api_plugin = app().register_plugin(); + +history_api_plugin::history_api_plugin(){} +history_api_plugin::~history_api_plugin(){} + +void history_api_plugin::set_program_options(options_description&, options_description&) {} +void history_api_plugin::plugin_initialize(const variables_map&) {} + +#define CALL(api_name, api_handle, api_namespace, call_name) \ +{std::string("/v1/" #api_name "/" #call_name), \ + [this, api_handle](string, string body, url_response_callback cb) mutable { \ + try { \ + if (body.empty()) body = "{}"; \ + auto result = api_handle.call_name(fc::json::from_string(body).as()); \ + cb(200, fc::json::to_string(result)); \ + } catch (fc::eof_exception& e) { \ + error_results results{400, "Bad Request", e}; \ + cb(400, fc::json::to_string(results)); \ + elog("Unable to parse arguments: ${args}", ("args", body)); \ + } catch (fc::exception& e) { \ + error_results results{500, "Internal Service Error", e}; \ + cb(500, fc::json::to_string(results)); \ + elog("Exception encountered while processing ${call}: ${e}", ("call", #api_name "." #call_name)("e", e)); \ + } \ + }} + +#define CHAIN_RO_CALL(call_name) CALL(history, ro_api, history_apis::read_only, call_name) +//#define CHAIN_RW_CALL(call_name) CALL(history, rw_api, history_apis::read_write, call_name) + +void history_api_plugin::plugin_startup() { + ilog( "starting history_api_plugin" ); + auto ro_api = app().get_plugin().get_read_only_api(); + //auto rw_api = app().get_plugin().get_read_write_api(); + + app().get_plugin().add_api({ +// CHAIN_RO_CALL(get_transaction), + CHAIN_RO_CALL(get_actions), + CHAIN_RO_CALL(get_transaction), +// CHAIN_RO_CALL(get_key_accounts), +// CHAIN_RO_CALL(get_controlled_accounts) + }); +} + +void history_api_plugin::plugin_shutdown() {} + +} diff --git a/plugins/history_api_plugin/include/eosio/history_api_plugin/history_api_plugin.hpp b/plugins/history_api_plugin/include/eosio/history_api_plugin/history_api_plugin.hpp new file mode 100644 index 00000000000..b50cea51d88 --- /dev/null +++ b/plugins/history_api_plugin/include/eosio/history_api_plugin/history_api_plugin.hpp @@ -0,0 +1,33 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +#pragma once +#include +#include +#include + +#include + +namespace eosio { + + using namespace appbase; + + class history_api_plugin : public plugin { + public: + APPBASE_PLUGIN_REQUIRES((history_plugin)(chain_plugin)(http_plugin)) + + history_api_plugin(); + virtual ~history_api_plugin(); + + virtual void set_program_options(options_description&, options_description&) override; + + void plugin_initialize(const variables_map&); + void plugin_startup(); + void plugin_shutdown(); + + private: + }; + +} diff --git a/plugins/history_plugin/CMakeLists.txt b/plugins/history_plugin/CMakeLists.txt new file mode 100644 index 00000000000..1eca3d247e0 --- /dev/null +++ b/plugins/history_plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB HEADERS "include/eosio/history_plugin/*.hpp") +add_library( history_plugin + history_plugin.cpp + ${HEADERS} ) + +target_link_libraries( history_plugin chain_plugin eosio_chain appbase ) +target_include_directories( history_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/history_plugin/history_plugin.cpp b/plugins/history_plugin/history_plugin.cpp new file mode 100644 index 00000000000..cebd51d6882 --- /dev/null +++ b/plugins/history_plugin/history_plugin.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include + +#include + +namespace eosio { + using namespace chain; + + static appbase::abstract_plugin& _history_plugin = app().register_plugin(); + + + struct account_history_object : public chainbase::object { + OBJECT_CTOR( account_history_object ); + + id_type id; + account_name account; ///< the name of the account which has this action in its history + uint64_t action_sequence_num = 0; ///< the sequence number of the relevant action (global) + int32_t account_sequence_num = 0; ///< the sequence number for this account (per-account) + }; + + struct action_history_object : public chainbase::object { + + OBJECT_CTOR( action_history_object, (packed_action_trace) ); + + id_type id; + uint64_t action_sequence_num; ///< the sequence number of the relevant action + + shared_string packed_action_trace; + uint32_t block_num; + block_timestamp_type block_time; + transaction_id_type trx_id; + }; + using account_history_id_type = account_history_object::id_type; + using action_history_id_type = action_history_object::id_type; + + + struct by_action_sequence_num; + struct by_account_action_seq; + struct by_trx_id; + + using action_history_index = chainbase::shared_multi_index_container< + action_history_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>, + ordered_unique, + composite_key< action_history_object, + member, + member + > + > + > + >; + + using account_history_index = chainbase::shared_multi_index_container< + account_history_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key< account_history_object, + member, + member + > + > + > + >; + +} /// namespace eosio + +CHAINBASE_SET_INDEX_TYPE(eosio::account_history_object, eosio::account_history_index) +CHAINBASE_SET_INDEX_TYPE(eosio::action_history_object, eosio::action_history_index) + +namespace eosio { + + class history_plugin_impl { + public: + std::set filter_on; + chain_plugin* chain_plug = nullptr; + + bool is_filtered( const account_name& n ) { + return !filter_on.size() || filter_on.find(n) != filter_on.end(); + } + bool filter( const action_trace& act ) { + if( filter_on.size() == 0 ) return true; + if( is_filtered( act.receipt.receiver ) ) return true; + for( const auto& a : act.act.authorization ) { + if( is_filtered( a.actor ) ) return true; + } + return false; + } + + set account_set( const action_trace& act ) { + set result; + + if( is_filtered( act.receipt.receiver ) ) + result.insert( act.receipt.receiver ); + for( const auto& a : act.act.authorization ) { + if( is_filtered( a.actor ) ) result.insert( a.actor ); + } + return result; + } + + void record_account_action( account_name n, const base_action_trace& act ) { + auto& chain = chain_plug->chain(); + auto& db = chain.db(); + + const auto& idx = db.get_index(); + auto itr = idx.lower_bound( boost::make_tuple( name(n.value+1), 0 ) ); + + uint64_t asn = 0; + if( itr != idx.begin() ) --itr; + if( itr->account == n ) + asn = itr->account_sequence_num + 1; + + //idump((n)(act.receipt.global_sequence)(asn)); + const auto& a = db.create( [&]( auto& aho ) { + aho.account = n; + aho.action_sequence_num = act.receipt.global_sequence; + aho.account_sequence_num = asn; + }); + //idump((a.account)(a.action_sequence_num)(a.action_sequence_num)); + } + + void on_action_trace( const action_trace& at ) { + if( filter( at ) ) { + //idump((fc::json::to_pretty_string(at))); + auto& chain = chain_plug->chain(); + auto& db = chain.db(); + + db.create( [&]( auto& aho ) { + auto ps = fc::raw::pack_size( at ); + aho.packed_action_trace.resize(ps); + datastream ds( aho.packed_action_trace.data(), ps ); + fc::raw::pack( ds, at ); + aho.action_sequence_num = at.receipt.global_sequence; + aho.block_num = chain.pending_block_state()->block_num; + aho.block_time = chain.pending_block_time(); + aho.trx_id = at.trx_id; + }); + + auto aset = account_set( at ); + for( auto a : aset ) { + record_account_action( a, at ); + } + } + for( const auto& iline : at.inline_traces ) { + on_action_trace( iline ); + } + } + + void on_applied_transaction( const transaction_trace_ptr& trace ) { + for( const auto& atrace : trace->action_traces ) { + on_action_trace( atrace ); + } + } + }; + + history_plugin::history_plugin() + :my(std::make_shared()) { + } + + history_plugin::~history_plugin() { + } + + + + void history_plugin::set_program_options(options_description& cli, options_description& cfg) { + cfg.add_options() + ("filter_on_accounts,f", bpo::value>()->composing(), + "Track only transactions whose scopes involve the listed accounts. Default is to track all transactions.") + ; + } + + void history_plugin::plugin_initialize(const variables_map& options) { + if(options.count("filter_on_accounts")) + { + auto foa = options.at("filter_on_accounts").as>(); + for(auto filter_account : foa) + my->filter_on.emplace(filter_account); + } + + my->chain_plug = app().find_plugin(); + auto& chain = my->chain_plug->chain(); + + chain.db().add_index(); + chain.db().add_index(); + + chain.applied_transaction.connect( [&]( const transaction_trace_ptr& p ){ + my->on_applied_transaction(p); + }); + + } + + void history_plugin::plugin_startup() { + } + + void history_plugin::plugin_shutdown() { + } + + + + + namespace history_apis { + read_only::get_actions_result read_only::get_actions( const read_only::get_actions_params& params )const { + edump((params)); + auto& chain = history->chain_plug->chain(); + const auto& db = chain.db(); + + const auto& idx = db.get_index(); + + int32_t start = 0; + int32_t pos = params.pos ? *params.pos : -1; + int32_t end = 0; + int32_t offset = params.offset ? *params.offset : -20; + auto n = params.account_name; + idump((pos)); + if( pos == -1 ) { + auto itr = idx.lower_bound( boost::make_tuple( name(n.value+1), 0 ) ); + if( itr == idx.begin() ) { + if( itr->account == n ) + pos = itr->account_sequence_num+1; + } else if( itr != idx.begin() ) --itr; + + if( itr->account == n ) + pos = itr->account_sequence_num + 1; + } + + if( pos== -1 ) pos = 0xfffffff; + + if( offset > 0 ) { + start = pos; + end = start + offset; + } else { + start = pos + offset; + if( start > pos ) start = 0; + end = pos; + } + FC_ASSERT( end >= start ); + + idump((start)(end)); + + auto start_itr = idx.lower_bound( boost::make_tuple( n, start ) ); + auto end_itr = idx.lower_bound( boost::make_tuple( n, end+1) ); + + auto start_time = fc::time_point::now(); + auto end_time = start_time; + + get_actions_result result; + result.last_irreversible_block = chain.last_irreversible_block_num(); + while( start_itr != end_itr ) { + const auto& a = db.get( start_itr->action_sequence_num ); + fc::datastream ds( a.packed_action_trace.data(), a.packed_action_trace.size() ); + action_trace t; + fc::raw::unpack( ds, t ); + result.actions.emplace_back( ordered_action_result{ + start_itr->action_sequence_num, + start_itr->account_sequence_num, + a.block_num, a.block_time, + chain.to_variant_with_abi(t) + }); + + end_time = fc::time_point::now(); + if( end_time - start_time > fc::microseconds(100000) ) { + result.time_limit_exceeded_error = true; + break; + } + ++start_itr; + } + return result; + } + + + read_only::get_transaction_result read_only::get_transaction( const read_only::get_transaction_params& p )const { + auto& chain = history->chain_plug->chain(); + + get_transaction_result result; + + result.id = p.id; + result.last_irreversible_block = chain.last_irreversible_block_num(); + + const auto& db = chain.db(); + + const auto& idx = db.get_index(); + auto itr = idx.lower_bound( boost::make_tuple(p.id) ); + if( itr == idx.end() ) { + return result; + } + result.id = itr->trx_id; + result.block_num = itr->block_num; + result.block_time = itr->block_time; + + if( fc::variant(result.id).as_string().substr(0,8) != fc::variant(p.id).as_string().substr(0,8) ) + return result; + + while( itr != idx.end() && itr->trx_id == result.id ) { + + fc::datastream ds( itr->packed_action_trace.data(), itr->packed_action_trace.size() ); + action_trace t; + fc::raw::unpack( ds, t ); + result.traces.emplace_back( chain.to_variant_with_abi(t) ); + + ++itr; + } + + auto blk = chain.fetch_block_by_number( result.block_num ); + if( blk == nullptr ) { // still in pending + auto blk_state = chain.pending_block_state(); + if( blk_state != nullptr ) { + blk = blk_state->block; + } + } + if( blk != nullptr ) { + for (const auto &receipt: blk->transactions) { + if (receipt.trx.contains()) { + auto &pt = receipt.trx.get(); + auto mtrx = transaction_metadata(pt); + if (mtrx.id == result.id) { + fc::mutable_variant_object r("receipt", receipt); + r("trx", chain.to_variant_with_abi(mtrx.trx)); + result.trx = move(r); + break; + } + } else { + auto &id = receipt.trx.get(); + if (id == result.id) { + fc::mutable_variant_object r("receipt", receipt); + result.trx = move(r); + break; + } + } + } + } + + return result; + } + + } /// history_apis + + + +} /// namespace eosio diff --git a/plugins/history_plugin/include/eosio/history_plugin.hpp b/plugins/history_plugin/include/eosio/history_plugin.hpp new file mode 100644 index 00000000000..cc84eecbc77 --- /dev/null +++ b/plugins/history_plugin/include/eosio/history_plugin.hpp @@ -0,0 +1,169 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once +#include + +#include + +namespace fc { class variant; } + +namespace eosio { + using chain::transaction_id_type; + using std::shared_ptr; + using namespace appbase; + using chain::name; + using fc::optional; + using chain::uint128_t; + + typedef shared_ptr history_ptr; + typedef shared_ptr history_const_ptr; + +namespace history_apis { + +class read_only { + history_const_ptr history; + + public: + read_only(history_const_ptr&& history) + : history(history) {} + + + /* + struct get_transaction_params { + chain::transaction_id_type transaction_id; + }; + struct get_transaction_results { + chain::transaction_id_type transaction_id; + fc::variant transaction; + }; + get_transaction_results get_transaction(const get_transaction_params& params) const; + */ + + + struct get_actions_params { + chain::account_name account_name; + optional pos; /// a absolute sequence positon -1 is the end/last action + optional offset; ///< the number of actions relative to pos, negative numbers return [pos-offset,pos), positive numbers return [pos,pos+offset) + }; + + struct ordered_action_result { + uint64_t global_action_seq = 0; + int32_t account_action_seq = 0; + uint32_t block_num; + chain::block_timestamp_type block_time; + fc::variant action_trace; + }; + + struct get_actions_result { + vector actions; + uint32_t last_irreversible_block; + optional time_limit_exceeded_error; + }; + + + get_actions_result get_actions( const get_actions_params& )const; + + + struct get_transaction_params { + transaction_id_type id; + }; + + struct get_transaction_result { + transaction_id_type id; + fc::variant trx; + chain::block_timestamp_type block_time; + uint32_t block_num = 0; + uint32_t last_irreversible_block = 0; + vector traces; + }; + + get_transaction_result get_transaction( const get_transaction_params& )const; + + + + + /* + struct ordered_transaction_results { + uint32_t seq_num; + chain::transaction_id_type transaction_id; + fc::variant transaction; + }; + + get_transactions_results get_transactions(const get_transactions_params& params) const; + */ + + + struct get_key_accounts_params { + chain::public_key_type public_key; + }; + struct get_key_accounts_results { + vector account_names; + }; + get_key_accounts_results get_key_accounts(const get_key_accounts_params& params) const; + + + struct get_controlled_accounts_params { + chain::account_name controlling_account; + }; + struct get_controlled_accounts_results { + vector controlled_accounts; + }; + get_controlled_accounts_results get_controlled_accounts(const get_controlled_accounts_params& params) const; +}; + + +} // namespace history_apis + + +/** + * This plugin tracks all actions and keys associated with a set of configured accounts. It enables + * wallets to paginate queries for history. + * + * An action will be included in the account's history if any of the following: + * - receiver + * - any account named in auth list + * + * A key will be linked to an account if the key is referneced in authorities of updateauth or newaccount + */ +class history_plugin : public plugin { + public: + APPBASE_PLUGIN_REQUIRES((chain_plugin)) + + history_plugin(); + virtual ~history_plugin(); + + virtual void set_program_options(options_description& cli, options_description& cfg) override; + + void plugin_initialize(const variables_map& options); + void plugin_startup(); + void plugin_shutdown(); + + history_apis::read_only get_read_only_api()const { return history_apis::read_only(history_const_ptr(my)); } + + private: + history_ptr my; +}; + +} /// namespace eosio + +FC_REFLECT( eosio::history_apis::read_only::get_actions_params, (account_name)(pos)(offset) ) +FC_REFLECT( eosio::history_apis::read_only::get_actions_result, (actions)(last_irreversible_block)(time_limit_exceeded_error) ) +FC_REFLECT( eosio::history_apis::read_only::ordered_action_result, (global_action_seq)(account_action_seq)(block_num)(block_time)(action_trace) ) + +FC_REFLECT( eosio::history_apis::read_only::get_transaction_params, (id) ) +FC_REFLECT( eosio::history_apis::read_only::get_transaction_result, (id)(trx)(block_time)(block_num)(last_irreversible_block)(traces) ) +/* +FC_REFLECT(eosio::history_apis::read_only::get_transaction_params, (transaction_id) ) +FC_REFLECT(eosio::history_apis::read_only::get_transaction_results, (transaction_id)(transaction) ) +FC_REFLECT(eosio::history_apis::read_only::get_transactions_params, (account_name)(skip_seq)(num_seq) ) +FC_REFLECT(eosio::history_apis::read_only::ordered_transaction_results, (seq_num)(transaction_id)(transaction) ) +FC_REFLECT(eosio::history_apis::read_only::get_transactions_results, (transactions)(time_limit_exceeded_error) ) +FC_REFLECT(eosio::history_apis::read_only::get_key_accounts_params, (public_key) ) +FC_REFLECT(eosio::history_apis::read_only::get_key_accounts_results, (account_names) ) +FC_REFLECT(eosio::history_apis::read_only::get_controlled_accounts_params, (controlling_account) ) +FC_REFLECT(eosio::history_apis::read_only::get_controlled_accounts_results, (controlled_accounts) ) +*/ + + diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index c8e775b4051..536bf6696df 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -3,7 +3,7 @@ * @copyright defined in eos/LICENSE.txt */ #include -#include +#include #include #include #include @@ -249,7 +249,7 @@ namespace { } abi_serializer abis; if (msg.account == chain::config::system_account_name) { - abi = chain::contracts::chain_initializer::eos_contract_abi(abi); + abi = chain::eosio_contract_abi(abi); } abis.set_abi(abi); auto v = abis.binary_to_variant(abis.get_action_type(msg.name), msg.data); @@ -472,7 +472,7 @@ void mongo_db_plugin_impl::_process_block(const block_trace& bt, const signed_bl (trx_trace.status == chain::transaction_receipt::hard_fail) ? "hard_fail" : "unknown"; trx_status_map[trx_trace.id] = trx_status; - + for (const auto& req : trx_trace.deferred_transaction_requests) { if ( req.contains() ) { auto trx = req.get(); @@ -666,7 +666,7 @@ void mongo_db_plugin_impl::update_account(const chain::action& msg) { } else if (msg.name == newaccount) { auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); - auto newaccount = msg.data_as(); + auto newaccount = msg.data_as(); // create new account bsoncxx::builder::stream::document doc{}; @@ -683,7 +683,7 @@ void mongo_db_plugin_impl::update_account(const chain::action& msg) { } else if (msg.name == setabi) { auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); - auto setabi = msg.data_as(); + auto setabi = msg.data_as(); auto from_account = find_account(accounts, setabi.account); document update_from{}; diff --git a/plugins/net_plugin/CMakeLists.txt b/plugins/net_plugin/CMakeLists.txt index 5485d834470..5d6515319d2 100644 --- a/plugins/net_plugin/CMakeLists.txt +++ b/plugins/net_plugin/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB HEADERS "include/eosio/net_plugin/*.hpp") +file(GLOB HEADERS "include/eosio/net_plugin/*.hpp" ) add_library( net_plugin net_plugin.cpp ${HEADERS} ) target_link_libraries( net_plugin chain_plugin producer_plugin appbase fc ) -target_include_directories( net_plugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) +target_include_directories( net_plugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include ) diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 940ee1e9f0a..586c4ae4aa6 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -14,6 +14,13 @@ namespace eosio { static_assert(sizeof(std::chrono::system_clock::duration::rep) >= 8, "system_clock is expected to be at least 64 bits"); typedef std::chrono::system_clock::duration::rep tstamp; + struct chain_size_message { + uint32_t last_irreversible_block_num = 0; + block_id_type last_irreversible_block_id; + uint32_t head_num = 0; + block_id_type head_id; + }; + struct handshake_message { uint16_t network_version = 0; ///< incremental value above a computed base chain_id_type chain_id; ///< used to identify chain @@ -32,6 +39,7 @@ namespace eosio { int16_t generation; }; + enum go_away_reason { no_reason, ///< no reason to go away self, ///< the connection is to itself @@ -130,19 +138,21 @@ namespace eosio { }; using net_message = static_variant; } // namespace eosio FC_REFLECT( eosio::select_ids, (mode)(pending)(ids) ) +FC_REFLECT( eosio::chain_size_message, + (last_irreversible_block_num)(last_irreversible_block_id) + (head_num)(head_id)) FC_REFLECT( eosio::handshake_message, (network_version)(chain_id)(node_id)(key) (time)(token)(sig)(p2p_address) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 6502a017426..28977898f15 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -6,14 +6,15 @@ #include #include -#include -#include +#include #include #include +#include #include #include -#include +#include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include +using namespace eosio::chain::plugin_interface::compat; + namespace fc { extern std::unordered_map& get_logger_map(); } @@ -41,16 +44,16 @@ namespace eosio { using boost::asio::ip::address_v4; using boost::asio::ip::host_name; using boost::intrusive::rbtree; + using boost::multi_index_container; using fc::time_point; using fc::time_point_sec; using eosio::chain::transaction_id_type; namespace bip = boost::interprocess; - using chain::contracts::uint16; class connection; class sync_manager; - class big_msg_manager; + class dispatch_manager; using connection_ptr = std::shared_ptr; using connection_wptr = std::weak_ptr; @@ -165,7 +168,7 @@ namespace eosio { std::set< connection_ptr > connections; bool done = false; unique_ptr< sync_manager > sync_master; - unique_ptr< big_msg_manager > big_msg_master; + unique_ptr< dispatch_manager > dispatcher; unique_ptr connector_check; unique_ptr transaction_check; @@ -189,6 +192,8 @@ namespace eosio { shared_ptr resolver; + channels::transaction_ack::channel_type::handle incoming_transaction_ack_subscription; + void connect( connection_ptr c ); void connect( connection_ptr c, tcp::resolver::iterator endpoint_itr ); void start_session( connection_ptr c ); @@ -201,12 +206,19 @@ namespace eosio { template void send_all( const net_message &msg, VerifierFunc verify ); - static void transaction_ready( const transaction_metadata&, const packed_transaction& txn); - void broadcast_block_impl( const signed_block &sb); + void accepted_block_header(const block_state_ptr&); + void accepted_block(const block_state_ptr&); + void irreversible_block(const block_state_ptr&); + void accepted_transaction(const transaction_metadata_ptr&); + void applied_transaction(const transaction_trace_ptr&); + void accepted_confirmation(const header_confirmation&); + + void transaction_ack(const std::pair&); bool is_valid( const handshake_message &msg); void handle_message( connection_ptr c, const handshake_message &msg); + void handle_message( connection_ptr c, const chain_size_message &msg); void handle_message( connection_ptr c, const go_away_message &msg ); /** \name Peer Timestamps * Time message handling @@ -226,10 +238,8 @@ namespace eosio { void handle_message( connection_ptr c, const notice_message &msg); void handle_message( connection_ptr c, const request_message &msg); void handle_message( connection_ptr c, const sync_request_message &msg); - void handle_message( connection_ptr c, const signed_block_summary &msg); void handle_message( connection_ptr c, const signed_block &msg); void handle_message( connection_ptr c, const packed_transaction &msg); - void handle_message( connection_ptr c, const signed_transaction &msg); void start_conn_timer( ); void start_txn_timer( ); @@ -368,7 +378,7 @@ namespace eosio { /** * */ - struct block_state { + struct peer_block_state { block_id_type id; uint32_t block_num; bool is_known; @@ -380,22 +390,22 @@ namespace eosio { void operator() (struct transaction_state &ts) { ts.requested_time = time_point::now(); } - void operator () (struct block_state &bs) { + void operator () (struct eosio::peer_block_state &bs) { bs.requested_time = time_point::now(); } } set_request_time; typedef multi_index_container< - block_state, + eosio::peer_block_state, indexed_by< - ordered_unique< tag, member >, - ordered_unique< tag, member > + ordered_unique< tag, member >, + ordered_unique< tag, member > > - > block_state_index; + > peer_block_state_index; struct update_known_by_peer { - void operator() (block_state& bs) { + void operator() (eosio::peer_block_state& bs) { bs.is_known = true; } void operator() (transaction_state& ts) { @@ -429,12 +439,12 @@ namespace eosio { ~connection(); void initialize(); - block_state_index blk_state; + peer_block_state_index blk_state; transaction_state_index trx_state; optional peer_requested; // this peer is requesting info from us socket_ptr socket; - message_buffer<1024*1024> pending_message_buffer; + fc::message_buffer<1024*1024> pending_message_buffer; vector blk_buffer; struct queued_write { @@ -606,24 +616,37 @@ namespace eosio { }; - class big_msg_manager { + class dispatch_manager { public: uint32_t just_send_it_max; - connection_ptr pending_txn_source; + struct block_request { block_id_type id; bool local_retry; }; vector req_blks; - vector req_txn; - - void bcast_block (const signed_block_summary& msg, connection_ptr skip = connection_ptr()); - void bcast_transaction (const transaction_id_type& id, - time_point_sec expiration, - const packed_transaction& msg); - void rejected_transaction (const packed_transaction& msg); - void recv_block (connection_ptr conn, const signed_block_summary& msg); - void recv_transaction(connection_ptr c, const transaction_id_type& id); + vector req_trx; + + struct block_origin { + block_id_type id; + connection_ptr origin; + }; + + struct transaction_origin { + transaction_id_type id; + connection_ptr origin; + }; + + vector received_blocks; + vector received_transactions; + + void bcast_transaction (const packed_transaction& msg); + void rejected_transaction (const transaction_id_type& msg); + void bcast_block (const signed_block& msg); + void rejected_block (const block_id_type &id); + + void recv_block (connection_ptr conn, const block_id_type& msg); + void recv_transaction(connection_ptr conn, const transaction_id_type& id); void recv_notice (connection_ptr conn, const notice_message& msg, bool generated); void retry_fetch (connection_ptr conn); @@ -729,7 +752,7 @@ namespace eosio { connecting = false; syncing = false; if( last_req ) { - my_impl->big_msg_master->retry_fetch (shared_from_this()); + my_impl->dispatcher->retry_fetch (shared_from_this()); } reset(); sent_handshake_count = 0; @@ -778,7 +801,7 @@ namespace eosio { } void connection::blk_send_branch() { - chain_controller &cc = my_impl->chain_plug->chain(); + controller &cc = my_impl->chain_plug->chain(); uint32_t head_num = cc.head_block_num (); notice_message note; note.known_blocks.mode = normal; @@ -793,9 +816,8 @@ namespace eosio { uint32_t lib_num; try { lib_num = cc.last_irreversible_block_num(); - if( lib_num != 0 ) - lib_id = cc.get_block_id_for_num(lib_num); - head_id = cc.get_block_id_for_num(head_num); + lib_id = cc.last_irreversible_block_id(); + head_id = cc.head_block_id(); } catch (const assert_exception &ex) { elog( "unable to retrieve block info: ${n} for ${p}",("n",ex.to_string())("p",peer_name())); @@ -807,11 +829,11 @@ namespace eosio { catch (...) { } - vector > bstack; + vector bstack; block_id_type null_id; for (auto bid = head_id; bid != null_id && bid != lib_id; ) { try { - optional b = cc.fetch_block_by_id(bid); + signed_block_ptr b = cc.fetch_block_by_id(bid); if ( b ) { bid = b->previous; bstack.push_back(b); @@ -824,25 +846,29 @@ namespace eosio { } } size_t count = 0; - if (bstack.back()->previous == lib_id) { - count = bstack.size(); - while (bstack.size()) { - enqueue (*bstack.back()); - bstack.pop_back(); + if (!bstack.empty()) { + if (bstack.back()->previous == lib_id) { + count = bstack.size(); + while (bstack.size()) { + enqueue(*bstack.back()); + bstack.pop_back(); + } } + fc_ilog(logger, "Sent ${n} blocks on my fork",("n",count)); + } else { + fc_ilog(logger, "Nothing to send on fork request"); } - fc_ilog(logger, "Sent ${n} blocks on my fork",("n",count)); syncing = false; } void connection::blk_send(const vector &ids) { - chain_controller &cc = my_impl->chain_plug->chain(); + controller &cc = my_impl->chain_plug->chain(); int count = 0; for(auto &blkid : ids) { ++count; try { - optional b = cc.fetch_block_by_id(blkid); + signed_block_ptr b = cc.fetch_block_by_id(blkid); if(b) { uint32_t bnum = b->block_num(); bool send_whole = bnum <= cc.last_irreversible_block_num(); @@ -851,8 +877,8 @@ namespace eosio { enqueue(net_message(*b)); } else { - signed_block_summary &sbs = *b; - enqueue(net_message(sbs)); + //signed_block_summary &sbs = *b; + enqueue(net_message(*b)); } } else { @@ -925,15 +951,22 @@ namespace eosio { if(write_queue.empty()) return; write_depth++; + size_t num_buffs = write_queue.size(); + std::vector bufs; + for (auto m: write_queue) { + bufs.push_back(boost::asio::buffer(*m.buff)); + } connection_wptr c(shared_from_this()); - boost::asio::async_write(*socket, boost::asio::buffer(*write_queue.front().buff), [c](boost::system::error_code ec, std::size_t w) { + boost::asio::async_write(*socket, bufs, [c, num_buffs](boost::system::error_code ec, std::size_t w) { try { auto conn = c.lock(); if(!conn) return; - if (conn->write_queue.size() ) { - conn->write_queue.front().cb(ec, w); + if (conn->write_queue.size() >= num_buffs ) { + for (size_t i = 0; i < num_buffs; i++) { + conn->write_queue[i].cb(ec, w); + } } conn->write_depth--; @@ -948,7 +981,9 @@ namespace eosio { my_impl->close(conn); return; } - conn->write_queue.pop_front(); + for (size_t i = 0; i < num_buffs; i++) { + conn->write_queue.pop_front(); + } conn->enqueue_sync_block(); conn->do_queue_write(); } @@ -989,7 +1024,7 @@ namespace eosio { } bool connection::enqueue_sync_block() { - chain_controller& cc = app().find_plugin()->chain(); + controller& cc = app().find_plugin()->chain(); if (!peer_requested) return false; uint32_t num = ++peer_requested->last; @@ -998,7 +1033,7 @@ namespace eosio { peer_requested.reset(); } try { - fc::optional sb = cc.fetch_block_by_number(num); + signed_block_ptr sb = cc.fetch_block_by_number(num); if(sb) { enqueue( *sb, trigger_send); return true; @@ -1094,7 +1129,7 @@ namespace eosio { void connection::fetch_timeout( boost::system::error_code ec ) { if( !ec ) { if( pending_fetch.valid() && !( pending_fetch->req_trx.empty( ) || pending_fetch->req_blocks.empty( ) ) ) { - my_impl->big_msg_master->retry_fetch (shared_from_this() ); + my_impl->dispatcher->retry_fetch (shared_from_this() ); } } else if( ec == boost::asio::error::operation_aborted ) { @@ -1331,7 +1366,7 @@ namespace eosio { } void sync_manager::recv_handshake (connection_ptr c, const handshake_message &msg) { - chain_controller& cc = chain_plug->chain(); + controller& cc = chain_plug->chain(); uint32_t lib_num = cc.last_irreversible_block_num( ); uint32_t peer_lib = msg.last_irreversible_block_num; reset_lib_num(c); @@ -1510,7 +1545,15 @@ namespace eosio { //------------------------------------------------------------------------ - void big_msg_manager::bcast_block (const signed_block_summary &bsum, connection_ptr skip) { + void dispatch_manager::bcast_block (const signed_block &bsum) { + connection_ptr skip; + for (auto org = received_blocks.begin(); org != received_blocks.end(); org++) { + if (org->id == bsum.id()) { + skip = org->origin; + received_blocks.erase(org); + break; + } + } net_message msg(bsum); uint32_t packsiz = fc::raw::pack_size(msg); uint32_t msgsiz = packsiz + sizeof(packsiz); @@ -1529,7 +1572,7 @@ namespace eosio { const auto& bs = c->blk_state.find(bid); bool unknown = bs == c->blk_state.end(); if (unknown) { - c->blk_state.insert((block_state){bid,bnum,false,true,time_point()}); + c->blk_state.insert((eosio::peer_block_state){bid,bnum,false,true,time_point()}); } else { elog("${p} already has knowledge of block ${b}", ("p",c->peer_name())("b",bid)); @@ -1544,68 +1587,88 @@ namespace eosio { } const auto& prev = cp->blk_state.find (bsum.previous); if (prev != cp->blk_state.end() && !prev->is_known) { - cp->blk_state.insert((block_state){bid,bnum,false,true,time_point()}); + cp->blk_state.insert((eosio::peer_block_state){bid,bnum,false,true,time_point()}); cp->enqueue( pending_notify ); } else { - cp->blk_state.insert((block_state){bid,bnum,true,true,time_point()}); + cp->blk_state.insert((eosio::peer_block_state){bid,bnum,true,true,time_point()}); cp->enqueue( bsum ); } } } } - void big_msg_manager::rejected_transaction (const packed_transaction& txn) { - transaction_id_type tid = txn.get_transaction().id(); - fc_dlog(logger,"not sending rejected transaction ${tid}",("tid",tid)); - pending_txn_source.reset(); + void dispatch_manager::rejected_block (const block_id_type& id) { + fc_dlog(logger,"not sending rejected transaction ${tid}",("tid",id)); + for (auto org = received_blocks.begin(); org != received_blocks.end(); org++) { + if (org->id == id) { + received_blocks.erase(org); + break; + } + } + } + void dispatch_manager::rejected_transaction (const transaction_id_type& id) { + fc_dlog(logger,"not sending rejected transaction ${tid}",("tid",id)); + for (auto org = received_transactions.begin(); org != received_transactions.end(); org++) { + if (org->id == id) { + received_transactions.erase(org); + break; + } + } } - void big_msg_manager::bcast_transaction (const transaction_id_type & txnid, - time_point_sec expiration, - const packed_transaction& txn) { - connection_ptr skip = pending_txn_source; - pending_txn_source.reset(); + void dispatch_manager::bcast_transaction (const packed_transaction& trx) { + connection_ptr skip; + transaction_id_type id = trx.id(); + + for (auto org = received_transactions.begin(); org != received_transactions.end(); org++) { + if (org->id == id) { + skip = org->origin; + received_transactions.erase(org); + break; + } + } + - for (auto ref = req_txn.begin(); ref != req_txn.end(); ++ref) { - if (*ref == txnid) { - req_txn.erase(ref); + for (auto ref = req_trx.begin(); ref != req_trx.end(); ++ref) { + if (*ref == id) { + req_trx.erase(ref); break; } } - if( my_impl->local_txns.get().find( txnid ) != my_impl->local_txns.end( ) ) { //found - fc_dlog(logger, "found txnid in local_txns" ); + if( my_impl->local_txns.get().find( id ) != my_impl->local_txns.end( ) ) { //found + fc_dlog(logger, "found trxid in local_trxs" ); return; } uint32_t packsiz = 0; uint32_t bufsiz = 0; - net_message msg(txn); + net_message msg(trx); packsiz = fc::raw::pack_size(msg); bufsiz = packsiz + sizeof(packsiz); vector buff(bufsiz); fc::datastream ds( buff.data(), bufsiz); ds.write( reinterpret_cast(&packsiz), sizeof(packsiz) ); fc::raw::pack( ds, msg ); - node_transaction_state nts = {txnid, - expiration, - txn, + node_transaction_state nts = {id, + trx.expiration(), + trx, std::move(buff), 0, 0, 0}; my_impl->local_txns.insert(std::move(nts)); if(bufsiz <= just_send_it_max) { - my_impl->send_all( txn, [skip, txnid](connection_ptr c) -> bool { + my_impl->send_all( trx, [skip, id](connection_ptr c) -> bool { if(c == skip || c->syncing ) { return false; } - const auto& bs = c->trx_state.find(txnid); + const auto& bs = c->trx_state.find(id); bool unknown = bs == c->trx_state.end(); if( unknown) { - c->trx_state.insert(transaction_state({txnid,true,true,0,time_point() })); - fc_dlog(logger, "sending whole txn to ${n}", ("n",c->peer_name() ) ); + c->trx_state.insert(transaction_state({id,true,true,0,time_point() })); + fc_dlog(logger, "sending whole trx to ${n}", ("n",c->peer_name() ) ); } return unknown; }); @@ -1613,17 +1676,17 @@ namespace eosio { else { notice_message pending_notify; pending_notify.known_trx.mode = normal; - pending_notify.known_trx.ids.push_back( txnid ); + pending_notify.known_trx.ids.push_back( id ); pending_notify.known_blocks.mode = none; - my_impl->send_all(pending_notify, [skip, txnid](connection_ptr c) -> bool { + my_impl->send_all(pending_notify, [skip, id](connection_ptr c) -> bool { if (c == skip || c->syncing) { return false; } - const auto& bs = c->trx_state.find(txnid); + const auto& bs = c->trx_state.find(id); bool unknown = bs == c->trx_state.end(); if( unknown) { fc_dlog(logger, "sending notice to ${n}", ("n",c->peer_name() ) ); - c->trx_state.insert(transaction_state({txnid,false,true,0, time_point() })); + c->trx_state.insert(transaction_state({id,false,true,0, time_point() })); } return unknown; }); @@ -1631,7 +1694,8 @@ namespace eosio { } - void big_msg_manager::recv_block (connection_ptr c, const signed_block_summary& msg) { +#if 0 + void dispatch_manager::recv_block (connection_ptr c, const signed_block& msg) { block_id_type blk_id = msg.id(); uint32_t num = msg.block_num(); const auto& blkstate = c->blk_state.get().find(blk_id); @@ -1661,7 +1725,7 @@ namespace eosio { const auto& bs = conn->blk_state.find(blk_id); bool unknown = bs == conn->blk_state.end(); if (unknown) { - conn->blk_state.insert(block_state({blk_id,num,false,true,fc::time_point() })); + conn->blk_state.insert(eosio::peer_block_state({blk_id,num,false,true,fc::time_point() })); } return unknown; }); @@ -1703,7 +1767,7 @@ namespace eosio { const auto& bs = conn->blk_state.find(blk_id); bool unknown = bs == conn->blk_state.end(); if (unknown) { - conn->blk_state.insert(block_state({blk_id,num,false,true,fc::time_point() })); + conn->blk_state.insert(eosio::peer_block_state({blk_id,num,false,true,fc::time_point() })); } return unknown; }); @@ -1713,13 +1777,27 @@ namespace eosio { fc_dlog(logger, "not forwarding from active syncing connection ${p}",("p",c->peer_name())); } } +#endif - void big_msg_manager::recv_transaction (connection_ptr c, const transaction_id_type& tid) { - pending_txn_source = c; + void dispatch_manager::recv_transaction (connection_ptr c, const transaction_id_type& id) { + received_transactions.emplace_back((transaction_origin){id, c}); if (c && c->last_req && c->last_req->req_trx.mode != none && - c->last_req->req_trx.ids.back() == tid) { + c->last_req->req_trx.ids.back() == id) { + c->last_req.reset(); + } + + fc_dlog(logger, "canceling wait on ${p}", ("p",c->peer_name())); + c->cancel_wait(); + } + + void dispatch_manager::recv_block (connection_ptr c, const block_id_type& id) { + received_blocks.emplace_back((block_origin){id, c}); + if (c && + c->last_req && + c->last_req->req_blocks.mode != none && + c->last_req->req_blocks.ids.back() == id) { c->last_req.reset(); } @@ -1727,12 +1805,12 @@ namespace eosio { c->cancel_wait(); } - void big_msg_manager::recv_notice (connection_ptr c, const notice_message& msg, bool generated) { + void dispatch_manager::recv_notice (connection_ptr c, const notice_message& msg, bool generated) { request_message req; req.req_trx.mode = none; req.req_blocks.mode = none; bool send_req = false; - chain_controller &cc = my_impl->chain_plug->chain(); + controller &cc = my_impl->chain_plug->chain(); if (msg.known_trx.mode == normal) { req.req_trx.mode = normal; req.req_trx.pending = 0; @@ -1746,7 +1824,7 @@ namespace eosio { time_point()} ); req.req_trx.ids.push_back( t ); - req_txn.push_back( t ); + req_trx.push_back( t ); } else { fc_dlog(logger,"big msg manager found txn id in table, ${id}",("id", t)); @@ -1762,7 +1840,7 @@ namespace eosio { if (msg.known_blocks.mode == normal) { req.req_blocks.mode = normal; for( const auto& blkid : msg.known_blocks.ids) { - optional b; + signed_block_ptr b; try { b = cc.fetch_block_by_id(blkid); } catch (const assert_exception &ex) { @@ -1790,7 +1868,7 @@ namespace eosio { } } - void big_msg_manager::retry_fetch( connection_ptr c ) { + void dispatch_manager::retry_fetch( connection_ptr c ) { if (!c->last_req) { return; } @@ -1947,9 +2025,15 @@ namespace eosio { if(!conn->socket) { return; } + connection_wptr weak_conn = conn; conn->socket->async_read_some (conn->pending_message_buffer.get_buffer_sequence_for_boost_async_read(), - [this,conn]( boost::system::error_code ec, std::size_t bytes_transferred ) { + [this,weak_conn]( boost::system::error_code ec, std::size_t bytes_transferred ) { + auto conn = weak_conn.lock(); + if (!conn) { + return; + } + try { if( !ec ) { if (bytes_transferred > conn->pending_message_buffer.bytes_to_write()) { @@ -2062,6 +2146,11 @@ namespace eosio { return valid; } + void net_plugin_impl::handle_message( connection_ptr c, const chain_size_message &msg) { + fc_dlog( logger, "got a chain_size_message from ${p}", ("p",c->peer_name())); + + } + void net_plugin_impl::handle_message( connection_ptr c, const handshake_message &msg) { fc_dlog( logger, "got a handshake_message from ${p} ${h}", ("p",c->peer_addr)("h",msg.p2p_address)); if (!is_valid(msg)) { @@ -2069,7 +2158,7 @@ namespace eosio { c->enqueue( go_away_message( fatal_other )); return; } - chain_controller& cc = chain_plug->chain(); + controller& cc = chain_plug->chain(); uint32_t lib_num = cc.last_irreversible_block_num( ); uint32_t peer_lib = msg.last_irreversible_block_num; if( c->connecting ) { @@ -2246,7 +2335,7 @@ namespace eosio { break; } case normal: { - big_msg_master->recv_notice (c, msg, false); + dispatcher->recv_notice (c, msg, false); } } @@ -2266,7 +2355,7 @@ namespace eosio { break; } case normal : { - big_msg_master->recv_notice (c, msg, false); + dispatcher->recv_notice (c, msg, false); break; } default: { @@ -2331,7 +2420,7 @@ namespace eosio { fc_dlog(logger, "got a duplicate transaction - dropping"); return; } - big_msg_master->recv_transaction(c, tid); + dispatcher->recv_transaction(c, tid); uint64_t code = 0; try { chain_plug->accept_transaction( msg); @@ -2346,118 +2435,18 @@ namespace eosio { elog( " caught something attempting to accept transaction"); } - big_msg_master->rejected_transaction(msg); - } - - void net_plugin_impl::handle_message( connection_ptr c, const signed_transaction &msg) { - fc_dlog(logger, "Got a signed transaction from ${p} cancel wait",("p",c->peer_name())); - c->cancel_wait(); - } - - void net_plugin_impl::handle_message( connection_ptr c, const signed_block_summary &msg) { - chain_controller &cc = chain_plug->chain(); - block_id_type blk_id = msg.id(); - uint32_t blk_num = msg.block_num(); - c->cancel_wait(); - - try { - if( cc.is_known_block(blk_id)) { - sync_master->recv_block(c, blk_id, blk_num, true); - return; - } - } catch( ...) { - // should this even be caught? - elog("Caught an unknown exception trying to recall blockID"); - } - - fc::microseconds age( fc::time_point::now() - msg.timestamp); - fc_dlog(logger, "got signed_block_summary #${n} from ${p} block age in secs = ${age}", - ("n",blk_num)("p",c->peer_name())("age",age.to_seconds())); - - signed_block sb(msg); - update_block_num ubn(blk_num); - for (const auto ®ion : sb.regions) { - for (const auto &cycle_sum : region.cycles_summary) { - for (const auto &shard : cycle_sum) { - for (const auto &recpt : shard.transactions) { - auto ltx = local_txns.get().find(recpt.id); - switch (recpt.status) { - case transaction_receipt::delayed: { - if (ltx == local_txns.end()) { - fc_dlog(logger, "got a delayed transaction, treat it like a notify"); - notice_message pending_notify; - pending_notify.known_blocks.mode = normal; - pending_notify.known_blocks.ids.push_back( blk_id ); - pending_notify.known_trx.mode = none; - big_msg_master->recv_notice (c, pending_notify, true); - return; - } - // else fall through to executed. - } - case transaction_receipt::executed: { - if( ltx != local_txns.end()) { - sb.input_transactions.push_back(ltx->packed_txn); - local_txns.modify( ltx, ubn ); - } - break; - } - case transaction_receipt::soft_fail: - case transaction_receipt::hard_fail: { - if( ltx != local_txns.end()) { - sb.input_transactions.push_back(ltx->packed_txn); - local_txns.modify( ltx, ubn ); - auto ctx = c->trx_state.get().find(recpt.id); - if( ctx != c->trx_state.end()) { - c->trx_state.modify( ctx, ubn ); - } - } - break; - } - } - } - } - } - } - - bool accepted = false; - try { - chain_plug->accept_block(sb, sync_master->is_active(c)); - accepted = true; - } catch( const unlinkable_block_exception &ex) { - elog( "unlinkable_block_exception accept block #${n} syncing from ${p}",("n",blk_num)("p",c->peer_name())); - } catch( const block_validate_exception &ex) { - elog( "block_validate_exception accept block #${n} syncing from ${p}",("n",blk_num)("p",c->peer_name())); - } catch( const assert_exception &ex) { - elog( "unable to accept block on assert exception ${n} from ${p}",("n",ex.to_string())("p",c->peer_name())); - } catch( const fc::exception &ex) { - elog( "accept_block threw a non-assert exception ${x} from ${p}",( "x",ex.to_string())("p",c->peer_name())); - } catch( ...) { - elog( "handle sync block caught something else from ${p}",("num",blk_num)("p",c->peer_name())); - } -#if 0 // after reviewing my conversaion with Bart, I believe this to be improper. - // as he explained, if the the block is deemed invalid by the local chain, then this implies a - // fork by the sender. - notice_message pending_notify; - pending_notify.known_blocks.mode = normal; - pending_notify.known_blocks.ids.push_back( blk_id ); - pending_notify.known_trx.mode = none; - big_msg_master->recv_notice (c, pending_notify, true); -#endif - big_msg_master->recv_block(c, msg); - sync_master->recv_block(c, blk_id, blk_num, accepted); - + dispatcher->rejected_transaction(tid); } void net_plugin_impl::handle_message( connection_ptr c, const signed_block &msg) { - // should only be during synch or rolling upgrade - chain_controller &cc = chain_plug->chain(); + controller &cc = chain_plug->chain(); block_id_type blk_id = msg.id(); uint32_t blk_num = msg.block_num(); fc_dlog(logger, "canceling wait on ${p}", ("p",c->peer_name())); c->cancel_wait(); try { - if( cc.is_known_block(blk_id)) { + if( cc.fetch_block_by_id(blk_id)) { sync_master->recv_block(c, blk_id, blk_num, true); return; } @@ -2466,13 +2455,15 @@ namespace eosio { elog("Caught an unknown exception trying to recall blockID"); } + dispatcher->recv_block(c, blk_id); fc::microseconds age( fc::time_point::now() - msg.timestamp); fc_dlog(logger, "got signed_block #${n} from ${p} block age in secs = ${age}", ("n",blk_num)("p",c->peer_name())("age",age.to_seconds())); go_away_reason reason = fatal_other; try { - chain_plug->accept_block(msg, sync_master->is_active(c)); + signed_block_ptr sbp = std::make_shared(msg); + chain_plug->accept_block(sbp); //, sync_master->is_active(c)); reason = no_reason; } catch( const unlinkable_block_exception &ex) { elog( "unlinkable_block_exception accept block #${n} syncing from ${p}",("n",blk_num)("p",c->peer_name())); @@ -2491,20 +2482,15 @@ namespace eosio { update_block_num ubn(blk_num); if( reason == no_reason ) { - for (const auto ®ion : msg.regions) { - for (const auto &cycle_sum : region.cycles_summary) { - for (const auto &shard : cycle_sum) { - for (const auto &recpt : shard.transactions) { - auto ltx = local_txns.get().find(recpt.id); - if( ltx != local_txns.end()) { - local_txns.modify( ltx, ubn ); - } - auto ctx = c->trx_state.get().find(recpt.id); - if( ctx != c->trx_state.end()) { - c->trx_state.modify( ctx, ubn ); - } - } - } + for (const auto &recpt : msg.transactions) { + auto id = (recpt.trx.which() == 0) ? recpt.trx.get() : recpt.trx.get().id(); + auto ltx = local_txns.get().find(id); + if( ltx != local_txns.end()) { + local_txns.modify( ltx, ubn ); + } + auto ctx = c->trx_state.get().find(id); + if( ctx != c->trx_state.end()) { + c->trx_state.modify( ctx, ubn ); } } } @@ -2567,7 +2553,7 @@ namespace eosio { old.erase( ex_lo, ex_up); auto &stale = local_txns.get(); - chain_controller &cc = chain_plug->chain(); + controller &cc = chain_plug->chain(); uint32_t bn = cc.last_irreversible_block_num(); stale.erase( stale.lower_bound(1), stale.upper_bound(bn) ); for ( auto &c : connections ) { @@ -2616,20 +2602,40 @@ namespace eosio { c->close(); } - /** - * This one is necessary to hook into the boost notifier api - **/ - void net_plugin_impl::transaction_ready(const transaction_metadata& md, const packed_transaction& txn) { - fc_dlog(logger,"transaction ready called, id = ${id}",("id",md.id)); - time_point_sec expire; - if (md.decompressed_trx) { - expire = md.decompressed_trx->expiration; - } - my_impl->big_msg_master->bcast_transaction(md.id, expire, txn); + void net_plugin_impl::accepted_block_header(const block_state_ptr& block) { + fc_ilog(logger,"signaled, id = ${id}",("id", block->id)); + } + + void net_plugin_impl::accepted_block(const block_state_ptr& block) { + fc_ilog(logger,"signaled, id = ${id}",("id", block->id)); + dispatcher->bcast_block(*block->block); } - void net_plugin_impl::broadcast_block_impl( const chain::signed_block &sb) { - big_msg_master->bcast_block(sb); + void net_plugin_impl::irreversible_block(const block_state_ptr&block) { + fc_ilog(logger,"signaled, id = ${id}",("id", block->id)); + } + + void net_plugin_impl::accepted_transaction(const transaction_metadata_ptr& md) { + fc_ilog(logger,"signaled, id = ${id}",("id", md->id)); +// dispatcher->bcast_transaction(md->packed_trx); + } + + void net_plugin_impl::applied_transaction(const transaction_trace_ptr& txn) { + fc_ilog(logger,"signaled, id = ${id}",("id", txn->id)); + } + + void net_plugin_impl::accepted_confirmation(const header_confirmation& head) { + fc_ilog(logger,"signaled, id = ${id}",("id", head.block_id)); + } + + void net_plugin_impl::transaction_ack(const std::pair& results) { + transaction_id_type id = results.second->id(); + if (results.first) { + fc_ilog(logger,"signaled NACK, trx-id = ${id} : ${why}",("id", id)("why", results.first->to_detail_string())); + } else { + fc_ilog(logger,"signaled ACK, trx-id = ${id}",("id", id)); + dispatcher->bcast_transaction(*results.second); + } } bool net_plugin_impl::authenticate_peer(const handshake_message& msg) const { @@ -2736,7 +2742,7 @@ namespace eosio { hello.agent = my_impl->user_agent_name; - chain_controller& cc = my_impl->chain_plug->chain(); + controller& cc = my_impl->chain_plug->chain(); hello.head_id = fc::sha256(); hello.last_irreversible_block_id = fc::sha256(); hello.head_num = cc.head_block_num(); @@ -2755,7 +2761,7 @@ namespace eosio { hello.head_id = cc.get_block_id_for_num( hello.head_num ); } catch( const unknown_block_exception &ex) { - hello.head_num = 0; + hello.head_num = 0; } } } @@ -2799,12 +2805,12 @@ namespace eosio { my->network_version_match = options.at("network-version-match").as(); my->sync_master.reset( new sync_manager(options.at("sync-fetch-span").as() ) ); - my->big_msg_master.reset( new big_msg_manager ); + my->dispatcher.reset( new dispatch_manager ); my->connector_period = std::chrono::seconds(options.at("connection-cleanup-period").as()); my->txn_exp_period = def_txn_expire_wait; my->resp_expected_period = def_resp_expected_wait; - my->big_msg_master->just_send_it_max = options.at("max-implicit-request").as(); + my->dispatcher->just_send_it_max = options.at("max-implicit-request").as(); my->max_client_count = options.at("max-clients").as(); my->num_clients = 0; @@ -2902,8 +2908,18 @@ namespace eosio { ilog("starting listener, max clients is ${mc}",("mc",my->max_client_count)); my->start_listen_loop(); } + { + chain::controller&cc = my->chain_plug->chain(); + cc.accepted_block_header.connect( boost::bind(&net_plugin_impl::accepted_block_header, my.get(), _1)); + cc.accepted_block.connect( boost::bind(&net_plugin_impl::accepted_block, my.get(), _1)); + cc.irreversible_block.connect( boost::bind(&net_plugin_impl::irreversible_block, my.get(), _1)); + cc.accepted_transaction.connect( boost::bind(&net_plugin_impl::accepted_transaction, my.get(), _1)); + cc.applied_transaction.connect( boost::bind(&net_plugin_impl::applied_transaction, my.get(), _1)); + cc.accepted_confirmation.connect( boost::bind(&net_plugin_impl::accepted_confirmation, my.get(), _1)); + } + + my->incoming_transaction_ack_subscription = app().get_channel().subscribe(boost::bind(&net_plugin_impl::transaction_ack, my.get(), _1)); - my->chain_plug->chain().on_pending_transaction.connect( &net_plugin_impl::transaction_ready); my->start_monitors(); for( auto seed_node : my->supplied_peers ) { @@ -2935,11 +2951,6 @@ namespace eosio { FC_CAPTURE_AND_RETHROW() } - void net_plugin::broadcast_block( const chain::signed_block &sb) { - fc_dlog(logger, "broadcasting block #${num}",("num",sb.block_num()) ); - my->broadcast_block_impl( sb); - } - size_t net_plugin::num_peers() const { return my->count_open_sockets(); } diff --git a/plugins/producer_plugin/CMakeLists.txt b/plugins/producer_plugin/CMakeLists.txt index 66bd29d0cd9..c9fb7ec01e3 100644 --- a/plugins/producer_plugin/CMakeLists.txt +++ b/plugins/producer_plugin/CMakeLists.txt @@ -5,6 +5,6 @@ add_library( producer_plugin ${HEADERS} ) -target_link_libraries( producer_plugin chain_plugin appbase eosio_chain eos_utilities net_plugin ) +target_link_libraries( producer_plugin chain_plugin appbase eosio_chain eos_utilities ) target_include_directories( producer_plugin - PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" ) diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index 68716ed1472..7c3d52728d9 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -21,10 +21,13 @@ namespace block_production_condition { no_private_key = 4, low_participation = 5, lag = 6, - exception_producing_block = 7 + exception_producing_block = 7, + fork_below_watermark = 8, }; } +using boost::signals2::signal; + class producer_plugin : public appbase::plugin { public: APPBASE_PLUGIN_REQUIRES((chain_plugin)) @@ -37,14 +40,14 @@ class producer_plugin : public appbase::plugin { boost::program_options::options_description &config_file_options ) override; - chain::public_key_type first_producer_public_key() const; - bool is_producer_key(const chain::public_key_type& key) const; - chain::signature_type sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const; + bool is_producer_key(const chain::public_key_type& key) const; + chain::signature_type sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const; virtual void plugin_initialize(const boost::program_options::variables_map& options); virtual void plugin_startup(); virtual void plugin_shutdown(); + signal confirmed_block; private: std::unique_ptr my; }; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 2e4ad4859ae..63794793977 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -3,17 +3,34 @@ * @copyright defined in eos/LICENSE.txt */ #include -#include #include +#include +#include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include + +namespace bmi = boost::multi_index; +using bmi::indexed_by; +using bmi::ordered_non_unique; +using bmi::member; +using bmi::tag; +using bmi::hashed_unique; + +using boost::multi_index_container; using std::string; using std::vector; @@ -23,30 +40,203 @@ namespace eosio { static appbase::abstract_plugin& _producer_plugin = app().register_plugin(); using namespace eosio::chain; +using namespace eosio::chain::plugin_interface; + +namespace { + bool failure_is_subjective(const fc::exception& e, bool deadline_is_subjective) { + auto code = e.code(); + return (code == block_cpu_usage_exceeded::code_value) || + (code == block_net_usage_exceeded::code_value) || + (code == deadline_exception::code_value && deadline_is_subjective) || + (code == leeway_deadline_exception::code_value && deadline_is_subjective); + } +} + +struct blacklisted_transaction { + transaction_id_type trx_id; + fc::time_point expiry; +}; + +struct by_id; +struct by_expiry; + +using blacklisted_transaction_index = multi_index_container< + blacklisted_transaction, + indexed_by< + hashed_unique, BOOST_MULTI_INDEX_MEMBER(blacklisted_transaction, transaction_id_type, trx_id)>, + ordered_non_unique, BOOST_MULTI_INDEX_MEMBER(blacklisted_transaction, fc::time_point, expiry)> + > +>; class producer_plugin_impl { -public: - producer_plugin_impl(boost::asio::io_service& io) - : _timer(io) {} + public: + producer_plugin_impl(boost::asio::io_service& io) + :_timer(io) + ,_transaction_ack_channel(app().get_channel()) + {} + + void schedule_production_loop(); + block_production_condition::block_production_condition_enum block_production_loop(); + block_production_condition::block_production_condition_enum maybe_produce_block(fc::mutable_variant_object& capture); + + boost::program_options::variables_map _options; + bool _production_enabled = false; + uint32_t _required_producer_participation = uint32_t(config::required_producer_participation); + uint32_t _production_skip_flags = 0; //eosio::chain::skip_nothing; + + std::map _private_keys; + std::set _producers; + boost::asio::deadline_timer _timer; + std::map _producer_watermarks; + + int32_t _max_transaction_time_ms; + + block_production_condition::block_production_condition_enum _prev_result = block_production_condition::produced; + uint32_t _prev_result_count = 0; + + time_point _last_signed_block_time; + time_point _start_time = fc::time_point::now(); + uint32_t _last_signed_block_num = 0; - void schedule_production_loop(); - block_production_condition::block_production_condition_enum block_production_loop(); - block_production_condition::block_production_condition_enum maybe_produce_block(fc::mutable_variant_object& capture); + producer_plugin* _self = nullptr; - boost::program_options::variables_map _options; - bool _production_enabled = false; - uint32_t _required_producer_participation = uint32_t(config::required_producer_participation); - uint32_t _production_skip_flags = eosio::chain::skip_nothing; + incoming::channels::block::channel_type::handle _incoming_block_subscription; + incoming::channels::transaction::channel_type::handle _incoming_transaction_subscription; - std::map _private_keys; - std::set _producers; - boost::asio::deadline_timer _timer; + compat::channels::transaction_ack::channel_type& _transaction_ack_channel; + + incoming::methods::block_sync::method_type::handle _incoming_block_sync_provider; + incoming::methods::transaction_sync::method_type::handle _incoming_transaction_sync_provider; + + blacklisted_transaction_index _blacklisted_transactions; + + void on_block( const block_state_ptr& bsp ) { + if( bsp->header.timestamp <= _last_signed_block_time ) return; + if( bsp->header.timestamp <= _start_time ) return; + if( bsp->block_num <= _last_signed_block_num ) return; + + const auto& active_producer_to_signing_key = bsp->active_schedule.producers; + + flat_set active_producers; + active_producers.reserve(bsp->active_schedule.producers.size()); + for (const auto& p: bsp->active_schedule.producers) { + active_producers.insert(p.producer_name); + } + + std::set_intersection( _producers.begin(), _producers.end(), + active_producers.begin(), active_producers.end(), + boost::make_function_output_iterator( [&]( const chain::account_name& producer ) + { + if( producer != bsp->header.producer ) { + auto itr = std::find_if( active_producer_to_signing_key.begin(), active_producer_to_signing_key.end(), + [&](const producer_key& k){ return k.producer_name == producer; } ); + if( itr != active_producer_to_signing_key.end() ) { + auto private_key_itr = _private_keys.find( itr->block_signing_key ); + if( private_key_itr != _private_keys.end() ) { + auto d = bsp->sig_digest(); + auto sig = private_key_itr->second.sign( d ); + _last_signed_block_time = bsp->header.timestamp; + _last_signed_block_num = bsp->block_num; + + // ilog( "${n} confirmed", ("n",name(producer)) ); + _self->confirmed_block( { bsp->id, d, producer, sig } ); + } + } + } + } ) ); + } + + template + auto publish_results_of(const Type &data, Channel& channel, F f) { + auto publish_success = fc::make_scoped_exit([&, this](){ + channel.publish(std::pair(nullptr, data)); + }); + + try { + return f(); + } catch (const fc::exception& e) { + publish_success.cancel(); + channel.publish(std::pair(e.dynamic_copy_exception(), data)); + throw e; + } catch( const std::exception& e ) { + publish_success.cancel(); + auto fce = fc::exception( + FC_LOG_MESSAGE( info, "Caught std::exception: ${what}", ("what",e.what())), + fc::std_exception_code, + BOOST_CORE_TYPEID(e).name(), + e.what() + ); + channel.publish(std::pair(fce.dynamic_copy_exception(),data)); + throw fce; + } catch( ... ) { + publish_success.cancel(); + auto fce = fc::unhandled_exception( + FC_LOG_MESSAGE( info, "Caught unknown exception"), + std::current_exception() + ); + + channel.publish(std::pair(fce.dynamic_copy_exception(), data)); + throw fce; + } + }; - block_production_condition::block_production_condition_enum _prev_result = block_production_condition::produced; - uint32_t _prev_result_count = 0; + void on_incoming_block(const signed_block_ptr& block) { + chain::controller& chain = app().get_plugin().chain(); + // abort the pending block + chain.abort_block(); + + // exceptions throw out, make sure we restart our loop + auto ensure = fc::make_scoped_exit([this](){ + // restart our production loop + schedule_production_loop(); + }); + + // push the new block + chain.push_block(block); + + if( chain.head_block_state()->header.timestamp.next().to_time_point() >= fc::time_point::now() ) + _production_enabled = true; + + ilog("Received block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]", + ("p",block->producer)("id",fc::variant(block->id()).as_string().substr(0,16)) + ("n",block_header::num_from_id(block->id()))("t",block->timestamp) + ("count",block->transactions.size())("lib",chain.last_irreversible_block_num())("confs", block->confirmed) ); + + } + + transaction_trace_ptr on_incoming_transaction(const packed_transaction_ptr& trx) { + return publish_results_of(trx, _transaction_ack_channel, [&]() -> transaction_trace_ptr { + while (true) { + chain::controller& chain = app().get_plugin().chain(); + auto block_time = chain.pending_block_state()->header.timestamp.to_time_point(); + auto max_deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms); + auto deadline = std::min(block_time, max_deadline); + auto trace = chain.push_transaction(std::make_shared(*trx), deadline); + + // if we failed because the block was exhausted push the block out and try again + if (trace->except) { + if (failure_is_subjective(*trace->except, deadline == block_time)) { + block_production_loop(); + } else { + trace->except->dynamic_rethrow_exception(); + } + } else { + return trace; + } + } + }); + } + + enum class start_block_result { + succeeded, + failed, + exhausted + }; + + start_block_result start_block(); }; -void new_chain_banner(const eosio::chain::chain_controller& db) +void new_chain_banner(const eosio::chain::controller& db) { std::cerr << "\n" "*******************************\n" @@ -57,7 +247,8 @@ void new_chain_banner(const eosio::chain::chain_controller& db) "* *\n" "*******************************\n" "\n"; - if(db.get_slot_at_time(fc::time_point::now()) > 200) + + if( db.head_block_state()->header.timestamp.to_time_point() < (fc::time_point::now() - fc::milliseconds(200 * config::block_interval_ms))) { std::cerr << "Your genesis seems to have an old timestamp\n" "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n" @@ -68,7 +259,9 @@ void new_chain_banner(const eosio::chain::chain_controller& db) } producer_plugin::producer_plugin() - : my(new producer_plugin_impl(app().get_io_service())){} + : my(new producer_plugin_impl(app().get_io_service())){ + my->_self = this; + } producer_plugin::~producer_plugin() {} @@ -83,6 +276,8 @@ void producer_plugin::set_program_options( producer_options.add_options() ("enable-stale-production,e", boost::program_options::bool_switch()->notifier([this](bool e){my->_production_enabled = e;}), "Enable block production, even if the chain is stale.") + ("max-transaction-time", bpo::value()->default_value(30), + "Limits the maximum time (in milliseconds) that is allowed a pushed transaction's code to execute before being considered invalid") ("required-participation", boost::program_options::value() ->default_value(uint32_t(config::required_producer_participation/config::percent_1)) ->notifier([this](uint32_t e) { @@ -98,16 +293,6 @@ void producer_plugin::set_program_options( config_file_options.add(producer_options); } -chain::public_key_type producer_plugin::first_producer_public_key() const -{ - chain::chain_controller& chain = app().get_plugin().chain(); - try { - return chain.get_producer(*my->_producers.begin()).signing_key; - } catch(std::out_of_range) { - return chain::public_key_type(); - } -} - bool producer_plugin::is_producer_key(const chain::public_key_type& key) const { auto private_key_itr = my->_private_keys.find(key); @@ -154,12 +339,38 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ my->_private_keys[key_id_to_wif_pair.first] = key_id_to_wif_pair.second; } } + + my->_max_transaction_time_ms = options.at("max-transaction-time").as(); + + + my->_incoming_block_subscription = app().get_channel().subscribe([this](const signed_block_ptr& block){ + try { + my->on_incoming_block(block); + } FC_LOG_AND_DROP(); + }); + + my->_incoming_transaction_subscription = app().get_channel().subscribe([this](const packed_transaction_ptr& trx){ + try { + my->on_incoming_transaction(trx); + } FC_LOG_AND_DROP(); + }); + + my->_incoming_block_sync_provider = app().get_method().register_provider([this](const signed_block_ptr& block){ + my->on_incoming_block(block); + }); + + my->_incoming_transaction_sync_provider = app().get_method().register_provider([this](const packed_transaction_ptr& trx) -> transaction_trace_ptr { + return my->on_incoming_transaction(trx); + }); + } FC_LOG_AND_RETHROW() } void producer_plugin::plugin_startup() { try { ilog("producer plugin: plugin_startup() begin"); - chain::chain_controller& chain = app().get_plugin().chain(); + + chain::controller& chain = app().get_plugin().chain(); + chain.accepted_block.connect( [this]( const auto& bsp ){ my->on_block( bsp ); } ); if (!my->_producers.empty()) { @@ -168,13 +379,14 @@ void producer_plugin::plugin_startup() { if(chain.head_block_num() == 0) new_chain_banner(chain); - my->_production_skip_flags |= eosio::chain::skip_undo_history_check; + //_production_skip_flags |= eosio::chain::skip_undo_history_check; } my->schedule_production_loop(); } else elog("No producers configured! Please add producer IDs and private keys to configuration."); + ilog("producer plugin: plugin_startup() end"); - } FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } void producer_plugin::plugin_shutdown() { try { @@ -184,21 +396,136 @@ void producer_plugin::plugin_shutdown() { } } -void producer_plugin_impl::schedule_production_loop() { +producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { + chain::controller& chain = app().get_plugin().chain(); + const auto& hbs = chain.head_block_state(); + //Schedule for the next second's tick regardless of chain state // If we would wait less than 50ms (1/10 of block_interval), wait for the whole block interval. fc::time_point now = fc::time_point::now(); - int64_t time_to_next_block_time = (config::block_interval_us) - (now.time_since_epoch().count() % (config::block_interval_us) ); - if(time_to_next_block_time < config::block_interval_us/10 ) { // we must sleep for at least 50ms - ilog("Less than ${t}us to next block time, time_to_next_block_time ${bt}us", - ("t", config::block_interval_us/10)("bt", time_to_next_block_time)); - time_to_next_block_time += config::block_interval_us; + fc::time_point base = std::max(now, chain.head_block_time()); + int64_t min_time_to_next_block = (config::block_interval_us) - (base.time_since_epoch().count() % (config::block_interval_us) ); + fc::time_point block_time = base + fc::microseconds(min_time_to_next_block); + + + if((block_time - now) < fc::microseconds(config::block_interval_us/10) ) { // we must sleep for at least 50ms + ilog("Less than ${t}us to next block time, time_to_next_block_time ${bt}", + ("t", config::block_interval_us/10)("bt", block_time)); + block_time += fc::microseconds(config::block_interval_us); } - //_timer.expires_from_now(boost::posix_time::microseconds(time_to_next_block_time)); - _timer.expires_from_now( boost::posix_time::microseconds(time_to_next_block_time) ); - //_timer.async_wait(boost::bind(&producer_plugin_impl::block_production_loop, this)); - _timer.async_wait( [&](const boost::system::error_code&){ block_production_loop(); } ); + static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); + _timer.expires_at( epoch + boost::posix_time::microseconds(block_time.time_since_epoch().count())); + + try { + // determine how many blocks this producer can confirm + // 1) if it is not a producer from this node, assume no confirmations (we will discard this block anyway) + // 2) if it is a producer on this node that has never produced, the conservative approach is to assume no + // confirmations to make sure we don't double sign after a crash TODO: make these watermarks durable? + // 3) if it is a producer on this node where this node knows the last block it produced, safely set it -UNLESS- + // 4) the producer on this node's last watermark is higher (meaning on a different fork) + const auto& hbs = chain.head_block_state(); + auto producer_name = hbs->get_scheduled_producer(block_time).producer_name; + uint16_t blocks_to_confirm = 0; + auto itr = _producer_watermarks.find(producer_name); + if (itr != _producer_watermarks.end()) { + auto watermark = itr->second; + if (watermark < hbs->block_num) { + blocks_to_confirm = std::min(std::numeric_limits::max(), (uint16_t)(hbs->block_num - watermark)); + } + } + + chain.abort_block(); + chain.start_block(block_time, blocks_to_confirm); + } FC_LOG_AND_DROP(); + + if (chain.pending_block_state()) { + bool exhausted = false; + auto unapplied_trxs = chain.get_unapplied_transactions(); + for (const auto& trx : unapplied_trxs) { + if (exhausted) { + break; + } + + try { + auto deadline = std::min(block_time, fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms)); + auto trace = chain.push_transaction(trx, deadline); + if (trace->except) { + if (failure_is_subjective(*trace->except, deadline == block_time)) { + exhausted = true; + } else { + // this failed our configured maximum transaction time, we don't want to replay it + chain.drop_unapplied_transaction(trx); + } + } + } FC_LOG_AND_DROP(); + } + + auto& blacklist_by_id = _blacklisted_transactions.get(); + auto& blacklist_by_expiry = _blacklisted_transactions.get(); + auto now = fc::time_point::now(); + while(!blacklist_by_expiry.empty() && blacklist_by_expiry.begin()->expiry <= now) { + blacklist_by_expiry.erase(blacklist_by_expiry.begin()); + } + + auto scheduled_trxs = chain.get_scheduled_transactions(); + for (const auto& trx : scheduled_trxs) { + if (exhausted) { + break; + } + + if (blacklist_by_id.find(trx) != blacklist_by_id.end()) { + continue; + } + + try { + auto deadline = std::min(block_time, fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms)); + auto trace = chain.push_scheduled_transaction(trx, deadline); + if (trace->except) { + if (failure_is_subjective(*trace->except, deadline == block_time)) { + exhausted = true; + } else { + auto expiration = fc::time_point::now() + fc::seconds(chain.get_global_properties().configuration.deferred_trx_expiration_window); + // this failed our configured maximum transaction time, we don't want to replay it add it to a blacklist + _blacklisted_transactions.insert(blacklisted_transaction{trx, expiration}); + } + } + } FC_LOG_AND_DROP(); + } + + return exhausted ? start_block_result::exhausted : start_block_result::succeeded; + } + + return start_block_result::failed; +} + +void producer_plugin_impl::schedule_production_loop() { + _timer.cancel(); + + auto result = start_block(); + switch(result) { + case start_block_result::exhausted: + // immediately proceed to making the block + block_production_loop(); + break; + case start_block_result::failed: + elog("Failed to start a pending block, will try again later"); + // we failed to start a block, so try again later? + _timer.async_wait([&](const boost::system::error_code& ec) { + if (ec != boost::asio::error::operation_aborted) { + schedule_production_loop(); + } + }); + break; + case start_block_result::succeeded: + //_timer.async_wait(boost::bind(&producer_plugin_impl::block_production_loop, this)); + _timer.async_wait([&](const boost::system::error_code& ec) { + if (ec != boost::asio::error::operation_aborted) { + block_production_loop(); + } + }); + break; + } } block_production_condition::block_production_condition_enum producer_plugin_impl::block_production_loop() { @@ -234,9 +561,12 @@ block_production_condition::block_production_condition_enum producer_plugin_impl case block_production_condition::produced: { const auto& db = app().get_plugin().chain(); auto producer = db.head_block_producer(); - // auto pending = db.pending().size(); + auto pending = db.head_block_state(); - wlog("${p} generated block ${id}... #${n} @ ${t} with ${count} trxs, lib: ${lib}", ("p", producer)("lib",db.last_irreversible_block_num())(capture) ); + ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]", + ("p",producer)("id",fc::variant(pending->id).as_string().substr(0,16)) + ("n",pending->block_num)("t",pending->block->timestamp) + ("count",pending->block->transactions.size())("lib",db.last_irreversible_block_num())("confs", pending->header.confirmed)(capture) ); break; } case block_production_condition::not_synced: @@ -260,6 +590,9 @@ block_production_condition::block_production_condition_enum producer_plugin_impl case block_production_condition::exception_producing_block: elog( "exception producing block" ); break; + case block_production_condition::fork_below_watermark: + elog( "Not producing block because \"${producer}\" signed a BFT confirmation OR block at a higher block number (${watermark}) than the current fork's head (${head_block_num})" ); + break; } } schedule_production_loop(); @@ -267,28 +600,22 @@ block_production_condition::block_production_condition_enum producer_plugin_impl } block_production_condition::block_production_condition_enum producer_plugin_impl::maybe_produce_block(fc::mutable_variant_object& capture) { - chain::chain_controller& chain = app().get_plugin().chain(); + chain::controller& chain = app().get_plugin().chain(); + block_state_ptr hbs = chain.head_block_state(); fc::time_point now = fc::time_point::now(); + /* if (app().get_plugin().is_skipping_transaction_signatures()) { _production_skip_flags |= skip_transaction_signatures; } + */ // If the next block production opportunity is in the present or future, we're synced. if( !_production_enabled ) { - if( chain.get_slot_time(1) >= now ) - _production_enabled = true; - else - return block_production_condition::not_synced; + return block_production_condition::not_synced; } - // is anyone scheduled to produce now or one second in the future? - uint32_t slot = chain.get_slot_at_time( now ); - if( slot == 0 ) - { - capture("next_time", chain.get_slot_time(1)); - return block_production_condition::not_time_yet; - } + auto pending_block_timestamp = chain.pending_block_state()->header.timestamp; // // this assert should not fail, because now <= db.head_block_time() @@ -300,48 +627,70 @@ block_production_condition::block_production_condition_enum producer_plugin_impl // assert( now > chain.head_block_time() ); - auto scheduled_producer = chain.get_scheduled_producer( slot ); + const auto& scheduled_producer = hbs->get_scheduled_producer( pending_block_timestamp ); // we must control the producer scheduled to produce the next block. - if( _producers.find( scheduled_producer ) == _producers.end() ) + if( _producers.find( scheduled_producer.producer_name ) == _producers.end() ) { - capture("scheduled_producer", scheduled_producer); + capture("scheduled_producer", scheduled_producer.producer_name); return block_production_condition::not_my_turn; } - auto scheduled_time = chain.get_slot_time( slot ); - eosio::chain::public_key_type scheduled_key = chain.get_producer(scheduled_producer).signing_key; - auto private_key_itr = _private_keys.find( scheduled_key ); + auto private_key_itr = _private_keys.find( scheduled_producer.block_signing_key ); if( private_key_itr == _private_keys.end() ) { - capture("scheduled_key", scheduled_key); + capture("scheduled_key", scheduled_producer.block_signing_key); return block_production_condition::no_private_key; } - uint32_t prate = chain.producer_participation_rate(); + /*uint32_t prate = hbs->producer_participation_rate(); if( prate < _required_producer_participation ) { capture("pct", uint32_t(prate / config::percent_1)); return block_production_condition::low_participation; - } + }*/ - if( llabs(( time_point(scheduled_time) - now).count()) > fc::milliseconds( config::block_interval_ms ).count() ) + if( llabs(( pending_block_timestamp.to_time_point() - now).count()) > fc::milliseconds( config::block_interval_ms ).count() ) { - capture("scheduled_time", scheduled_time)("now", now); + capture("scheduled_time", pending_block_timestamp)("now", now); return block_production_condition::lag; } + // determine if our watermark excludes us from producing at this point + auto itr = _producer_watermarks.find(scheduled_producer.producer_name); + if (itr != _producer_watermarks.end()) { + if (itr->second >= hbs->block_num + 1) { + capture("producer", scheduled_producer.producer_name)("watermark",itr->second)("head_block_num",hbs->block_num); + return block_production_condition::fork_below_watermark; + } + } - auto block = chain.generate_block( - scheduled_time, - scheduled_producer, - private_key_itr->second, - _production_skip_flags - ); + //idump( (fc::time_point::now() - chain.pending_block_time()) ); + chain.finalize_block(); + chain.sign_block( [&]( const digest_type& d ) { return private_key_itr->second.sign(d); } ); + chain.commit_block(); + auto hbt = chain.head_block_time(); + //idump((fc::time_point::now() - hbt)); + + block_state_ptr new_bs = chain.head_block_state(); + // for newly installed producers we can set their watermarks to the block they became + if (hbs->active_schedule.version != new_bs->active_schedule.version) { + flat_set new_producers; + new_producers.reserve(new_bs->active_schedule.producers.size()); + for( const auto& p: new_bs->active_schedule.producers) { + if (_producers.count(p.producer_name) > 0) + new_producers.insert(p.producer_name); + } - capture("n", block.block_num())("t", block.timestamp)("c", now)("count",block.input_transactions.size())("id",string(block.id()).substr(8,8)); + for( const auto& p: hbs->active_schedule.producers) { + new_producers.erase(p.producer_name); + } - app().get_plugin().broadcast_block(block); + for (const auto& new_producer: new_producers) { + _producer_watermarks[new_producer] = chain.head_block_num(); + } + } + _producer_watermarks[scheduled_producer.producer_name] = chain.head_block_num(); return block_production_condition::produced; } diff --git a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp index ec78d35a6d0..8e7205d2fde 100644 --- a/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp +++ b/plugins/txn_test_gen_plugin/txn_test_gen_plugin.cpp @@ -71,15 +71,20 @@ using namespace eosio::chain; eosio::detail::txn_test_gen_empty result; struct txn_test_gen_plugin_impl { + void push_transaction( signed_transaction& trx ) { try { + chain_plugin& cp = app().get_plugin(); + return cp.accept_transaction( packed_transaction(trx) ); + } FC_CAPTURE_AND_RETHROW( (transaction_header(trx)) ) } + void create_test_accounts(const std::string& init_name, const std::string& init_priv_key) { name newaccountA("txn.test.a"); name newaccountB("txn.test.b"); name newaccountC("eosio.token"); name creator(init_name); - contracts::abi_def eosio_token_abi_def = fc::json::from_string(eosio_token_abi).as(); + abi_def currency_abi_def = fc::json::from_string(eosio_token_abi).as(); - chain_controller& cc = app().get_plugin().chain(); + controller& cc = app().get_plugin().chain(); chain::chain_id_type chainid; app().get_plugin().get_chain_id(chainid); @@ -100,31 +105,28 @@ struct txn_test_gen_plugin_impl { { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; - auto recovery_auth = eosio::chain::authority{1, {}, {{{creator, "active"}, 1}}}; - trx.actions.emplace_back(vector{{creator,"active"}}, contracts::newaccount{creator, newaccountA, owner_auth, active_auth, recovery_auth}); + trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountA, owner_auth, active_auth}); } //create "B" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; - auto recovery_auth = eosio::chain::authority{1, {}, {{{creator, "active"}, 1}}}; - trx.actions.emplace_back(vector{{creator,"active"}}, contracts::newaccount{creator, newaccountB, owner_auth, active_auth, recovery_auth}); + trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountB, owner_auth, active_auth}); } //create "eosio.token" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; - auto recovery_auth = eosio::chain::authority{1, {}, {{{creator, "active"}, 1}}}; - trx.actions.emplace_back(vector{{creator,"active"}}, contracts::newaccount{creator, newaccountC, owner_auth, active_auth, recovery_auth}); + trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountC, owner_auth, active_auth}); } trx.expiration = cc.head_block_time() + fc::seconds(30); trx.set_reference_block(cc.head_block_id()); trx.sign(creator_priv_key, chainid); - cc.push_transaction(packed_transaction(trx)); + push_transaction(trx); } //set eosio.token contract & initialize it @@ -133,21 +135,19 @@ struct txn_test_gen_plugin_impl { vector wasm = wast_to_wasm(std::string(eosio_token_wast)); - contracts::setcode handler; + setcode handler; handler.account = newaccountC; handler.code.assign(wasm.begin(), wasm.end()); trx.actions.emplace_back( vector{{newaccountC,"active"}}, handler); { - contracts::setabi handler; + setabi handler; handler.account = newaccountC; - handler.abi = eosio_token_abi_def; + handler.abi = json::from_string(eosio_token_abi).as(); trx.actions.emplace_back( vector{{newaccountC,"active"}}, handler); } - abi_serializer eosio_token_serializer(eosio_token_abi_def); - { action act; act.account = N(eosio.token); @@ -185,7 +185,7 @@ struct txn_test_gen_plugin_impl { trx.set_reference_block(cc.head_block_id()); trx.max_net_usage_words = 5000; trx.sign(txn_test_receiver_C_priv_key, chainid); - cc.push_transaction(packed_transaction(trx)); + push_transaction(trx); } } @@ -238,7 +238,7 @@ struct txn_test_gen_plugin_impl { } void send_transaction() { - chain_controller& cc = app().get_plugin().chain(); + controller& cc = app().get_plugin().chain(); chain::chain_id_type chainid; app().get_plugin().get_chain_id(chainid); @@ -248,7 +248,7 @@ struct txn_test_gen_plugin_impl { fc::crypto::private_key b_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'b'))); static uint64_t nonce = static_cast(fc::time_point::now().sec_since_epoch()) << 32; - abi_serializer eosio_serializer(cc.get_database().find(config::system_account_name)->get_abi()); + abi_serializer eosio_serializer(cc.db().find(config::system_account_name)->get_abi()); uint32_t reference_block_num = cc.last_irreversible_block_num(); if (txn_reference_block_lag >= 0) { @@ -264,29 +264,25 @@ struct txn_test_gen_plugin_impl { for(unsigned int i = 0; i < batch; ++i) { { - variant nonce_vo = fc::mutable_variant_object() - ("value", fc::to_string(nonce++)); signed_transaction trx; trx.actions.push_back(act_a_to_b); - trx.context_free_actions.emplace_back(action({}, config::system_account_name, "nonce", eosio_serializer.variant_to_binary("nonce", nonce_vo))); + trx.context_free_actions.emplace_back(action({}, config::null_account_name, "nonce", fc::raw::pack(nonce++))); trx.set_reference_block(reference_block_id); trx.expiration = cc.head_block_time() + fc::seconds(30); trx.max_net_usage_words = 100; trx.sign(a_priv_key, chainid); - cc.push_transaction(packed_transaction(trx)); + push_transaction(trx); } { - variant nonce_vo = fc::mutable_variant_object() - ("value", fc::to_string(nonce++)); signed_transaction trx; trx.actions.push_back(act_b_to_a); - trx.context_free_actions.emplace_back(action({}, config::system_account_name, "nonce", eosio_serializer.variant_to_binary("nonce", nonce_vo))); + trx.context_free_actions.emplace_back(action({}, config::null_account_name, "nonce", fc::raw::pack(nonce++))); trx.set_reference_block(reference_block_id); trx.expiration = cc.head_block_time() + fc::seconds(30); trx.max_net_usage_words = 100; trx.sign(b_priv_key, chainid); - cc.push_transaction(packed_transaction(trx)); + push_transaction(trx); } } } @@ -310,7 +306,7 @@ struct txn_test_gen_plugin_impl { int32_t txn_reference_block_lag; - abi_serializer eosio_token_serializer = fc::json::from_string(eosio_token_abi).as(); + abi_serializer eosio_token_serializer = fc::json::from_string(eosio_token_abi).as(); }; txn_test_gen_plugin::txn_test_gen_plugin() {} diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp index 44a94a8316b..6cb76607867 100644 --- a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp @@ -88,7 +88,7 @@ class wallet_manager { /// The wallet remains unlocked until ::lock is called or program exit. /// @param name the name of the wallet to lock. /// @param password the plaintext password returned from ::create. - /// @throws fc::exception if wallet not found or invalid password. + /// @throws fc::exception if wallet not found or invalid password or already unlocked. void unlock(const std::string& name, const std::string& password); /// Import private key into specified wallet. diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_plugin.hpp b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_plugin.hpp index 3dd8a68ca4a..1b0986fe8f9 100644 --- a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_plugin.hpp +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_plugin.hpp @@ -5,7 +5,7 @@ #pragma once #include #include -#include +#include #include namespace fc { class variant; } @@ -42,4 +42,3 @@ class wallet_plugin : public plugin { }; } - diff --git a/plugins/wallet_plugin/wallet_manager.cpp b/plugins/wallet_plugin/wallet_manager.cpp index 9a1f89d4d06..becd745ec7d 100644 --- a/plugins/wallet_plugin/wallet_manager.cpp +++ b/plugins/wallet_plugin/wallet_manager.cpp @@ -24,7 +24,7 @@ void wallet_manager::set_timeout(const std::chrono::seconds& t) { void wallet_manager::check_timeout() { if (timeout_time != timepoint_t::max()) { const auto& now = std::chrono::system_clock::now(); - if (now >= timeout_time + timeout) { + if (now >= timeout_time) { lock_all(); } timeout_time = now + timeout; @@ -154,6 +154,7 @@ void wallet_manager::unlock(const std::string& name, const std::string& password } auto& w = wallets.at(name); if (!w->is_locked()) { + EOS_THROW(chain::wallet_unlocked_exception, "Wallet is already unlocked: ${w}", ("w", name)); return; } w->unlock(password); diff --git a/plugins/wallet_plugin/wallet_plugin.cpp b/plugins/wallet_plugin/wallet_plugin.cpp index 2c18adb34dc..f5ac9615dee 100644 --- a/plugins/wallet_plugin/wallet_plugin.cpp +++ b/plugins/wallet_plugin/wallet_plugin.cpp @@ -28,10 +28,10 @@ void wallet_plugin::set_program_options(options_description& cli, options_descri cfg.add_options() ("wallet-dir", bpo::value()->default_value("."), "The path of the wallet files (absolute path or relative to application data dir)") - ("unlock-timeout", bpo::value(), - "Timeout for unlocked wallet in seconds. " - "Wallets will automatically lock after specified number of seconds of inactivity. " - "Activity is defined as any wallet command e.g. list-wallets.") + ("unlock-timeout", bpo::value()->default_value(900), + "Timeout for unlocked wallet in seconds (default 900 (15 minutes)). " + "Wallets will automatically lock after specified number of seconds of inactivity. " + "Activity is defined as any wallet command e.g. list-wallets.") ("eosio-key", bpo::value(), "eosio key that will be imported automatically when a wallet is created.") ; diff --git a/programs/cleos/eosc.pot b/programs/cleos/eosc.pot index b00396b1961..58949956b4c 100644 --- a/programs/cleos/eosc.pot +++ b/programs/cleos/eosc.pot @@ -3,7 +3,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Dawn 1.0\n" +"Project-Id-Version: Dawn 4.0\n" "Report-Msgid-Bugs-To: https://github.com/EOSIO/eos/issues" "POT-Creation-Date: 2017-09-28 11:40-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/programs/cleos/httpc.cpp b/programs/cleos/httpc.cpp index 70aa160aa7d..b3db33f05a8 100644 --- a/programs/cleos/httpc.cpp +++ b/programs/cleos/httpc.cpp @@ -77,39 +77,44 @@ namespace eosio { namespace client { namespace http { return re.str(); } + parsed_url parse_url( const string& server_url ) { + parsed_url res; + + //via rfc3986 and modified a bit to suck out the port number + //Sadly this doesn't work for ipv6 addresses + std::regex rgx(R"xx(^(([^:/?#]+):)?(//([^:/?#]*)(:(\d+))?)?([^?#]*)(\?([^#]*))?(#(.*))?)xx"); + std::smatch match; + if(std::regex_search(server_url.begin(), server_url.end(), match, rgx)) { + res.scheme = match[2]; + res.server = match[4]; + res.port = match[6]; + res.path_prefix = match[7]; + } + if(res.scheme != "http" && res.scheme != "https") + FC_THROW("Unrecognized URL scheme (${s}) in URL \"${u}\"", ("s", res.scheme)("u", server_url)); + if(res.server.empty()) + FC_THROW("No server parsed from URL \"${u}\"", ("u", server_url)); + if(res.port.empty()) + res.port = res.scheme == "http" ? "8888" : "443"; + boost::trim_right_if(res.path_prefix, boost::is_any_of("/")); + return res; + } + fc::variant do_http_call( const std::string& server_url, - const std::string& path, - const fc::variant& postdata ) { + const std::string& path, + const fc::variant& postdata ) { std::string postjson; if( !postdata.is_null() ) postjson = fc::json::to_string( postdata ); boost::asio::io_service io_service; - string scheme, server, port, path_prefix; - - //via rfc3986 and modified a bit to suck out the port number - //Sadly this doesn't work for ipv6 addresses - std::regex rgx(R"xx(^(([^:/?#]+):)?(//([^:/?#]*)(:(\d+))?)?([^?#]*)(\?([^#]*))?(#(.*))?)xx"); - std::smatch match; - if(std::regex_search(server_url.begin(), server_url.end(), match, rgx)) { - scheme = match[2]; - server = match[4]; - port = match[6]; - path_prefix = match[7]; - } - if(scheme != "http" && scheme != "https") - FC_THROW("Unrecognized URL scheme (${s}) in URL \"${u}\"", ("s", scheme)("u", server_url)); - if(server.empty()) - FC_THROW("No server parsed from URL \"${u}\"", ("u", server_url)); - if(port.empty()) - port = scheme == "http" ? "8888" : "443"; - boost::trim_right_if(path_prefix, boost::is_any_of("/")); + auto url = parse_url( server_url ); boost::asio::streambuf request; std::ostream request_stream(&request); - request_stream << "POST " << path_prefix + path << " HTTP/1.0\r\n"; - request_stream << "Host: " << server << "\r\n"; + request_stream << "POST " << url.path_prefix + path << " HTTP/1.0\r\n"; + request_stream << "Host: " << url.server << "\r\n"; request_stream << "content-length: " << postjson.size() << "\r\n"; request_stream << "Accept: */*\r\n"; request_stream << "Connection: close\r\n\r\n"; @@ -118,9 +123,9 @@ namespace eosio { namespace client { namespace http { unsigned int status_code; std::string re; - if(scheme == "http") { + if(url.scheme == "http") { tcp::socket socket(io_service); - do_connect(socket, server, port); + do_connect(socket, url.server, url.port); re = do_txrx(socket, request, status_code); } else { //https @@ -137,13 +142,13 @@ namespace eosio { namespace client { namespace http { boost::asio::ssl::stream socket(io_service, ssl_context); socket.set_verify_mode(boost::asio::ssl::verify_peer); - do_connect(socket.next_layer(), server, port); + do_connect(socket.next_layer(), url.server, url.port); socket.handshake(boost::asio::ssl::stream_base::client); re = do_txrx(socket, request, status_code); //try and do a clean shutdown; but swallow if this fails (other side could have already gave TCP the ax) try {socket.shutdown();} catch(...) {} } - + const auto response_result = fc::json::from_string(re); if( status_code == 200 || status_code == 201 || status_code == 202 ) { return response_result; diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index 14e8b436edd..4bead25b1b9 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -5,9 +5,19 @@ #pragma once namespace eosio { namespace client { namespace http { + + struct parsed_url { + string scheme; + string server; + string port; + string path_prefix; + }; + + parsed_url parse_url( const string& server_url ); + fc::variant do_http_call( const std::string& server_url, - const std::string& path, - const fc::variant& postdata = fc::variant() ); + const std::string& path, + const fc::variant& postdata = fc::variant() ); const string chain_func_base = "/v1/chain"; const string get_info_func = chain_func_base + "/get_info"; @@ -22,8 +32,12 @@ namespace eosio { namespace client { namespace http { const string get_currency_stats_func = chain_func_base + "/get_currency_stats"; const string get_required_keys = chain_func_base + "/get_required_keys"; + + const string history_func_base = "/v1/history"; + const string get_actions_func = history_func_base + "/get_actions"; + const string get_transaction_func = history_func_base + "/get_transaction"; + const string account_history_func_base = "/v1/account_history"; - const string get_transaction_func = account_history_func_base + "/get_transaction"; const string get_transactions_func = account_history_func_base + "/get_transactions"; const string get_key_accounts_func = account_history_func_base + "/get_key_accounts"; const string get_controlled_accounts_func = account_history_func_base + "/get_controlled_accounts"; @@ -46,6 +60,7 @@ namespace eosio { namespace client { namespace http { const string wallet_unlock = wallet_func_base + "/unlock"; const string wallet_import_key = wallet_func_base + "/import_key"; const string wallet_sign_trx = wallet_func_base + "/sign_transaction"; + const string keosd_stop = "/v1/keosd/stop"; FC_DECLARE_EXCEPTION( connection_exception, 1100000, "Connection Exception" ); - }}} \ No newline at end of file + }}} diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 96cdb2ccc4f..948d43b3e11 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -39,7 +39,7 @@ Usage: programs/cleos/cleos [OPTIONS] SUBCOMMAND sign Sign a transaction push Push arbitrary transactions to the blockchain multisig Multisig contract commands - + ``` To get help with any particular subcommand, run it with no arguments as well: ``` @@ -69,11 +69,10 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey -p,--permission TEXT ... An account and permission level to authorize, as in 'account@permission' (defaults to 'creator@active') ``` */ + #include #include #include -#include -#include #include #include #include @@ -84,11 +83,22 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include +#include #include #include -#include +#include #include +#include +#pragma push_macro("N") +#undef N + +#include +#include +#include +#include +#include +#include #include #include #include @@ -97,6 +107,8 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include +#pragma pop_macro("N") + #include #include #include @@ -136,7 +148,7 @@ FC_DECLARE_EXCEPTION( localized_exception, 10000000, "an error occured" ); ) string url = "http://localhost:8888/"; -string wallet_url = "http://localhost:8888/"; +string wallet_url = "http://localhost:8900/"; auto tx_expiration = fc::seconds(30); string tx_ref_block_num_or_id; @@ -145,7 +157,7 @@ bool tx_dont_broadcast = false; bool tx_skip_sign = false; bool tx_print_json = false; -uint32_t tx_max_cpu_usage = 0; +uint8_t tx_max_cpu_usage = 0; uint32_t tx_max_net_usage = 0; vector tx_permission; @@ -173,7 +185,7 @@ void add_standard_transaction_options(CLI::App* cmd, string default_permission = msg += " (defaults to '" + default_permission + "')"; cmd->add_option("-p,--permission", tx_permission, localized(msg.c_str())); - cmd->add_option("--max-cpu-usage", tx_max_cpu_usage, localized("set an upper limit on the cpu usage budget, in instructions-retired, for the execution of the transaction (defaults to 0 which means no limit)")); + cmd->add_option("--max-cpu-usage-ms", tx_max_cpu_usage, localized("set an upper limit on the milliseconds of cpu usage budget, for the execution of the transaction (defaults to 0 which means no limit)")); cmd->add_option("--max-net-usage", tx_max_net_usage, localized("set an upper limit on the net usage budget, in bytes, for the transaction (defaults to 0 which means no limit)")); } @@ -217,23 +229,12 @@ eosio::chain_apis::read_only::get_info_results get_info() { return call(url, get_info_func).as(); } -string generate_nonce_value() { +string generate_nonce_string() { return fc::to_string(fc::time_point::now().time_since_epoch().count()); } -chain::action generate_nonce() { - auto v = generate_nonce_value(); - variant nonce = fc::mutable_variant_object() - ("value", v); - - try { - auto result = call(get_code_func, fc::mutable_variant_object("account_name", name(config::system_account_name))); - abi_serializer eosio_serializer(result["abi"].as()); - return chain::action( {}, config::system_account_name, "nonce", eosio_serializer.variant_to_binary("nonce", nonce)); - } - catch (...) { - EOS_THROW(account_query_exception, "A system contract is required to use nonce"); - } +chain::action generate_nonce_action() { + return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count())); } fc::variant determine_required_keys(const signed_transaction& trx) { @@ -259,26 +260,24 @@ fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000 trx.expiration = info.head_block_time + tx_expiration; // Set tapos, default to last irreversible block if it's not specified by the user - block_id_type ref_block_id; + block_id_type ref_block_id = info.last_irreversible_block_id; try { fc::variant ref_block; if (!tx_ref_block_num_or_id.empty()) { ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id)); - } else { - ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", info.last_irreversible_block_num)); + ref_block_id = ref_block["id"].as(); } - ref_block_id = ref_block["id"].as(); } EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id)); trx.set_reference_block(ref_block_id); if (tx_force_unique) { - trx.context_free_actions.emplace_back( generate_nonce() ); + trx.context_free_actions.emplace_back( generate_nonce_action() ); } auto required_keys = determine_required_keys(trx); size_t num_keys = required_keys.is_array() ? required_keys.get_array().size() : 1; - trx.max_kcpu_usage = (tx_max_cpu_usage + 1023)/1024; + trx.max_cpu_usage_ms = tx_max_net_usage; trx.max_net_usage_words = (tx_max_net_usage + 7)/8; if (!tx_skip_sign) { @@ -299,41 +298,81 @@ fc::variant push_actions(std::vector&& actions, int32_t extra_kcp return push_transaction(trx, extra_kcpu, compression); } -void print_result( const fc::variant& result ) { +void print_action( const fc::variant& at ) { + const auto& receipt = at["receipt"]; + auto receiver = receipt["receiver"].as_string(); + const auto& act = at["act"].get_object(); + auto code = act["account"].as_string(); + auto func = act["name"].as_string(); + auto args = fc::json::to_string( act["data"] ); + auto console = at["console"].as_string(); + + /* + if( code == "eosio" && func == "setcode" ) + args = args.substr(40)+"..."; + if( name(code) == config::system_account_name && func == "setabi" ) + args = args.substr(40)+"..."; + */ + if( args.size() > 100 ) args = args.substr(0,100) + "..."; + cout << "#" << std::setw(14) << right << receiver << " <= " << std::setw(28) << std::left << (code +"::" + func) << " " << args << "\n"; + if( console.size() ) { + std::stringstream ss(console); + string line; + std::getline( ss, line ); + cout << ">> " << line << "\n"; + } +} + +void print_action_tree( const fc::variant& action ) { + print_action( action ); + const auto& inline_traces = action["inline_traces"].get_array(); + for( const auto& t : inline_traces ) { + print_action_tree( t ); + } +} + +void print_result( const fc::variant& result ) { try { const auto& processed = result["processed"]; const auto& transaction_id = processed["id"].as_string(); - const auto& status = processed["status"].as_string() ; - auto net = processed["net_usage"].as_int64(); - auto cpu = processed["cpu_usage"].as_int64(); - - cout << status << " transaction: " << transaction_id << " " << net << " bytes " << cpu << " cycles\n"; - - const auto& actions = processed["action_traces"].get_array(); - for( const auto& at : actions ) { - auto receiver = at["receiver"].as_string(); - const auto& act = at["act"].get_object(); - auto code = act["account"].as_string(); - auto func = act["name"].as_string(); - auto args = fc::json::to_string( act["data"] ); - auto console = at["console"].as_string(); - - /* - if( code == "eosio" && func == "setcode" ) - args = args.substr(40)+"..."; - if( name(code) == config::system_account_name && func == "setabi" ) - args = args.substr(40)+"..."; - */ - if( args.size() > 100 ) args = args.substr(0,100) + "..."; - - cout << "#" << std::setw(14) << right << receiver << " <= " << std::setw(28) << std::left << (code +"::" + func) << " " << args << "\n"; - if( console.size() ) { - std::stringstream ss(console); - string line; - std::getline( ss, line ); - cout << ">> " << line << "\n"; + string status = processed["receipt"].is_object() ? processed["receipt"]["status"].as_string() : "failed"; + int64_t net = -1; + int64_t cpu = -1; + if (processed.get_object().contains("receipt")) { + const auto& receipt = processed["receipt"]; + if (receipt.is_object()) { + net = receipt["net_usage_words"].as_int64() * 8; + cpu = receipt["cpu_usage_us"].as_int64(); } } -} + + cerr << status << " transaction: " << transaction_id << " "; + if (net < 0) { + cerr << ""; + } else { + cerr << net; + } + cerr << " bytes "; + if (cpu < 0) { + cerr << ""; + } else { + cerr << cpu; + } + + cerr << " us\n"; + + if( status == "failed" ) { + auto soft_except = processed["except"].as>(); + if( soft_except ) { + edump((soft_except->to_detail_string())); + } + } else { + const auto& actions = processed["action_traces"].get_array(); + for( const auto& a : actions ) { + print_action_tree( a ); + } + wlog( "\rwarning: transaction executed locally, but may not be confirmed by the network yet" ); + } +} FC_CAPTURE_AND_RETHROW( (result) ) } using std::cout; void send_actions(std::vector&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { @@ -360,12 +399,11 @@ void send_transaction( signed_transaction& trx, int32_t extra_kcpu, packed_trans chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) { return action { tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), - contracts::newaccount{ + eosio::chain::newaccount{ .creator = creator, .name = newaccount, .owner = eosio::chain::authority{1, {{owner, 1}}, {}}, - .active = eosio::chain::authority{1, {{active, 1}}, {}}, - .recovery = eosio::chain::authority{1, {}, {{{creator, config::active_name}, 1}}} + .active = eosio::chain::authority{1, {{active, 1}}, {}} } }; } @@ -377,56 +415,89 @@ chain::action create_action(const vector& authorization, const ("args", args); auto result = call(json_to_bin_func, arg); - wlog("result=${r}",("r",result)); + wdump((result)(arg)); return chain::action{authorization, code, act, result.get_object()["binargs"].as()}; } +chain::action create_buyram(const name& creator, const name& newaccount, const asset& quantity) { + fc::variant act_payload = fc::mutable_variant_object() + ("payer", creator.to_string()) + ("receiver", newaccount.to_string()) + ("quant", quantity.to_string()); + return create_action(tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), + config::system_account_name, N(buyram), act_payload); +} + +chain::action create_buyrambytes(const name& creator, const name& newaccount, uint32_t numbytes) { + fc::variant act_payload = fc::mutable_variant_object() + ("payer", creator.to_string()) + ("receiver", newaccount.to_string()) + ("bytes", numbytes); + return create_action(tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), + config::system_account_name, N(buyrambytes), act_payload); +} + +chain::action create_delegate(const name& from, const name& receiver, const asset& net, const asset& cpu, bool transfer) { + fc::variant act_payload = fc::mutable_variant_object() + ("from", from.to_string()) + ("receiver", receiver.to_string()) + ("stake_net_quantity", net.to_string()) + ("stake_cpu_quantity", cpu.to_string()) + ("transfer", transfer); + return create_action(tx_permission.empty() ? vector{{from,config::active_name}} : get_account_permissions(tx_permission), + config::system_account_name, N(delegatebw), act_payload); +} + fc::variant regproducer_variant(const account_name& producer, public_key_type key, - uint64_t max_storage_size, - uint32_t percent_of_max_inflation_rate, - uint32_t storage_reserve_ratio) { + string url) { + /* fc::variant_object params = fc::mutable_variant_object() - ("base_per_transaction_net_usage", config::default_base_per_transaction_net_usage) - ("base_per_transaction_cpu_usage", config::default_base_per_transaction_cpu_usage) - ("base_per_action_cpu_usage", config::default_base_per_action_cpu_usage) - ("base_setcode_cpu_usage", config::default_base_setcode_cpu_usage) - ("per_signature_cpu_usage", config::default_per_signature_cpu_usage) - ("per_lock_net_usage", config::default_per_lock_net_usage) - ("context_free_discount_cpu_usage_num", config::default_context_free_discount_cpu_usage_num) - ("context_free_discount_cpu_usage_den", config::default_context_free_discount_cpu_usage_den) - ("max_transaction_cpu_usage", config::default_max_transaction_cpu_usage) - ("max_transaction_net_usage", config::default_max_transaction_net_usage) - ("max_block_cpu_usage", config::default_max_block_cpu_usage) - ("target_block_cpu_usage_pct", config::default_target_block_cpu_usage_pct) - ("max_block_net_usage", config::default_max_block_net_usage) - ("target_block_net_usage_pct", config::default_target_block_net_usage_pct) - ("max_transaction_lifetime", config::default_max_trx_lifetime) - ("max_transaction_exec_time", config::default_max_trx_runtime) - ("max_authority_depth", config::default_max_auth_depth) - ("max_inline_depth", config::default_max_inline_depth) - ("max_inline_action_size", config::default_max_inline_action_size) - ("max_generated_transaction_count", config::default_max_gen_trx_count) - ("max_storage_size", max_storage_size) - ("percent_of_max_inflation_rate", percent_of_max_inflation_rate) - ("storage_reserve_ratio", storage_reserve_ratio); + ("max_block_net_usage", config::default_max_block_net_usage) + ("target_block_net_usage_pct", config::default_target_block_net_usage_pct) + ("max_transaction_net_usage", config::default_max_transaction_net_usage) + ("base_per_transaction_net_usage", config::default_base_per_transaction_net_usage) + ("net_usage_leeway", config::default_net_usage_leeway) + ("context_free_discount_net_usage_num", config::default_context_free_discount_net_usage_num) + ("context_free_discount_net_usage_den", config::default_context_free_discount_net_usage_den) + ("max_block_cpu_usage", config::default_max_block_cpu_usage) + ("target_block_cpu_usage_pct", config::default_target_block_cpu_usage_pct) + ("max_transaction_cpu_usage", config::default_max_transaction_cpu_usage) + ("base_per_transaction_cpu_usage", config::default_base_per_transaction_cpu_usage) + ("base_per_action_cpu_usage", config::default_base_per_action_cpu_usage) + ("base_setcode_cpu_usage", config::default_base_setcode_cpu_usage) + ("per_signature_cpu_usage", config::default_per_signature_cpu_usage) + ("cpu_usage_leeway", config::default_cpu_usage_leeway) + ("context_free_discount_cpu_usage_num", config::default_context_free_discount_cpu_usage_num) + ("context_free_discount_cpu_usage_den", config::default_context_free_discount_cpu_usage_den) + ("max_transaction_lifetime", config::default_max_trx_lifetime) + ("deferred_trx_expiration_window", config::default_deferred_trx_expiration_window) + ("max_transaction_delay", config::default_max_trx_delay) + ("max_inline_action_size", config::default_max_inline_action_size) + ("max_inline_depth", config::default_max_inline_action_depth) + ("max_authority_depth", config::default_max_auth_depth) + ("max_generated_transaction_count", config::default_max_gen_trx_count) + ("max_storage_size", max_storage_size) + ("percent_of_max_inflation_rate", percent_of_max_inflation_rate) + ("storage_reserve_ratio", storage_reserve_ratio); + */ return fc::mutable_variant_object() ("producer", producer) - ("producer_key", fc::raw::pack(key)) - ("prefs", params); + ("producer_key", key) + ("url", url); } -chain::action create_transfer(const name& sender, const name& recipient, uint64_t amount, const string& memo ) { +chain::action create_transfer(const string& contract, const name& sender, const name& recipient, asset amount, const string& memo ) { auto transfer = fc::mutable_variant_object ("from", sender) ("to", recipient) - ("quantity", asset(amount)) + ("quantity", amount) ("memo", memo); auto args = fc::mutable_variant_object - ("code", name(config::system_account_name)) + ("code", contract) ("action", "transfer") ("args", transfer); @@ -434,14 +505,14 @@ chain::action create_transfer(const name& sender, const name& recipient, uint64_ return action { tx_permission.empty() ? vector{{sender,config::active_name}} : get_account_permissions(tx_permission), - config::system_account_name, "transfer", result.get_object()["binargs"].as() + contract, "transfer", result.get_object()["binargs"].as() }; } -chain::action create_setabi(const name& account, const contracts::abi_def& abi) { +chain::action create_setabi(const name& account, const abi_def& abi) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::setabi{ + setabi{ .account = account, .abi = abi } @@ -451,7 +522,7 @@ chain::action create_setabi(const name& account, const contracts::abi_def& abi) chain::action create_setcode(const name& account, const bytes& code) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::setcode{ + setcode{ .account = account, .vmtype = 0, .vmversion = 0, @@ -462,28 +533,28 @@ chain::action create_setcode(const name& account, const bytes& code) { chain::action create_updateauth(const name& account, const name& permission, const name& parent, const authority& auth) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::updateauth{account, permission, parent, auth}}; + updateauth{account, permission, parent, auth}}; } chain::action create_deleteauth(const name& account, const name& permission) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::deleteauth{account, permission}}; + deleteauth{account, permission}}; } chain::action create_linkauth(const name& account, const name& code, const name& type, const name& requirement) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::linkauth{account, code, type, requirement}}; + linkauth{account, code, type, requirement}}; } chain::action create_unlinkauth(const name& account, const name& code, const name& type) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), - contracts::unlinkauth{account, code, type}}; + unlinkauth{account, code, type}}; } fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) { regex r("^[ \t]*[\{\[]"); - if ( !regex_search(file_or_str, r) && is_regular_file(file_or_str) ) { + if ( !regex_search(file_or_str, r) && fc::is_regular_file(file_or_str) ) { return fc::json::from_file(file_or_str, ptype); } else { return fc::json::from_string(file_or_str, ptype); @@ -506,6 +577,42 @@ authority parse_json_authority_or_key(const std::string& authorityJsonOrFile) { } } +asset to_asset( const string& code, const string& s ) { + static map cache; + auto a = asset::from_string( s ); + eosio::chain::symbol_code sym = a.sym.to_symbol_code(); + auto it = cache.find( sym ); + auto sym_str = a.symbol_name(); + if ( it == cache.end() ) { + auto json = call(get_currency_stats_func, fc::mutable_variant_object("json", false) + ("code", code) + ("symbol", sym_str) + ); + auto obj = json.get_object(); + auto obj_it = obj.find( sym_str ); + if (obj_it != obj.end()) { + auto result = obj_it->value().as(); + auto p = cache.insert(make_pair( sym, result.max_supply.sym )); + it = p.first; + } else { + FC_THROW("Symbol ${s} is not supported by token contract ${c}", ("s", sym_str)("c", code)); + } + } + auto expected_symbol = it->second; + if ( a.decimals() < expected_symbol.decimals() ) { + auto factor = expected_symbol.precision() / a.precision(); + auto a_old = a; + a = asset( a.amount * factor, expected_symbol ); + } else if ( a.decimals() > expected_symbol.decimals() ) { + FC_THROW("Too many decimal digits in ${a}, only ${d} supported", ("a", a)("d", expected_symbol.decimals())); + } // else precision matches + return a; +} + +inline asset to_asset( const string& s ) { + return to_asset( "eosio.token", s ); +} + struct set_account_permission_subcommand { string accountStr; string permissionStr; @@ -593,7 +700,79 @@ struct set_action_permission_subcommand { }; -CLI::callback_t old_host_port = [](CLI::results_t) { +bool port_used(uint16_t port) { + using namespace boost::asio; + + io_service ios; + boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), port); + boost::asio::ip::tcp::socket socket(ios); + boost::system::error_code ec = error::would_block; + //connecting/failing to connect to localhost should be always fast - don't care about timeouts + socket.async_connect(endpoint, [&](const boost::system::error_code& error) { ec = error; } ); + do { + ios.run_one(); + } while (ec == error::would_block); + return !ec; +} + +void try_port( uint16_t port, uint32_t duration ) { + using namespace std::chrono; + auto start_time = duration_cast( system_clock::now().time_since_epoch() ).count(); + while ( !port_used(port)) { + if (duration_cast( system_clock::now().time_since_epoch()).count() - start_time > duration ) { + std::cerr << "Unable to connect to keosd, if keosd is running please kill the process and try again.\n"; + throw connection_exception(fc::log_messages{FC_LOG_MESSAGE(error, "Unable to connect to keosd")}); + } + } +} + +void ensure_keosd_running() { + auto parsed_url = parse_url(wallet_url); + if (parsed_url.server != "localhost" && parsed_url.server == "127.0.0.1") + return; + + auto wallet_port = std::stoi(parsed_url.port); + if (wallet_port < 0 || wallet_port > 65535) { + FC_THROW("port is not in valid range"); + } + + if (port_used(uint16_t(wallet_port))) // Hopefully taken by keosd + return; + + + boost::filesystem::path binPath = boost::dll::program_location(); + binPath.remove_filename(); + // This extra check is necessary when running cleos like this: ./cleos ... + if (binPath.filename_is_dot()) + binPath.remove_filename(); + binPath.append("keosd"); // if cleos and keosd are in the same installation directory + if (!boost::filesystem::exists(binPath)) { + binPath.remove_filename().remove_filename().append("keosd").append("keosd"); + } + + if (boost::filesystem::exists(binPath)) { + namespace bp = boost::process; + binPath = boost::filesystem::canonical(binPath); + ::boost::process::child keos(binPath, "--http-server-address=127.0.0.1:" + parsed_url.port, + bp::std_in.close(), + bp::std_out > bp::null, + bp::std_err > bp::null); + if (keos.running()) { + std::cerr << binPath << " launched" << std::endl; + keos.detach(); + sleep(1); + } else { + std::cerr << "No wallet service listening on 127.0.0.1:" + << std::to_string(wallet_port) << ". Failed to launch " << binPath << std::endl; + } + } else { + std::cerr << "No wallet service listening on 127.0.0.1: " << std::to_string(wallet_port) + << ". Cannot automatically start keosd because keosd was not found." << std::endl; + } +} + + +CLI::callback_t obsoleted_option_host_port = [](CLI::results_t) { std::cerr << localized("Host and port options (-H, --wallet-host, etc.) have been replaced with -u/--url and --wallet-url\n" "Use for example -u http://localhost:8888 or --url https://example.invalid/\n"); exit(1); @@ -603,17 +782,13 @@ CLI::callback_t old_host_port = [](CLI::results_t) { struct register_producer_subcommand { string producer_str; string producer_key_str; - uint64_t max_storage_size = 10 * 1024 * 1024; - uint32_t percent_of_max_inflation_rate = 0; - uint32_t storage_reserve_ratio = 1000; + string url; register_producer_subcommand(CLI::App* actionRoot) { auto register_producer = actionRoot->add_subcommand("regproducer", localized("Register a new producer")); register_producer->add_option("account", producer_str, localized("The account to register as a producer"))->required(); register_producer->add_option("producer_key", producer_key_str, localized("The producer's public key"))->required(); - register_producer->add_option("max_storage_size", max_storage_size, localized("The max storage size"), true); - register_producer->add_option("percent_of_max_inflation_rate", percent_of_max_inflation_rate, localized("Percent of max inflation rate"), true); - register_producer->add_option("storage_reserve_ratio", storage_reserve_ratio, localized("Storage Reserve Ratio"), true); + register_producer->add_option("url", url, localized("url where info about producer can be found"), true); add_standard_transaction_options(register_producer); @@ -623,12 +798,69 @@ struct register_producer_subcommand { producer_key = public_key_type(producer_key_str); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid producer public key: ${public_key}", ("public_key", producer_key_str)) - auto regprod_var = regproducer_variant(producer_str, producer_key, max_storage_size, percent_of_max_inflation_rate, storage_reserve_ratio); + auto regprod_var = regproducer_variant(producer_str, producer_key, url ); send_actions({create_action({permission_level{producer_str,config::active_name}}, config::system_account_name, N(regproducer), regprod_var)}); }); } }; +struct create_account_subcommand { + string creator; + string account_name; + string owner_key_str; + string active_key_str; + string stake_net; + string stake_cpu; + uint32_t buy_ram_bytes_in_kbytes = 8; + string buy_ram_eos; + bool transfer; + bool simple; + + create_account_subcommand(CLI::App* actionRoot, bool s) : simple(s) { + auto createAccount = actionRoot->add_subcommand( (simple ? "account" : "newaccount"), localized("Create an account, buy ram, stake for bandwidth for the account")); + createAccount->add_option("creator", creator, localized("The name of the account creating the new account"))->required(); + createAccount->add_option("name", account_name, localized("The name of the new account"))->required(); + createAccount->add_option("OwnerKey", owner_key_str, localized("The owner public key for the new account"))->required(); + createAccount->add_option("ActiveKey", active_key_str, localized("The active public key for the new account")); + + if (!simple) { + createAccount->add_option("--stake-net", stake_net, + (localized("The amount of EOS delegated for net bandwidth")))->required(); + createAccount->add_option("--stake-cpu", stake_cpu, + (localized("The amount of EOS delegated for CPU bandwidth")))->required(); + createAccount->add_option("--buy-ram-bytes", buy_ram_bytes_in_kbytes, + (localized("The amount of RAM bytes to purchase for the new account in kilobytes KiB, default is 8 KiB"))); + createAccount->add_option("--buy-ram-EOS", buy_ram_eos, + (localized("The amount of RAM bytes to purchase for the new account in EOS"))); + createAccount->add_flag("--transfer", transfer, + (localized("Transfer?"))); + } + + add_standard_transaction_options(createAccount); + + createAccount->set_callback([this] { + if( !active_key_str.size() ) + active_key_str = owner_key_str; + public_key_type owner_key, active_key; + try { + owner_key = public_key_type(owner_key_str); + } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid owner public key: ${public_key}", ("public_key", owner_key_str)); + try { + active_key = public_key_type(active_key_str); + } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid active public key: ${public_key}", ("public_key", active_key_str)); + auto create = create_newaccount(creator, account_name, owner_key, active_key); + if (!simple) { + action buyram = !buy_ram_eos.empty() ? create_buyram(creator, account_name, to_asset(buy_ram_eos)) + : create_buyrambytes(creator, account_name, buy_ram_bytes_in_kbytes * 1024); + action delegate = create_delegate( creator, account_name, to_asset(stake_net), to_asset(stake_cpu), transfer); + send_actions( { create, buyram, delegate } ); + } else { + send_actions( { create } ); + } + }); + } +}; + struct unregister_producer_subcommand { string producer_str; @@ -668,30 +900,87 @@ struct vote_producer_proxy_subcommand { struct vote_producers_subcommand { string voter_str; - std::vector producers; + vector producer_names; vote_producers_subcommand(CLI::App* actionRoot) { auto vote_producers = actionRoot->add_subcommand("prods", localized("Vote for one or more producers")); vote_producers->add_option("voter", voter_str, localized("The voting account"))->required(); - vote_producers->add_option("producers", producers, localized("The account(s) to vote for. All options from this position and following will be treated as the producer list."))->required(); + vote_producers->add_option("producers", producer_names, localized("The account(s) to vote for. All options from this position and following will be treated as the producer list."))->required(); add_standard_transaction_options(vote_producers); vote_producers->set_callback([this] { + + std::sort( producer_names.begin(), producer_names.end() ); + fc::variant act_payload = fc::mutable_variant_object() ("voter", voter_str) ("proxy", "") - ("producers", producers); + ("producers", producer_names); send_actions({create_action({permission_level{voter_str,config::active_name}}, config::system_account_name, N(voteproducer), act_payload)}); }); } }; +struct list_producers_subcommand { + bool print_json = false; + bool sort_names = false; + + list_producers_subcommand(CLI::App* actionRoot) { + auto list_producers = actionRoot->add_subcommand("listproducers", localized("List producers")); + list_producers->add_flag("--json,-j", print_json, localized("Output in JSON format") ); + list_producers->add_flag("--sort-account-names,-n", sort_names, localized("Sort by account names (default order is by votes)") ); + list_producers->set_callback([this] { + auto result = call(get_table_func, fc::mutable_variant_object("json", true) + ("code", name(config::system_account_name).to_string()) + ("scope", name(config::system_account_name).to_string()) + ("table", "producers") + ); + + if ( !print_json ) { + auto res = result.as(); + std::vector> v; + for ( auto& row : res.rows ) { + auto& r = row.get_object(); + v.emplace_back( r["owner"].as_string(), r["total_votes"].as_string(), r["producer_key"].as_string(), r["url"].as_string() ); + + } + if ( !v.empty() ) { + if ( sort_names ) { + std::sort( v.begin(), v.end(), [](auto a, auto b) { return std::get<0>(a) < std::get<0>(b); } ); + } else { + std::sort( v.begin(), v.end(), [](auto a, auto b) { + return std::get<1>(a) < std::get<1>(b) || (std::get<1>(a) == std::get<1>(b) && std::get<0>(a) < std::get<0>(b)); } + ); + } + + std::cout << std::left << std::setw(14) << "Producer" << std::setw(55) << "Producer key" + << std::setw(50) << "Url" << "Total votes" << std::endl; + for ( auto& x : v ) { + std::cout << std::left << std::setw(14) << std::get<0>(x) << std::setw(55) << std::get<2>(x) + << std::setw(50) << std::get<3>(x) << std::get<1>(x) << std::endl; + } + } else { + std::cout << "No producers found" << std::endl; + } + } else { + if ( sort_names ) { + FC_THROW("Sorting producers is not supported for JSON format"); + } + std::cout << fc::json::to_pretty_string(result) + << std::endl; + } + } + ); + } +}; + struct delegate_bandwidth_subcommand { string from_str; string receiver_str; string stake_net_amount; string stake_cpu_amount; string stake_storage_amount; + bool transfer = false; delegate_bandwidth_subcommand(CLI::App* actionRoot) { auto delegate_bandwidth = actionRoot->add_subcommand("delegatebw", localized("Delegate bandwidth")); @@ -699,16 +988,18 @@ struct delegate_bandwidth_subcommand { delegate_bandwidth->add_option("receiver", receiver_str, localized("The account to receive the delegated bandwidth"))->required(); delegate_bandwidth->add_option("stake_net_quantity", stake_net_amount, localized("The amount of EOS to stake for network bandwidth"))->required(); delegate_bandwidth->add_option("stake_cpu_quantity", stake_cpu_amount, localized("The amount of EOS to stake for CPU bandwidth"))->required(); - delegate_bandwidth->add_option("stake_storage_quantity", stake_storage_amount, localized("The amount of EOS to stake for storage"))->required(); + delegate_bandwidth->add_flag("--transfer", transfer, localized("specify to stake in name of receiver rather than in name of from"))->required(); add_standard_transaction_options(delegate_bandwidth); delegate_bandwidth->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("receiver", receiver_str) - ("stake_net_quantity", stake_net_amount + " EOS") - ("stake_cpu_quantity", stake_cpu_amount + " EOS") - ("stake_storage_quantity", stake_storage_amount + " EOS"); + ("stake_net_quantity", to_asset(stake_net_amount)) + ("stake_cpu_quantity", to_asset(stake_cpu_amount)) + ("transfer", transfer) + ; + wdump((act_payload)); send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(delegatebw), act_payload)}); }); } @@ -727,21 +1018,60 @@ struct undelegate_bandwidth_subcommand { undelegate_bandwidth->add_option("receiver", receiver_str, localized("The account to undelegate bandwidth from"))->required(); undelegate_bandwidth->add_option("unstake_net_quantity", unstake_net_amount, localized("The amount of EOS to undelegate for network bandwidth"))->required(); undelegate_bandwidth->add_option("unstake_cpu_quantity", unstake_cpu_amount, localized("The amount of EOS to undelegate for CPU bandwidth"))->required(); - undelegate_bandwidth->add_option("unstake_storage_bytes", unstake_storage_bytes, localized("The amount of byte storage to undelegate"))->required(); add_standard_transaction_options(undelegate_bandwidth); undelegate_bandwidth->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("receiver", receiver_str) - ("unstake_net_quantity", unstake_net_amount + " EOS") - ("unstake_cpu_quantity", unstake_cpu_amount + " EOS") - ("unstake_storage_bytes", unstake_storage_bytes); + ("unstake_net_quantity", to_asset(unstake_net_amount)) + ("unstake_cpu_quantity", to_asset(unstake_cpu_amount)); send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(undelegatebw), act_payload)}); }); } }; +struct buyram_subcommand { + string from_str; + string receiver_str; + string amount; + + buyram_subcommand(CLI::App* actionRoot) { + auto buyram = actionRoot->add_subcommand("buyram", localized("Buy RAM")); + buyram->add_option("payer", from_str, localized("The account paying for RAM"))->required(); + buyram->add_option("receiver", receiver_str, localized("The account receiving bought RAM"))->required(); + buyram->add_option("tokens", amount, localized("The amount of EOS to pay for RAM"))->required(); + add_standard_transaction_options(buyram); + buyram->set_callback([this] { + fc::variant act_payload = fc::mutable_variant_object() + ("payer", from_str) + ("receiver", receiver_str) + ("quant", to_asset(amount)); + send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(buyram), act_payload)}); + }); + } +}; + +struct sellram_subcommand { + string from_str; + string receiver_str; + uint64_t amount; + + sellram_subcommand(CLI::App* actionRoot) { + auto sellram = actionRoot->add_subcommand("sellram", localized("Sell RAM")); + sellram->add_option("account", receiver_str, localized("The account to receive EOS for sold RAM"))->required(); + sellram->add_option("bytes", amount, localized("Number of RAM bytes to sell"))->required(); + add_standard_transaction_options(sellram); + + sellram->set_callback([this] { + fc::variant act_payload = fc::mutable_variant_object() + ("account", receiver_str) + ("bytes", amount); + send_actions({create_action({permission_level{receiver_str,config::active_name}}, config::system_account_name, N(sellram), act_payload)}); + }); + } +}; + struct claimrewards_subcommand { string owner; @@ -790,45 +1120,6 @@ struct unregproxy_subcommand { } }; -struct postrecovery_subcommand { - string account; - string json_str_or_file; - string memo; - - postrecovery_subcommand(CLI::App* actionRoot) { - auto post_recovery = actionRoot->add_subcommand("postrecovery", localized("Post recovery request")); - post_recovery->add_option("account", account, localized("The account to post the recovery for"))->required(); - post_recovery->add_option("data", json_str_or_file, localized("The authority to post the recovery with as EOS public key, JSON string, or filename"))->required(); - post_recovery->add_option("memo", memo, localized("A memo describing the post recovery request"))->required(); - add_standard_transaction_options(post_recovery); - - post_recovery->set_callback([this] { - authority data_auth = parse_json_authority_or_key(json_str_or_file); - fc::variant act_payload = fc::mutable_variant_object() - ("account", account) - ("data", data_auth) - ("memo", memo); - send_actions({create_action({permission_level{account,config::active_name}}, config::system_account_name, N(postrecovery), act_payload)}); - }); - } -}; - -struct vetorecovery_subcommand { - string account; - - vetorecovery_subcommand(CLI::App* actionRoot) { - auto veto_recovery = actionRoot->add_subcommand("vetorecovery", localized("Veto a posted recovery")); - veto_recovery->add_option("account", account, localized("The account to veto the recovery for"))->required(); - add_standard_transaction_options(veto_recovery); - - veto_recovery->set_callback([this] { - fc::variant act_payload = fc::mutable_variant_object() - ("account", account); - send_actions({create_action({permission_level{account,config::active_name}}, config::system_account_name, N(vetorecovery), act_payload)}); - }); - } -}; - struct canceldelay_subcommand { string cancelling_account; string cancelling_permission; @@ -851,26 +1142,175 @@ struct canceldelay_subcommand { } }; -int main( int argc, char** argv ) { - fc::path binPath = argv[0]; - if (binPath.is_relative()) { - binPath = relative(binPath, current_path()); +void get_account( const string& accountName, bool json_format ) { + auto json = call(get_account_func, fc::mutable_variant_object("account_name", accountName)); + auto res = json.as(); + + if (!json_format) { + std::cout << "privileged: " << ( res.privileged ? "true" : "false") << std::endl; + + constexpr size_t indent_size = 5; + const string indent(indent_size, ' '); + + std::cout << "permissions: " << std::endl; + unordered_map/*children*/> tree; + vector roots; //we don't have multiple roots, but we can easily handle them here, so let's do it just in case + unordered_map cache; + for ( auto& perm : res.permissions ) { + if ( perm.parent ) { + tree[perm.parent].push_back( perm.perm_name ); + } else { + roots.push_back( perm.perm_name ); + } + auto name = perm.perm_name; //keep copy before moving `perm`, since thirst argument of emplace can be evaluated first + // looks a little crazy, but should be efficient + cache.insert( std::make_pair(name, std::move(perm)) ); + } + std::function dfs_print = [&]( account_name name, int depth ) -> void { + auto& p = cache.at(name); + std::cout << indent << std::string(depth*3, ' ') << name << ' ' << std::setw(5) << p.required_auth.threshold << ": "; + for ( auto it = p.required_auth.keys.begin(); it != p.required_auth.keys.end(); ++it ) { + if ( it != p.required_auth.keys.begin() ) { + std::cout << ", "; + } + std::cout << it->weight << ' ' << string(it->key); + } + for ( auto& acc : p.required_auth.accounts ) { + std::cout << acc.weight << ' ' << string(acc.permission.actor) << '@' << string(acc.permission.permission) << ", "; + } + std::cout << std::endl; + auto it = tree.find( name ); + if (it != tree.end()) { + auto& children = it->second; + sort( children.begin(), children.end() ); + for ( auto& n : children ) { + // we have a tree, not a graph, so no need to check for already visited nodes + dfs_print( n, depth+1 ); + } + } // else it's a leaf node + }; + std::sort(roots.begin(), roots.end()); + for ( auto r : roots ) { + dfs_print( r, 0 ); + } + + std::cout << "memory: " << std::endl + << indent << "quota: " << std::setw(15) << res.ram_quota << " bytes used: " << std::setw(15) << res.ram_usage << " bytes" << std::endl << std::endl; + + std::cout << "net bandwidth:" << std::endl; + if ( res.total_resources.is_object() ) { + asset net_own( res.delegated_bandwidth.is_object() ? stoll( res.delegated_bandwidth.get_object()["net_weight"].as_string() ) : 0 ); + auto net_others = to_asset(res.total_resources.get_object()["net_weight"].as_string()) - net_own; + std::cout << indent << "staked:" << std::setw(20) << net_own + << std::string(11, ' ') << "(total stake delegated from account to self)" << std::endl + << indent << "delegated:" << std::setw(17) << net_others + << std::string(11, ' ') << "(total staked delegated to account from others)" << std::endl; + } + + string unit = "bytes"; + double net_usage = res.net_limit.available; + + if( res.net_limit.available >= 1024*1024*1024 ){ + unit = "Gb"; + net_usage /= 1024*1024*1024; + } else if( res.net_limit.available >= 1024*1024 ){ + unit = "Mb"; + net_usage /= 1024*1024; + } else if( res.net_limit.available >= 1024 ) { + unit = "Kb"; + net_usage /= 1024; + } + + std::cout << std::fixed << setprecision(3); + std::cout << indent << "used:" << std::setw(15) << ("~"+std::to_string(res.net_limit.used)) << " bytes (past 3 days)" + << std::string(3, ' ') << "(assuming current congestion and current usage)" << std::endl + << indent << "available:" << std::setw(19) << ("~"+std::to_string(net_usage)) << " "< 60*60*24 ) { + cunit = "days"; + cpu_avail /= 60*60*24; + } else if( cpu_avail > 60*60 ) { + cunit = "hr"; + cpu_avail /= 60*60; + } else if( cpu_avail > 60 ) { + cunit = "min"; + cpu_avail /= 60; + } + + + if ( res.total_resources.is_object() ) { + asset cpu_own( res.delegated_bandwidth.is_object() ? stoll( res.delegated_bandwidth.get_object()["cpu_weight"].as_string() ) : 0 ); + auto cpu_others = to_asset(res.total_resources.get_object()["cpu_weight"].as_string()) - cpu_own; + std::cout << indent << "staked:" << std::setw(20) << cpu_own + << std::string(11, ' ') << "(total stake delegated from account to self)" << std::endl + << indent << "delegated:" << std::setw(17) << cpu_others + << std::string(11, ' ') << "(total staked delegated to account from others)" << std::endl; + } + + std::cout << indent << "used:" << std::setw(15) << ("~"+std::to_string(double(res.cpu_limit.used/1000000.))) << " sec (past 3 days)\n" + << indent << "available:" << std::setw(19) << ("~"+std::to_string(cpu_avail) )<< " " << cunit <<"\n" + /* + << indent << "guaranteed:" << std::setw(12) << res.cpu_limit.guaranteed_per_day/1024 << " kcycle/day" + << std::string(4, ' ') << "(assuming 100% congestion and 0 usage in current window)" << std::endl + */ + << std::endl; + + if ( res.voter_info.is_object() ) { + auto& obj = res.voter_info.get_object(); + string proxy = obj["proxy"].as_string(); + if ( proxy.empty() ) { + auto& prods = obj["producers"].get_array(); + std::cout << "producers:"; + if ( !prods.empty() ) { + for ( int i = 0; i < prods.size(); ++i ) { + if ( i%3 == 0 ) { + std::cout << std::endl << indent; + } + std::cout << std::setw(16) << std::left << prods[i].as_string(); + } + std::cout << std::endl; + } else { + std::cout << indent << "" << std::endl; + } + } else { + std::cout << "proxy:" << indent << proxy << std::endl; + } + } + std::cout << std::endl; + } else { + std::cout << fc::json::to_pretty_string(json) << std::endl; } +} +int main( int argc, char** argv ) { setlocale(LC_ALL, ""); bindtextdomain(locale_domain, locale_path); textdomain(locale_domain); CLI::App app{"Command Line Interface to EOSIO Client"}; app.require_subcommand(); - app.add_option( "-H,--host", old_host_port, localized("the host where nodeos is running") )->group("hidden"); - app.add_option( "-p,--port", old_host_port, localized("the port where nodeos is running") )->group("hidden"); - app.add_option( "--wallet-host", old_host_port, localized("the host where keosd is running") )->group("hidden"); - app.add_option( "--wallet-port", old_host_port, localized("the port where keosd is running") )->group("hidden"); + app.add_option( "-H,--host", obsoleted_option_host_port, localized("the host where nodeos is running") )->group("hidden"); + app.add_option( "-p,--port", obsoleted_option_host_port, localized("the port where nodeos is running") )->group("hidden"); + app.add_option( "--wallet-host", obsoleted_option_host_port, localized("the host where keosd is running") )->group("hidden"); + app.add_option( "--wallet-port", obsoleted_option_host_port, localized("the port where keosd is running") )->group("hidden"); app.add_option( "-u,--url", url, localized("the http/https URL where nodeos is running"), true ); app.add_option( "--wallet-url", wallet_url, localized("the http/https URL where keosd is running"), true ); + app.set_callback([] { ensure_keosd_running();}); + bool verbose_errors = false; app.add_flag( "-v,--verbose", verbose_errors, localized("output verbose actions on error")); @@ -895,27 +1335,7 @@ int main( int argc, char** argv ) { }); // create account - string creator; - string account_name; - string owner_key_str; - string active_key_str; - - auto createAccount = create->add_subcommand("account", localized("Create a new account on the blockchain"), false); - createAccount->add_option("creator", creator, localized("The name of the account creating the new account"))->required(); - createAccount->add_option("name", account_name, localized("The name of the new account"))->required(); - createAccount->add_option("OwnerKey", owner_key_str, localized("The owner public key for the new account"))->required(); - createAccount->add_option("ActiveKey", active_key_str, localized("The active public key for the new account"))->required(); - add_standard_transaction_options(createAccount, "creator@active"); - createAccount->set_callback([&] { - public_key_type owner_key, active_key; - try { - owner_key = public_key_type(owner_key_str); - } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid owner public key: ${public_key}", ("public_key", owner_key_str)) - try { - active_key = public_key_type(active_key_str); - } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid active public key: ${public_key}", ("public_key", active_key_str)) - send_actions({create_newaccount(creator, account_name, owner_key, active_key)}); - }); + auto createAccount = create_account_subcommand( create, true /*simple*/ ); // Get subcommand auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false); @@ -937,13 +1357,11 @@ int main( int argc, char** argv ) { // get account string accountName; + bool print_json; auto getAccount = get->add_subcommand("account", localized("Retrieve an account from the blockchain"), false); getAccount->add_option("name", accountName, localized("The name of the account to retrieve"))->required(); - getAccount->set_callback([&] { - std::cout << fc::json::to_pretty_string(call(get_account_func, - fc::mutable_variant_object("account_name", accountName))) - << std::endl; - }); + getAccount->add_flag("--json,-j", print_json, localized("Output in JSON format") ); + getAccount->set_callback([&]() { get_account(accountName, print_json); }); // get code string codeFilename; @@ -1071,16 +1489,109 @@ int main( int argc, char** argv ) { getTransaction->set_callback([&] { transaction_id_type transaction_id; try { + while( transaction_id_str.size() < 64 ) transaction_id_str += "0"; transaction_id = transaction_id_type(transaction_id_str); } EOS_RETHROW_EXCEPTIONS(transaction_id_type_exception, "Invalid transaction ID: ${transaction_id}", ("transaction_id", transaction_id_str)) - auto arg= fc::mutable_variant_object( "transaction_id", transaction_id); + auto arg= fc::mutable_variant_object( "id", transaction_id); std::cout << fc::json::to_pretty_string(call(get_transaction_func, arg)) << std::endl; }); - // get transactions + // get actions + string account_name; string skip_seq_str; string num_seq_str; bool printjson = false; + bool fullact = false; + bool prettyact = false; + bool printconsole = false; + + int32_t pos_seq = -1; + int32_t offset = -20; + auto getActions = get->add_subcommand("actions", localized("Retrieve all actions with specific account name referenced in authorization or receiver"), false); + getActions->add_option("account_name", account_name, localized("name of account to query on"))->required(); + getActions->add_option("pos", pos_seq, localized("sequence number of action for this account, -1 for last")); + getActions->add_option("offset", offset, localized("get actions [pos,pos+offset] for positive offset or [pos-offset,pos) for negative offset")); + getActions->add_flag("--json,-j", printjson, localized("print full json")); + getActions->add_flag("--full", fullact, localized("don't truncate action json")); + getActions->add_flag("--pretty", prettyact, localized("pretty print full action json ")); + getActions->add_flag("--console", printconsole, localized("print console output generated by action ")); + getActions->set_callback([&] { + fc::mutable_variant_object arg; + arg( "account_name", account_name ); + arg( "pos", pos_seq ); + arg( "offset", offset); + + auto result = call(get_actions_func, arg); + + + if( printjson ) { + std::cout << fc::json::to_pretty_string(result) << std::endl; + } else { + auto& traces = result["actions"].get_array(); + uint32_t lib = result["last_irreversible_block"].as_uint64(); + + + cout << "#" << setw(5) << "seq" << " " << setw( 24 ) << left << "when"<< " " << setw(24) << right << "contract::action" << " => " << setw(13) << left << "receiver" << " " << setw(11) << left << "trx id..." << " args\n"; + cout << "================================================================================================================\n"; + for( const auto& trace: traces ) { + std::stringstream out; + if( trace["block_num"].as_uint64() <= lib ) + out << "#"; + else + out << "?"; + + out << setw(5) << trace["account_action_seq"].as_uint64() <<" "; + out << setw(24) << trace["block_time"].as_string() <<" "; + + const auto& at = trace["action_trace"].get_object(); + + auto id = at["trx_id"].as_string(); + const auto& receipt = at["receipt"]; + auto receiver = receipt["receiver"].as_string(); + const auto& act = at["act"].get_object(); + auto code = act["account"].as_string(); + auto func = act["name"].as_string(); + string args; + if( prettyact ) { + args = fc::json::to_pretty_string( act["data"] ); + } + else { + args = fc::json::to_string( act["data"] ); + if( !fullact ) { + args = args.substr(0,60) + "..."; + } + } + out << std::setw(24) << std::right<< (code +"::" + func) << " => " << left << std::setw(13) << receiver; + + out << " " << setw(11) << (id.substr(0,8) + "..."); + + if( fullact || prettyact ) out << "\n"; + else out << " "; + + out << args ;//<< "\n"; + + if( trace["block_num"].as_uint64() <= lib ) { + dlog( "\r${m}", ("m",out.str()) ); + } else { + wlog( "\r${m}", ("m",out.str()) ); + } + if( printconsole ) { + auto console = at["console"].as_string(); + if( console.size() ) { + stringstream out; + std::stringstream ss(console); + string line; + std::getline( ss, line ); + out << ">> " << line << "\n"; + cerr << out.str(); //ilog( "\r${m} ", ("m",out.str()) ); + } + } + } + } + }); + + + /* auto getTransactions = get->add_subcommand("transactions", localized("Retrieve all transactions with specific account name referenced in their scope"), false); getTransactions->add_option("account_name", account_name, localized("name of account to query on"))->required(); getTransactions->add_option("skip_seq", skip_seq_str, localized("Number of most recent transactions to skip (0 would start at most recent transaction)")); @@ -1133,6 +1644,7 @@ int main( int argc, char** argv ) { } }); + */ // set subcommand auto setSubcommand = app.add_subcommand("set", localized("Set or update blockchain state")); @@ -1172,7 +1684,7 @@ int main( int argc, char** argv ) { { abiPath = (cpath / (cpath.filename().generic_string()+".abi")).generic_string(); } - + std::cout << localized(("Reading WAST/WASM from " + wastPath + "...").c_str()) << std::endl; fc::read_file_contents(wastPath, wast); FC_ASSERT( !wast.empty(), "no wast file found ${f}", ("f", wastPath) ); @@ -1193,7 +1705,7 @@ int main( int argc, char** argv ) { FC_ASSERT( fc::exists( abiPath ), "no abi file found ${f}", ("f", abiPath) ); try { - actions.emplace_back( create_setabi(account, fc::json::from_file(abiPath).as()) ); + actions.emplace_back( create_setabi(account, fc::json::from_file(abiPath).as()) ); } EOS_RETHROW_EXCEPTIONS(abi_type_exception, "Fail to parse ABI JSON") std::cout << localized("Publishing contract...") << std::endl; @@ -1220,26 +1732,28 @@ int main( int argc, char** argv ) { auto setActionPermission = set_action_permission_subcommand(setAction); // Transfer subcommand + string con = "eosio.token"; string sender; string recipient; - uint64_t amount; + string amount; string memo; auto transfer = app.add_subcommand("transfer", localized("Transfer EOS from account to account"), false); transfer->add_option("sender", sender, localized("The account sending EOS"))->required(); transfer->add_option("recipient", recipient, localized("The account receiving EOS"))->required(); transfer->add_option("amount", amount, localized("The amount of EOS to send"))->required(); transfer->add_option("memo", memo, localized("The memo for the transfer")); + transfer->add_option("--contract,-c", con, localized("The contract which controls the token")); add_standard_transaction_options(transfer, "sender@active"); transfer->set_callback([&] { signed_transaction trx; if (tx_force_unique && memo.size() == 0) { // use the memo to add a nonce - memo = generate_nonce_value(); + memo = generate_nonce_string(); tx_force_unique = false; } - send_actions({create_transfer(sender, recipient, amount, memo)}); + send_actions({create_transfer(con,sender, recipient, to_asset(amount), memo)}); }); // Net subcommand @@ -1283,6 +1797,9 @@ int main( int argc, char** argv ) { auto createWallet = wallet->add_subcommand("create", localized("Create a new wallet locally"), false); createWallet->add_option("-n,--name", wallet_name, localized("The name of the new wallet"), true); createWallet->set_callback([&wallet_name] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + const auto& v = call(wallet_url, wallet_create, wallet_name); std::cout << localized("Creating wallet: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; std::cout << localized("Save password to use in the future to unlock this wallet.") << std::endl; @@ -1294,6 +1811,9 @@ int main( int argc, char** argv ) { auto openWallet = wallet->add_subcommand("open", localized("Open an existing wallet"), false); openWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to open")); openWallet->set_callback([&wallet_name] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + call(wallet_url, wallet_open, wallet_name); std::cout << localized("Opened: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; }); @@ -1302,6 +1822,9 @@ int main( int argc, char** argv ) { auto lockWallet = wallet->add_subcommand("lock", localized("Lock wallet"), false); lockWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to lock")); lockWallet->set_callback([&wallet_name] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + call(wallet_url, wallet_lock, wallet_name); std::cout << localized("Locked: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; }); @@ -1309,6 +1832,9 @@ int main( int argc, char** argv ) { // lock all wallets auto locakAllWallets = wallet->add_subcommand("lock_all", localized("Lock all unlocked wallets"), false); locakAllWallets->set_callback([] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + call(wallet_url, wallet_lock_all); std::cout << localized("Locked All Wallets") << std::endl; }); @@ -1325,6 +1851,8 @@ int main( int argc, char** argv ) { std::getline( std::cin, wallet_pw, '\n' ); fc::set_console_echo(true); } + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)}; @@ -1354,6 +1882,9 @@ int main( int argc, char** argv ) { // list wallets auto listWallet = wallet->add_subcommand("list", localized("List opened wallets, * = unlocked"), false); listWallet->set_callback([] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + std::cout << localized("Wallets:") << std::endl; const auto& v = call(wallet_url, wallet_list); std::cout << fc::json::to_pretty_string(v) << std::endl; @@ -1362,10 +1893,26 @@ int main( int argc, char** argv ) { // list keys auto listKeys = wallet->add_subcommand("keys", localized("List of private keys from all unlocked wallets in wif format."), false); listKeys->set_callback([] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + const auto& v = call(wallet_url, wallet_list_keys); std::cout << fc::json::to_pretty_string(v) << std::endl; }); + auto stopKeosd = wallet->add_subcommand("stop", localized("Stop keosd (doesn't work with nodeos)."), false); + stopKeosd->set_callback([] { + // wait for keosd to come up + try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); + + const auto& v = call(wallet_url, keosd_stop); + if ( !v.is_object() || v.get_object().size() != 0 ) { //on success keosd responds with empty object + std::cerr << fc::json::to_pretty_string(v) << std::endl; + } else { + std::cout << "OK" << std::endl; + } + }); + // sign subcommand string trx_json_to_sign; string str_private_key; @@ -1537,7 +2084,7 @@ int main( int argc, char** argv ) { if (!proposer.empty()) { accountPermissions = vector{{proposer, config::active_name}}; } else { - EOS_THROW(tx_missing_auth, "Authority is not provided (either by multisig parameter or -p)"); + EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (proposer.empty()) { @@ -1547,11 +2094,10 @@ int main( int argc, char** argv ) { transaction trx; trx.expiration = fc::time_point_sec( fc::time_point::now() + fc::hours(proposal_expiration_hours) ); - trx.region = 0; trx.ref_block_num = 0; trx.ref_block_prefix = 0; trx.max_net_usage_words = 0; - trx.max_kcpu_usage = 0; + trx.max_cpu_usage_ms = 0; trx.delay_sec = 0; trx.actions = { chain::action(trxperm, name(proposed_contract), name(proposed_action), proposed_trx_serialized) }; @@ -1571,14 +2117,14 @@ int main( int argc, char** argv ) { }); //resolver for ABI serializer to decode actions in proposed transaction in multisig contract - auto resolver = [](const name& code) -> optional { + auto resolver = [](const name& code) -> optional { auto result = call(get_code_func, fc::mutable_variant_object("account_name", code.to_string())); if (result["abi"].is_object()) { //std::cout << "ABI: " << fc::json::to_pretty_string(result) << std::endl; - return optional(abi_serializer(result["abi"].as())); + return optional(abi_serializer(result["abi"].as())); } else { std::cerr << "ABI for contract " << code.to_string() << " not found. Action data will be shown in hex only." << std::endl; - return optional(); + return optional(); } }; @@ -1671,7 +2217,7 @@ int main( int argc, char** argv ) { if (!canceler.empty()) { accountPermissions = vector{{canceler, config::active_name}}; } else { - EOS_THROW(tx_missing_auth, "Authority is not provided (either by multisig parameter or -p)"); + EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (canceler.empty()) { @@ -1703,7 +2249,7 @@ int main( int argc, char** argv ) { if (!executer.empty()) { accountPermissions = vector{{executer, config::active_name}}; } else { - EOS_THROW(tx_missing_auth, "Authority is not provided (either by multisig parameter or -p)"); + EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (executer.empty()) { @@ -1728,6 +2274,7 @@ int main( int argc, char** argv ) { auto system = app.add_subcommand("system", localized("Send eosio.system contract action to the blockchain."), false); system->require_subcommand(); + auto createAccountSystem = create_account_subcommand( system, false /*simple*/ ); auto registerProducer = register_producer_subcommand(system); auto unregisterProducer = unregister_producer_subcommand(system); @@ -1736,17 +2283,19 @@ int main( int argc, char** argv ) { auto voteProxy = vote_producer_proxy_subcommand(voteProducer); auto voteProducers = vote_producers_subcommand(voteProducer); + auto listProducers = list_producers_subcommand(system); + auto delegateBandWidth = delegate_bandwidth_subcommand(system); auto undelegateBandWidth = undelegate_bandwidth_subcommand(system); + auto biyram = buyram_subcommand(system); + auto sellram = sellram_subcommand(system); + auto claimRewards = claimrewards_subcommand(system); auto regProxy = regproxy_subcommand(system); auto unregProxy = unregproxy_subcommand(system); - auto postRecovery = postrecovery_subcommand(system); - auto vetoRecovery = vetorecovery_subcommand(system); - auto cancelDelay = canceldelay_subcommand(system); try { diff --git a/programs/eosio-abigen/main.cpp b/programs/eosio-abigen/main.cpp index 74cb2217f44..b84b4771863 100644 --- a/programs/eosio-abigen/main.cpp +++ b/programs/eosio-abigen/main.cpp @@ -4,7 +4,7 @@ #include using namespace eosio; -using namespace eosio::chain::contracts; +using namespace eosio::chain; using mvo = fc::mutable_variant_object; diff --git a/programs/eosio-launcher/main.cpp b/programs/eosio-launcher/main.cpp index 3236590bf20..56631940199 100644 --- a/programs/eosio-launcher/main.cpp +++ b/programs/eosio-launcher/main.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include "config.hpp" @@ -291,8 +291,7 @@ struct prodkey_def { }; struct producer_set_def { - uint32_t version; - vector producers; + vector schedule; }; struct server_name_def { @@ -745,7 +744,6 @@ launcher_def::bind_nodes () { int non_bios = prod_nodes - 1; int per_node = producers / non_bios; int extra = producers % non_bios; - producer_set.version = 1; unsigned int i = 0; for (auto &h : bindings) { for (auto &inst : h.instances) { @@ -761,7 +759,7 @@ launcher_def::bind_nodes () { if (is_bios) { string prodname = "eosio"; node.producers.push_back(prodname); - producer_set.producers.push_back({prodname,pubkey}); + producer_set.schedule.push_back({prodname,pubkey}); } else { if (i < non_bios) { @@ -775,7 +773,7 @@ launcher_def::bind_nodes () { while (count--) { string prodname = pname+ext; node.producers.push_back(prodname); - producer_set.producers.push_back({prodname,pubkey}); + producer_set.schedule.push_back({prodname,pubkey}); ext += non_bios; } } @@ -1011,7 +1009,7 @@ launcher_def::write_config_file (tn_node_def &node) { cfg << "plugin = eosio::mongo_db_plugin\n"; } cfg << "plugin = eosio::chain_api_plugin\n" - << "plugin = eosio::account_history_api_plugin\n"; + << "plugin = eosio::history_api_plugin\n"; cfg.close(); } @@ -1042,6 +1040,11 @@ launcher_def::write_logging_config_file(tn_node_def &node) { ( "host", instance.name ) ) ); log_config.loggers.front().appenders.push_back("net"); + fc::logger_config p2p ("net_plugin_impl"); + p2p.level=fc::log_level::debug; + p2p.appenders.push_back ("stderr"); + p2p.appenders.push_back ("net"); + log_config.loggers.emplace_back(p2p); } auto str = fc::json::to_pretty_string( log_config, fc::json::stringify_large_ints_and_doubles ); @@ -1055,7 +1058,7 @@ launcher_def::init_genesis () { bfs::ifstream src(genesis_path); if (!src.good()) { cout << "generating default genesis file " << genesis_path << endl; - eosio::chain::contracts::genesis_state_type default_genesis; + eosio::chain::genesis_state default_genesis; fc::json::save_to_file( default_genesis, genesis_path, true ); src.open(genesis_path); } @@ -1100,11 +1103,10 @@ launcher_def::write_setprods_file() { exit (9); } producer_set_def no_bios; - for (auto &p : producer_set.producers) { + for (auto &p : producer_set.schedule) { if (p.producer_name != "eosio") - no_bios.producers.push_back(p); + no_bios.schedule.push_back(p); } - no_bios.version = 1; auto str = fc::json::to_pretty_string( no_bios, fc::json::stringify_large_ints_and_doubles ); psfile.write( str.c_str(), str.size() ); psfile.close(); @@ -1142,7 +1144,7 @@ launcher_def::write_bios_boot () { } } else if (key == "cacmd") { - for (auto &p : producer_set.producers) { + for (auto &p : producer_set.schedule) { if (p.producer_name == "eosio") { continue; } @@ -1290,12 +1292,11 @@ void launcher_def::make_custom () { bfs::path source = shape; fc::json::from_file(source).as(network); - producer_set.version = 1; for (auto &h : bindings) { for (auto &inst : h.instances) { tn_node_def *node = &network.nodes[inst.name]; for (auto &p : node->producers) { - producer_set.producers.push_back({p,node->keys[0].get_public_key()}); + producer_set.schedule.push_back({p,node->keys[0].get_public_key()}); } node->instance = &inst; inst.node = node; @@ -1871,7 +1872,7 @@ FC_REFLECT( prodkey_def, (producer_name)(block_signing_key)) FC_REFLECT( producer_set_def, - (version)(producers)) + (schedule)) FC_REFLECT( host_def, (genesis)(ssh_identity)(ssh_args)(eosio_home) diff --git a/programs/exchange-tutorial-python/README.md b/programs/exchange-tutorial-python/README.md new file mode 100644 index 00000000000..d8364ef5dd1 --- /dev/null +++ b/programs/exchange-tutorial-python/README.md @@ -0,0 +1,36 @@ +The following steps must be taken for the example script to work. + +0. Create wallet +0. Create account for eosio.token +0. Create account for scott +0. Create account for exchange +0. Set token contract on eosio.token +0. Create EOS token +0. Issue initial tokens to scott + +**Note**: +Deleting the `transactions.txt` file will prevent replay from working. + + +### Create wallet +`cleos wallet create` + +### Create account steps +`cleos create key` + +`cleos create key` + +`cleos wallet import ` + +`cleos wallet import ` + +`cleos create account eosio ` + +### Set contract steps +`cleos set contract eosio.token /contracts/eosio.token -p eosio.token@active` + +### Create EOS token steps +`cleos push action eosio.token create '{"issuer": "eosio.token", "maximum_supply": "100000.0000 EOS", "can_freeze": 1, "can_recall": 1, "can_whitelist": 1}' -p eosio.token@active` + +### Issue token steps +`cleos push action eosio.token issue '{"to": "scott", "quantity": "900.0000 EOS", "memo": "testing"}' -p eosio.token@active` diff --git a/programs/exchange-tutorial-python/exchange_tutorial.py b/programs/exchange-tutorial-python/exchange_tutorial.py new file mode 100644 index 00000000000..c8a5bf6a4ad --- /dev/null +++ b/programs/exchange-tutorial-python/exchange_tutorial.py @@ -0,0 +1,187 @@ +import json +import pprint +import os +import sys +import subprocess +import time + +from subprocess import PIPE + +# This key would be different for each user. +KEY_TO_INTERNAL_ACCOUNT='12345' +DEMO_USER='scott' + +def main(): + try: + command = sys.argv[1] + if command == 'monitor': + setup() + while True: + monitor_exchange() + time.sleep(.1) + elif command == 'transfer': + if len(sys.argv) == 4: + transfer(sys.argv[2], sys.argv[3]) + else: + print('Transfer must be called by `python exchange_tutorial.py transfer {} 1.0000`'.format(DEMO_USER)) + except subprocess.CalledProcessError as e: + print(e) + print(str(e.stderr, 'utf-8')) + +def monitor_exchange(): + action_num = get_last_action() + 1 + results = cleos('get actions exchange {} 0 -j'.format(action_num)) + + results = json.loads(results.stdout) + action_list = results['actions'] + if len(action_list) == 0: + return + + action = action_list[0] + last_irreversible_block = results['last_irreversible_block'] + to = action['action_trace']['act']['data']['to'] + block_num = action['block_num'] + + if is_irreversible(block_num, last_irreversible_block): + update_balance(action, to) + set_last_action(action_num) + +def update_balance(action, to): + current_balance = get_balance() + new_balance = current_balance + transfer_quantity = action['action_trace']['act']['data']['quantity'].split()[0] + transfer_quantity = float(transfer_quantity) + + if to == 'exchange': + if is_valid_deposit(action): + new_balance = current_balance + transfer_quantity + set_balance(new_balance) + elif is_valid_withdrawal(action): + new_balance = current_balance - transfer_quantity + set_balance(new_balance) + + +def transfer(to, quantity): + if quantity[:-4] != ' EOS': + quantity += ' EOS' + results = cleos('transfer exchange {} "{}" {} -j'.format(to, quantity, KEY_TO_INTERNAL_ACCOUNT)) + transaction_info = json.loads(str(results.stdout, 'utf-8')) + transaction_id = transaction_info['transaction_id'] + + transaction_status = transaction_info['processed']['receipt']['status'] + if transaction_status == 'hard_fail': + print('Transaction failed.') + return + + add_transactions(transaction_id) + print('Initiated transfer of {} to {}. Transaction id is {}.'.format(quantity, to, transaction_id)) + + +def is_irreversible(block_num, last_irreversible_block): + return block_num <= last_irreversible_block + +def is_valid_deposit(action): + account = action['action_trace']['act']['account'] + action_name = action['action_trace']['act']['name'] + memo = action['action_trace']['act']['data']['memo'] + receiver = action['action_trace']['receipt']['receiver'] + token = action['action_trace']['act']['data']['quantity'].split()[1] + + valid_user = action['action_trace']['act']['data']['to'] == 'exchange' + from_user = action['action_trace']['act']['data']['from'] + + # Filter only to actions that notify the exchange account. + if receiver != 'exchange': + return False + + if (account == 'eosio.token' and + action_name == 'transfer' and + memo == KEY_TO_INTERNAL_ACCOUNT and + valid_user and + from_user == DEMO_USER and + token == 'EOS'): + return True + + print('Invalid deposit') + return False + +def is_valid_withdrawal(action): + account = action['action_trace']['act']['account'] + action_name = action['action_trace']['act']['name'] + memo = action['action_trace']['act']['data']['memo'] + receiver = action['action_trace']['receipt']['receiver'] + token = action['action_trace']['act']['data']['quantity'].split()[1] + + transaction_id = action['action_trace']['trx_id'] + + valid_user = action['action_trace']['act']['data']['from'] == 'exchange' + to_user = action['action_trace']['act']['data']['to'] + + # Filter only to actions that notify the exchange account. + if receiver != 'exchange': + return False + + if (account == 'eosio.token' and + action_name == 'transfer' and + memo == KEY_TO_INTERNAL_ACCOUNT and + valid_user and + to_user == DEMO_USER and + transaction_id in get_transactions() and + token == 'EOS'): + return True + + print('Invalid withdrawal') + return False + +def cleos(args): + if isinstance(args, list): + command = ['./cleos'] + command.extend(args) + else: + command = './cleos ' + args + + results = subprocess.run(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, check=True) + return results + +def setup(): + if not os.path.exists('last_action.txt'): + set_last_action(-1) + if not os.path.exists('balance.txt'): + set_balance(0) + if not os.path.exists('transactions.txt'): + with open('transactions.txt', 'w') as f: + f.write(json.dumps({"transactions": []})) + +def get_transactions(): + with open('transactions.txt', 'r') as f: + transactions = json.load(f) + return set(transactions['transactions']) + +def add_transactions(transaction_id): + transactions = get_transactions() + transactions.add(transaction_id) + with open('transactions.txt', 'w') as f: + transactions = json.dumps({'transactions': list(transactions)}) + f.write(transactions) + +def get_last_action(): + with open('last_action.txt', 'r') as f: + last_action = int(f.read()) + return last_action + +def set_last_action(action): + with open('last_action.txt', 'w') as f: + f.write(str(action)) + +def get_balance(): + with open('balance.txt', 'r') as f: + balance = float(f.read()) + return balance + +def set_balance(balance): + with open('balance.txt', 'w') as f: + f.write(str(balance)) + print("{}'s balance is: {}".format(DEMO_USER, balance)) + +if __name__ == '__main__': + main() diff --git a/programs/keosd/main.cpp b/programs/keosd/main.cpp index a115f04f344..ce1588fccd6 100644 --- a/programs/keosd/main.cpp +++ b/programs/keosd/main.cpp @@ -42,6 +42,8 @@ int main(int argc, char** argv) app().register_plugin(); if(!app().initialize(argc, argv)) return -1; + auto& http = app().get_plugin(); + http.add_handler("/v1/keosd/stop", [](string, string, url_response_callback cb) { cb(200, "{}"); std::raise(SIGTERM); } ); app().startup(); app().exec(); } catch (const fc::exception& e) { diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index b978c49cfd9..77406f8e373 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -45,19 +45,21 @@ endif() target_link_libraries( nodeos PRIVATE appbase - PRIVATE -Wl,${whole_archive_flag} account_history_api_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${whole_archive_flag} history_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${whole_archive_flag} history_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} chain_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} wallet_api_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${whole_archive_flag} net_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_api_plugin -Wl,${no_whole_archive_flag} - PRIVATE -Wl,${whole_archive_flag} faucet_testnet_plugin -Wl,${no_whole_archive_flag} +# PRIVATE -Wl,${whole_archive_flag} faucet_testnet_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} txn_test_gen_plugin -Wl,${no_whole_archive_flag} - PRIVATE account_history_plugin producer_plugin chain_plugin net_plugin http_plugin + PRIVATE chain_plugin http_plugin producer_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -if(BUILD_MONGO_DB_PLUGIN) - target_link_libraries( nodeos - PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) -endif() +#if(BUILD_MONGO_DB_PLUGIN) +# target_link_libraries( nodeos +# PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) +#endif() install( TARGETS nodeos diff --git a/programs/nodeos/main.cpp b/programs/nodeos/main.cpp index 0e30fa3adca..b474d428fb7 100644 --- a/programs/nodeos/main.cpp +++ b/programs/nodeos/main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -82,7 +83,9 @@ int main(int argc, char** argv) { try { app().set_version(eosio::nodeos::config::version); - auto root = fc::app_path(); + app().register_plugin(); + + auto root = fc::app_path(); app().set_default_data_dir(root / "eosio/nodeos/data" ); app().set_default_config_dir(root / "eosio/nodeos/config" ); if(!app().initialize(argc, argv)) diff --git a/scripts/eosio_build_amazon.sh b/scripts/eosio_build_amazon.sh index 9c6ae1e6635..b7d51acc608 100644 --- a/scripts/eosio_build_amazon.sh +++ b/scripts/eosio_build_amazon.sh @@ -1,6 +1,6 @@ OS_VER=$( grep VERSION_ID /etc/os-release | cut -d'=' -f2 | sed 's/[^0-9\.]//gI' | cut -d'.' -f1 ) - MEM_MEG=$( free -m | grep Mem | tr -s ' ' | cut -d\ -f2 ) + MEM_MEG=$( free -m | sed -n 2p | tr -s ' ' | cut -d\ -f2 ) CPU_SPEED=$( lscpu | grep "MHz" | tr -s ' ' | cut -d\ -f3 | cut -d'.' -f1 ) CPU_CORE=$( lscpu | grep "^CPU(s)" | tr -s ' ' | cut -d\ -f2 ) MEM_GIG=$(( ((MEM_MEG / 1000) / 2) )) diff --git a/scripts/eosio_build_centos.sh b/scripts/eosio_build_centos.sh index 55f9a4b0630..6742bbc8407 100644 --- a/scripts/eosio_build_centos.sh +++ b/scripts/eosio_build_centos.sh @@ -1,7 +1,7 @@ OS_VER=$( cat /etc/os-release | grep VERSION_ID | cut -d'=' -f2 | sed 's/[^0-9\.]//gI' \ | cut -d'.' -f1 ) - MEM_MEG=$( free -m | grep Mem | tr -s ' ' | cut -d\ -f2 ) + MEM_MEG=$( free -m | sed -n 2p | tr -s ' ' | cut -d\ -f2 ) CPU_SPEED=$( lscpu | grep "MHz" | tr -s ' ' | cut -d\ -f3 | cut -d'.' -f1 ) CPU_CORE=$( lscpu | grep "^CPU(s)" | tr -s ' ' | cut -d\ -f2 ) MEM_GIG=$(( (($MEM_MEG / 1000) / 2) )) diff --git a/scripts/eosio_build_fedora.sh b/scripts/eosio_build_fedora.sh index 928acbe4884..a3e86da277f 100644 --- a/scripts/eosio_build_fedora.sh +++ b/scripts/eosio_build_fedora.sh @@ -1,6 +1,6 @@ OS_VER=$( cat /etc/os-release | grep VERSION_ID | cut -d'=' -f2 | sed 's/[^0-9\.]//gI' ) - MEM_MEG=$( free -m | grep Mem | tr -s ' ' | cut -d\ -f2 ) + MEM_MEG=$( free -m | sed -n 2p | tr -s ' ' | cut -d\ -f2 ) CPU_SPEED=$( lscpu | grep "MHz" | tr -s ' ' | cut -d\ -f3 | cut -d'.' -f1 ) CPU_CORE=$( lscpu | grep "^CPU(s)" | tr -s ' ' | cut -d\ -f2 ) MEM_GIG=$(( (($MEM_MEG / 1000) / 2) )) diff --git a/scripts/eosio_build_ubuntu.sh b/scripts/eosio_build_ubuntu.sh index 3f3012bc641..4ba77489f7a 100644 --- a/scripts/eosio_build_ubuntu.sh +++ b/scripts/eosio_build_ubuntu.sh @@ -2,7 +2,7 @@ OS_MAJ=$(echo "${OS_VER}" | cut -d'.' -f1) OS_MIN=$(echo "${OS_VER}" | cut -d'.' -f2) - MEM_MEG=$( free -m | grep Mem | tr -s ' ' | cut -d\ -f2 || cut -d' ' -f2 ) + MEM_MEG=$( free -m | sed -n 2p | tr -s ' ' | cut -d\ -f2 || cut -d' ' -f2 ) CPU_SPEED=$( lscpu | grep -m1 "MHz" | tr -s ' ' | cut -d\ -f3 || cut -d' ' -f3 | cut -d'.' -f1 ) CPU_CORE=$( lscpu | grep "^CPU(s)" | tr -s ' ' | cut -d\ -f2 || cut -d' ' -f2 ) diff --git a/testnet.template b/testnet.template index 0340c1f258e..4d147c63d9b 100644 --- a/testnet.template +++ b/testnet.template @@ -13,8 +13,8 @@ if [ -z "$biosport" ]; then fi wddir=eosio-ignition-wd -wdport=8899 -wdhost=127.0.0.1 +wdaddr=localhost:8899 +wdurl=http://$wdaddr # Manual deployers, add a line below this block that looks like: # bioshost=$BIOS_HOSTNAME # biosport=$BIOS_HTTP_PORT @@ -35,7 +35,7 @@ mkdir $wddir step=1 echo Initializing ignition sequence at $(date) | tee $logfile -echo "http-server-address = 127.0.0.1:$wdport" > $wddir/config.ini +echo "http-server-address = $wdaddr" > $wddir/config.ini programs/keosd/keosd --config-dir $wddir --data-dir $wddir 2> $wddir/wdlog.txt & echo $$ > ignition_wallet.pid @@ -44,9 +44,9 @@ sleep 1 ecmd () { echo ===== Start: $step ============ >> $logfile - echo executing: cleos --wallet-port $wdport -p $biosport -H $bioshost $* | tee -a $logfile + echo executing: cleos --wallet-url $wdurl --url http://$bioshost:$biosport $* | tee -a $logfile echo ----------------------- >> $logfile - programs/cleos/cleos --wallet-port $wdport --wallet-host $wdhost -p $biosport -H $bioshost $* >> $logfile 2>&1 + programs/cleos/cleos --wallet-url $wdurl --url http://$bioshost:$biosport $* >> $logfile 2>&1 echo ==== End: $step ============== >> $logfile step=$(($step + 1)) } @@ -89,18 +89,18 @@ ecmd set contract eosio contracts/eosio.bios contracts/eosio.bios/eosio.bios.was #the setprods.json argument cannot pass through the function call due to embedded quotes echo ===== Start: $step ============ >> $logfile -echo executing: cleos --wallet-port $wdport -p $biosport -H $bioshost push action eosio setprods setprods.json -p eosio@active | tee -a $logfile +echo executing: cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio setprods setprods.json -p eosio@active | tee -a $logfile echo ----------------------- >> $logfile -programs/cleos/cleos --wallet-port $wdport --wallet-host $wdhost -p $biosport -H $bioshost push action eosio setprods setprods.json -p eosio@active >> $logfile 2>&1 +programs/cleos/cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio setprods setprods.json -p eosio@active >> $logfile 2>&1 echo ==== End: $step ============== >> $logfile step=$(($step + 1)) ecmd set contract eosio contracts/eosio.system contracts/eosio.system/eosio.system.wast contracts/eosio.system/eosio.system.abi echo ===== Start: $step ============ >> $logfile -echo executing: cleos --wallet-port $wdport -p $biosport -H $bioshost push action eosio issue '{"to":"eosio","quantity":"1000000000.0000 EOS","memo":"init"}' -p eosio@active | tee -a $logfile +echo executing: cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio issue '{"to":"eosio","quantity":"1000000000.0000 EOS","memo":"init"}' -p eosio@active | tee -a $logfile echo ----------------------- >> $logfile -programs/cleos/cleos --wallet-port $wdport --wallet-host $wdhost -p $biosport -H $bioshost push action eosio issue '{"to":"eosio","quantity":"1000000000.0000 EOS","memo":"init"}' -p eosio@active >> $logfile 2>&1 +programs/cleos/cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio issue '{"to":"eosio","quantity":"1000000000.0000 EOS","memo":"init"}' -p eosio@active >> $logfile 2>&1 echo ==== End: $step ============== >> $logfile step=$(($step + 1)) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6c5bd0c7d9d..feb7cc98443 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,3 @@ -#file(GLOB COMMON_SOURCES "common/*.cpp") find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) @@ -12,23 +11,17 @@ link_directories(${LLVM_LIBRARY_DIR}) set( CMAKE_CXX_STANDARD 14 ) -#include_directories("${CMAKE_BINARY_DIR}/tests/api_tests") -include_directories("${CMAKE_BINARY_DIR}/contracts") -include_directories("${CMAKE_SOURCE_DIR}/contracts") include_directories("${CMAKE_SOURCE_DIR}/plugins/wallet_plugin/include") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/config.hpp ESCAPE_QUOTES) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/config.hpp ESCAPE_QUOTES) -file(GLOB UNIT_TESTS "chain_tests/*.cpp" "api_tests/*.cpp" "tests/abi_tests.cpp" "tests/database_tests.cpp" "tests/misc_tests.cpp" "wasm_tests/*.cpp" "tests/message_buffer_tests.cpp" "tests/special_accounts_tests.cpp" "tests/wallet_tests.cpp" "library_tests/*/*.cpp") +file(GLOB UNIT_TESTS "wallet_tests.cpp") -add_executable( chain_test ${UNIT_TESTS} ${WASM_UNIT_TESTS} common/main.cpp) -target_link_libraries( chain_test eosio_testing eosio_chain chainbase eos_utilities chain_plugin wallet_plugin abi_generator fc ${PLATFORM_SPECIFIC_LIBS} ) +add_executable( plugin_test ${UNIT_TESTS} ${WASM_UNIT_TESTS} main.cpp) +target_link_libraries( plugin_test eosio_testing eosio_chain chainbase eos_utilities chain_plugin wallet_plugin abi_generator fc ${PLATFORM_SPECIFIC_LIBS} ) -target_include_directories( chain_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CMAKE_CURRENT_BINARY_DIR}/tests/contracts ) -target_include_directories( chain_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/wasm_tests ) -target_include_directories( chain_test PUBLIC ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include ) -target_include_directories( chain_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) -add_dependencies(chain_test asserter test_api test_api_mem test_api_db test_api_multi_index exchange proxy identity identity_test stltest infinite eosio.system eosio.token eosio.bios test.inline multi_index_test noop dice eosio.msig) +target_include_directories( plugin_test PUBLIC ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include ) +add_dependencies(plugin_test asserter test_api test_api_mem test_api_db test_api_multi_index exchange proxy identity identity_test stltest infinite eosio.system eosio.token eosio.bios test.inline multi_index_test noop dice eosio.msig) # configure_file(${CMAKE_CURRENT_SOURCE_DIR}/p2p_tests/sync/test.sh ${CMAKE_CURRENT_BINARY_DIR}/p2p_tests/sync/test.sh COPYONLY) @@ -43,16 +36,16 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_test.py ${CMAKE_CURRENT_BI configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_remote_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_remote_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/consensus-validation-malicious-producers.py ${CMAKE_CURRENT_BINARY_DIR}/consensus-validation-malicious-producers.py COPYONLY) -#Manually run chain_test for all supported runtimes -#To run chain_test with all log from blockchain displayed, put --verbose after --, i.e. chain_test -- --verbose -add_test(NAME chain_test_binaryen COMMAND chain_test --report_level=detailed --color_output -- --binaryen) -add_test(NAME chain_test_wavm COMMAND chain_test --report_level=detailed --color_output --catch_system_errors=no -- --wavm) -add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME nodeos_run_remote_test COMMAND tests/nodeos_run_remote_test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME p2p_dawn515_test COMMAND tests/p2p_tests/dawn_515/test.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -if(BUILD_MONGO_DB_PLUGIN) - add_test(NAME nodeos_run_test-mongodb COMMAND tests/nodeos_run_test.py --mongodb -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -endif() +#To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose +add_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) + +#add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +#add_test(NAME nodeos_run_remote_test COMMAND tests/nodeos_run_remote_test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# TODO removed on slim: add_test(NAME p2p_dawn515_test COMMAND tests/p2p_tests/dawn_515/test.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +#if(BUILD_MONGO_DB_PLUGIN) +# add_test(NAME nodeos_run_test-mongodb COMMAND tests/nodeos_run_test.py --mongodb -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +#endif() # TODO: Tests removed until working again on master. # TODO: add_test(NAME p2p_sync_test COMMAND tests/p2p_tests/sync/test.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -60,8 +53,8 @@ endif() # TODO: add_test(NAME message_storm COMMAND tests/p2p_tests/sync/test.sh -m -p 21 -n 21 -d 5 -l WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # TODO: add_test(NAME trans_sync_across_mixed_cluster_test COMMAND tests/trans_sync_across_mixed_cluster_test.sh -p 1 -n 2 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_test(NAME distributed-transactions-test COMMAND tests/distributed-transactions-test.py -p 1 -n 4 -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME distributed-transactions-remote-test COMMAND tests/distributed-transactions-remote-test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME restart-scenarios-test_resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +# TODO removed on slim: add_test(NAME distributed-transactions-remote-test COMMAND tests/distributed-transactions-remote-test.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +# TODO removed on slim: add_test(NAME restart-scenarios-test_resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # add_test(NAME restart-scenarios-test_replay COMMAND tests/restart-scenarios-test.py -c replay -p4 -v --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # TODO: add_test(NAME consensus-validation-malicious-producers COMMAND tests/consensus-validation-malicious-producers.py -w 80 --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -82,7 +75,7 @@ if(ENABLE_COVERAGE_TESTING) endif() # NOT GENHTML_PATH # no spaces allowed within tests list - set(ctest_tests 'chain_test_binaryen|chain_test_wavm|p2p_dawn515_test|nodeos_run_test|distributed-transactions-test|restart-scenarios-test_resync') + set(ctest_tests 'plugin_test|p2p_dawn515_test|nodeos_run_test|distributed-transactions-test|restart-scenarios-test_resync') set(ctest_exclude_tests 'nodeos_run_remote_test|nodeos_run_test-mongodb|distributed-transactions-remote-test|restart-scenarios-test_replay') # Setup target diff --git a/tests/chain_tests/block_tests.cpp b/tests/chain_tests/block_tests.cpp index 09d62c022ff..655d7059d24 100644 --- a/tests/chain_tests/block_tests.cpp +++ b/tests/chain_tests/block_tests.cpp @@ -4,7 +4,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; #ifdef NON_VALIDATING_TEST @@ -46,7 +45,7 @@ BOOST_AUTO_TEST_CASE( last_irreversible_update_bug_test ) { try { vector producer_names = {N(inita), N(initb), N(initc), N(initd)}; producers.create_accounts(producer_names); producers.set_producers(producer_names); - + auto block = producers.produce_block(); disconnected.push_block(block); while (true) { @@ -57,7 +56,7 @@ BOOST_AUTO_TEST_CASE( last_irreversible_update_bug_test ) { try { } auto produce_one_block = [&]( auto& t, int i, int offset, signed_block& sb ) { - signed_block new_block = t.control->generate_block( block_timestamp_type{sb.timestamp.slot + offset}, + signed_block new_block = t.control->generate_block( block_timestamp_type{sb.timestamp.slot + offset}, producer_names[i], t.get_private_key( producer_names[i], "active" ) ); new_block.previous = sb.id(); @@ -68,7 +67,7 @@ BOOST_AUTO_TEST_CASE( last_irreversible_update_bug_test ) { try { // lets start at inita BOOST_CHECK_EQUAL( block.block_num(), 48 ); BOOST_CHECK_EQUAL( block.producer, N(inita) ); - + block = produce_one_block( producers, 1, 12, block ); disconnected.push_block(block); BOOST_CHECK_EQUAL( block.producer, N(initb) ); @@ -80,7 +79,7 @@ BOOST_AUTO_TEST_CASE( last_irreversible_update_bug_test ) { try { block = produce_one_block( producers, 3, 12, block ); disconnected.push_block(block); BOOST_CHECK_EQUAL( block.producer, N(initd) ); - + // start here block = produce_one_block( producers, 0, 12, block ); disconnected.push_block(block); @@ -137,7 +136,7 @@ BOOST_AUTO_TEST_CASE( last_irreversible_update_bug_test ) { try { BOOST_CHECK_EQUAL( right_fork.block_num(), 58 ); BOOST_CHECK_EQUAL( right_fork.producer, N(initd) ); BOOST_CHECK_EQUAL( producers.control->last_irreversible_block_num(), 56 ); - + // TODO This should fail after chain_controller refactor, as this bug should not exist BOOST_CHECK_THROW( producers.sync_with( disconnected ), assert_exception ); @@ -192,8 +191,8 @@ BOOST_AUTO_TEST_CASE( push_invalid_block ) { try { signed_block new_block; auto head_time = chain.control->head_block_time(); auto next_time = head_time + fc::microseconds(config::block_interval_us); - uint32_t slot = chain.control->get_slot_at_time( next_time ); - auto sch_pro = chain.control->get_scheduled_producer(slot); + uint32_t slot = chain.control->head_block_state()->get_slot_at_time( next_time ); + auto sch_pro = chain.control->head_block_state()->get_scheduled_producer(slot).producer_name; auto priv_key = chain.get_private_key( sch_pro, "active" ); // On block action @@ -213,7 +212,9 @@ BOOST_AUTO_TEST_CASE( push_invalid_block ) { try { new_block.producer = sch_pro; new_block.block_mroot = chain.control->get_dynamic_global_properties().block_merkle_root.get_root(); vector input_metas; - new_block.sign(priv_key); + + auto schedule = chain.control->active_producer_schedule(); + new_block.sign(priv_key, digest_type::hash(schedule) ); // Create a new empty region new_block.regions.resize(new_block.regions.size() + 1); @@ -570,14 +571,15 @@ BOOST_AUTO_TEST_CASE(missed_blocks) // Second, end the next producer round (since the next round will start new set of producers from the middle) chain.produce_blocks_until_end_of_round(); + const auto& hbs = *chain.control->head_block_state(); - const auto& ref_block_num = chain.control->head_block_num(); + const auto ref_block_num = chain.control->head_block_num(); - account_name skipped_producers[3] = {chain.control->get_scheduled_producer(config::producer_repetitions), - chain.control->get_scheduled_producer(2 * config::producer_repetitions), - chain.control->get_scheduled_producer(3 * config::producer_repetitions)}; - auto next_block_time = static_cast(chain.control->get_slot_time(4 * config::producer_repetitions)); - auto next_producer = chain.control->get_scheduled_producer(4 * config::producer_repetitions); + account_name skipped_producers[3] = {hbs.get_scheduled_producer(config::producer_repetitions).producer_name, + hbs.get_scheduled_producer(2 * config::producer_repetitions).producer_name, + hbs.get_scheduled_producer(3 * config::producer_repetitions).producer_name}; + auto next_block_time = static_cast(hbs.get_slot_time(4 * config::producer_repetitions)); + auto next_producer = hbs.get_scheduled_producer(4 * config::producer_repetitions).producer_name; BOOST_TEST(chain.control->head_block_num() == ref_block_num); const auto& blocks_to_miss = (config::producer_repetitions - 1) + 3 * config::producer_repetitions; @@ -586,11 +588,13 @@ BOOST_AUTO_TEST_CASE(missed_blocks) BOOST_TEST(chain.control->head_block_num() == ref_block_num + 1); BOOST_TEST(static_cast(chain.control->head_block_time()) == static_cast(next_block_time)); BOOST_TEST(chain.control->head_block_producer() == next_producer); - BOOST_TEST(chain.control->get_producer(next_producer).total_missed == 0); + //BOOST_TEST(chain.control->get_producer(next_producer).total_missed == 0); + /* for (auto producer : skipped_producers) { BOOST_TEST(chain.control->get_producer(producer).total_missed == config::producer_repetitions); } + */ BOOST_REQUIRE_EQUAL( chain.validate(), true ); } FC_LOG_AND_RETHROW() } @@ -968,7 +972,7 @@ BOOST_AUTO_TEST_CASE(irrelevant_sig_soft_check) { trx.signatures.clear(); trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() ); trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() ); - BOOST_REQUIRE_THROW(chain.push_transaction( trx ), tx_irrelevant_sig); + BOOST_REQUIRE_THROW(chain.push_transaction( trx ), tx_duplicate_sig); // Sign the transaction properly and push to the block trx.signatures.clear(); @@ -1065,8 +1069,8 @@ BOOST_AUTO_TEST_CASE(block_id_sig_independent) // Create a new block signed_block new_block; auto next_time = chain.control->head_block_time() + fc::microseconds(config::block_interval_us); - uint32_t slot = chain.control->get_slot_at_time( next_time ); - auto sch_pro = chain.control->get_scheduled_producer(slot); + uint32_t slot = chain.control->head_block_state()->get_slot_at_time( next_time ); + auto sch_pro = chain.control->head_block_state()->get_scheduled_producer(slot); // On block action action on_block_act; @@ -1087,12 +1091,14 @@ BOOST_AUTO_TEST_CASE(block_id_sig_independent) new_block.block_mroot = chain.control->get_dynamic_global_properties().block_merkle_root.get_root(); vector input_metas; + auto sch = chain.control->active_producer_schedule(); + // Sign the block with active signature - new_block.sign(chain.get_private_key( sch_pro, "active" )); + new_block.sign(chain.get_private_key( sch_pro, "active" ), digest_type::hash(sch) ); auto block_id_act_sig = new_block.id(); // Sign the block with other signature - new_block.sign(chain.get_private_key( sch_pro, "other" )); + new_block.sign(chain.get_private_key( sch_pro, "other" ), digest_type::hash(sch) ); auto block_id_othr_sig = new_block.id(); // The block id should be independent of the signature @@ -1122,7 +1128,7 @@ BOOST_AUTO_TEST_CASE(get_required_keys) }); chain.set_transaction_headers(trx); - BOOST_REQUIRE_THROW(chain.push_transaction(trx), tx_missing_sigs); + BOOST_REQUIRE_THROW(chain.push_transaction(trx), unsatisfied_authorization); const auto priv_key_not_needed_1 = chain.get_private_key("alice", "blah"); const auto priv_key_not_needed_2 = chain.get_private_key("alice", "owner"); @@ -1150,7 +1156,7 @@ BOOST_AUTO_TEST_CASE(get_required_keys) // transaction_receipt and packed_trx_digest (if the tx is an input tx, which doesn't include implicit/ deferred tx) // Deactivating this test. on_block transaction hash should not be hardcoded as the the work done following onblock action -// can change. As chain controller is being refactored, this test will have to be changed. +// can change. As chain controller is being refactored, this test will have to be changed. #if 0 BOOST_AUTO_TEST_CASE(transaction_mroot) { try { @@ -1218,12 +1224,11 @@ BOOST_AUTO_TEST_CASE(account_ram_limit) { try { trace = chain.create_account(N(acc3), acc1); chain.produce_block(); BOOST_REQUIRE_EQUAL(trace.status, transaction_trace::executed); - + BOOST_REQUIRE_EXCEPTION( - chain.create_account(N(acc4), acc1), - tx_resource_exhausted, - [] (const tx_resource_exhausted &e)->bool { - BOOST_REQUIRE_EQUAL(std::string("transaction exhausted allowed resources"), e.what()); + chain.create_account(N(acc4), acc1), + ram_usage_exceeded, + [] (const ram_usage_exceeded &e)->bool { return true; } ); @@ -1245,14 +1250,14 @@ BOOST_AUTO_TEST_CASE(producer_r1_key) { try { // Add signing key to the tester object, so it can sign with the correct key chain.block_signing_private_keys[producer_r1_pub_key] = producer_r1_priv_key; - + // Wait until the current round ends chain.produce_blocks_until_end_of_round(); // The next set of producers will be producing starting in the middle of next round // This round should not throw any exception BOOST_CHECK_NO_THROW(chain.produce_blocks_until_end_of_round()); - + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/chain_tests/recovery_tests.cpp b/tests/chain_tests/recovery_tests.cpp deleted file mode 100644 index 32c1b9a7866..00000000000 --- a/tests/chain_tests/recovery_tests.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include - -#ifdef NON_VALIDATING_TEST -#define TESTER tester -#else -#define TESTER validating_tester -#endif - -using namespace eosio; -using namespace eosio::chain; -using namespace eosio::chain::contracts; -using namespace eosio::testing; - -auto make_postrecovery(const TESTER &t, account_name account, string role) { - signed_transaction trx; - trx.actions.emplace_back( vector{{account,config::active_name}}, - postrecovery{ - .account = account, - .data = authority(t.get_public_key(account, role)), - .memo = "Test recovery" - } ); - t.set_transaction_headers(trx); - trx.sign(t.get_private_key(account, "active"), chain_id_type()); - return trx; -} - -auto make_vetorecovery(const TESTER &t, account_name account, permission_name vetoperm = N(active), optional signing_key = optional()) { - signed_transaction trx; - trx.actions.emplace_back( vector{{account,vetoperm}}, - vetorecovery{ - .account = account - } ); - t.set_transaction_headers(trx); - if (signing_key) { - trx.sign(*signing_key, chain_id_type()); - } else { - trx.sign(t.get_private_key(account, (string)vetoperm), chain_id_type()); - } - return trx; -} - - -BOOST_AUTO_TEST_SUITE(recovery_tests) - -BOOST_FIXTURE_TEST_CASE( test_recovery_multisig_owner, TESTER ) try { - produce_blocks(1000); - create_account(N(alice), config::system_account_name, true); - produce_block(); - - BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner"), tx_missing_sigs); // requires multisig authorization - push_reqauth(N(alice), "owner", true); - produce_block(); - - fc::time_point expected_recovery(fc::seconds(control->head_block_time().sec_since_epoch()) +fc::days(30)); - - transaction_id_type recovery_txid; - { - signed_transaction trx = make_postrecovery(*this, N(alice), "owner.recov"); - auto trace = push_transaction(trx); - BOOST_REQUIRE_EQUAL(trace.deferred_transaction_requests.size(), 1); - recovery_txid = trace.deferred_transaction_requests.front().get().id(); - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true); - } - - auto skip_time = expected_recovery - control->head_block_time() - fc::milliseconds(config::block_interval_ms); - produce_block(skip_time); - control->push_deferred_transactions(true); - auto last_old_nonce_id = push_reqauth(N(alice), "owner", true).id; - produce_block(); - control->push_deferred_transactions(true); - - BOOST_REQUIRE_EQUAL(chain_has_transaction(last_old_nonce_id), true); - BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner", true), tx_missing_sigs); - auto first_new_nonce_id = push_reqauth(N(alice), "owner.recov").id; - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(first_new_nonce_id), true); - -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE( test_recovery_owner, TESTER ) try { - produce_blocks(1000); - create_account(N(alice)); - produce_block(); - - fc::time_point expected_recovery(fc::seconds(control->head_block_time().sec_since_epoch()) +fc::days(30)); - - transaction_id_type recovery_txid; - { - signed_transaction trx = make_postrecovery(*this, N(alice), "owner.recov"); - auto trace = push_transaction(trx); - BOOST_REQUIRE_EQUAL(trace.deferred_transaction_requests.size(), 1); - recovery_txid = trace.deferred_transaction_requests.front().get().id(); - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true); - } - - - auto skip_time = expected_recovery - control->head_block_time() - fc::milliseconds(config::block_interval_ms); - produce_block(skip_time); - control->push_deferred_transactions(true); - auto last_old_nonce_id = push_reqauth(N(alice), "owner").id; - produce_block(); - control->push_deferred_transactions(true); - - BOOST_REQUIRE_EQUAL(chain_has_transaction(last_old_nonce_id), true); - BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner"), tx_missing_sigs); - auto first_new_nonce_id = push_reqauth(N(alice), "owner.recov").id; - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(first_new_nonce_id), true); - -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE( test_recovery_owner_veto, TESTER ) try { - produce_blocks(1000); - create_account(N(alice)); - produce_block(); - - fc::time_point expected_recovery(fc::seconds(control->head_block_time().sec_since_epoch()) +fc::days(30)); - - transaction_id_type recovery_txid; - { - signed_transaction trx = make_postrecovery(*this, N(alice), "owner.recov"); - auto trace = push_transaction(trx); - BOOST_REQUIRE_EQUAL(trace.deferred_transaction_requests.size(), 1); - recovery_txid = trace.deferred_transaction_requests.front().get().id(); - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true); - } - - auto skip_time = expected_recovery - control->head_block_time() - fc::milliseconds(config::block_interval_ms); - produce_block(skip_time); - control->push_deferred_transactions(true); - auto last_old_nonce_id = push_reqauth(N(alice), "owner").id; - - // post the veto at the last possible time - { - signed_transaction trx = make_vetorecovery(*this, N(alice)); - auto trace = push_transaction(trx); - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true); - } - BOOST_REQUIRE_EQUAL(chain_has_transaction(last_old_nonce_id), true); - - control->push_deferred_transactions(true); - - // make sure the old owner is still in control - - BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner.recov"), tx_missing_sigs); - auto first_new_nonce_id = push_reqauth(N(alice), "owner").id; - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(first_new_nonce_id), true); - -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE( test_recovery_bad_creator, TESTER ) try { - produce_blocks(1000); - create_account(N(alice), config::system_account_name, true); - produce_block(); - - fc::time_point expected_recovery(fc::seconds(control->head_block_time().sec_since_epoch()) +fc::days(30)); - - transaction_id_type recovery_txid; - { - signed_transaction trx = make_postrecovery(*this, N(alice), "owner"); - auto trace = push_transaction(trx); - BOOST_REQUIRE_EQUAL(trace.deferred_transaction_requests.size(), 1); - recovery_txid = trace.deferred_transaction_requests.front().get().id(); - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true); - } - - auto skip_time = expected_recovery - control->head_block_time() - fc::milliseconds(config::block_interval_ms); - produce_block(skip_time); - control->push_deferred_transactions(true); - - // try all types of veto from the bad partner - { - signed_transaction trx = make_vetorecovery(*this, N(alice), N(active), get_private_key(N(inita),"active")); - BOOST_REQUIRE_THROW(push_transaction(trx), tx_missing_sigs); - } - - { - signed_transaction trx = make_vetorecovery(*this, N(alice), N(active), get_private_key(N(inita),"owner")); - BOOST_REQUIRE_THROW(push_transaction(trx), tx_missing_sigs); - } - - { - signed_transaction trx = make_vetorecovery(*this, N(alice), N(owner), get_private_key(N(inita),"active")); - BOOST_REQUIRE_THROW(push_transaction(trx), tx_missing_sigs); - } - - { - signed_transaction trx = make_vetorecovery(*this, N(alice), N(owner), get_private_key(N(inita),"owner")); - BOOST_REQUIRE_THROW(push_transaction(trx), tx_missing_sigs); - } - - produce_block(); - control->push_deferred_transactions(true); - - // make sure the recovery goes through - auto first_new_nonce_id = push_reqauth(N(alice), "owner").id; - produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(first_new_nonce_id), true); - -} FC_LOG_AND_RETHROW() - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp deleted file mode 100644 index 4d796608961..00000000000 --- a/tests/common/database_fixture.cpp +++ /dev/null @@ -1,266 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "database_fixture.hpp" - -uint32_t EOS_TESTING_GENESIS_TIMESTAMP = 1431700005; - -namespace eosio { namespace chain { - -testing_fixture::testing_fixture() { - default_genesis_state.initial_timestamp = fc::time_point_sec(EOS_TESTING_GENESIS_TIMESTAMP); - for (int i = 0; i < config::blocks_per_round; ++i) { - auto name = std::string("inita"); name.back()+=i; - auto private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(name)); - public_key_type public_key = private_key.get_public_key(); - default_genesis_state.initial_accounts.emplace_back(name, 0, 100000, public_key, public_key); - store_private_key(private_key); - - private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(name + ".producer")); - public_key = private_key.get_public_key(); - default_genesis_state.initial_producers.emplace_back(name, public_key); - store_private_key(private_key); - } -} - -fc::path testing_fixture::get_temp_dir(std::string id) { - if (id.empty()) { - anonymous_temp_dirs.emplace_back(); - return anonymous_temp_dirs.back().path(); - } - if (named_temp_dirs.count(id)) - return named_temp_dirs[id].path(); - return named_temp_dirs.emplace(std::make_pair(id, fc::temp_directory())).first->second.path(); -} - -const native_contract::genesis_state_type& testing_fixture::genesis_state() const { - return default_genesis_state; -} - -native_contract::genesis_state_type& testing_fixture::genesis_state() { - return default_genesis_state; -} - -void testing_fixture::store_private_key(const private_key_type& key) { - key_ring[key.get_public_key()] = key; -} - -private_key_type testing_fixture::get_private_key(const public_key_type& public_key) const { - auto itr = key_ring.find(public_key); - EOS_ASSERT(itr != key_ring.end(), missing_key_exception, - "Private key corresponding to public key ${k} not known.", ("k", public_key)); - return itr->second; -} - -flat_set testing_fixture::available_keys() const { - auto range = key_ring | boost::adaptors::map_keys; - return {range.begin(), range.end()}; -} - -testing_blockchain::testing_blockchain(chainbase::database& db, fork_database& fork_db, block_log& blocklog, - chain_initializer_interface& initializer, testing_fixture& fixture) - : chain_controller(db, fork_db, blocklog, initializer, native_contract::make_administrator(), - ::eosio::chain_plugin::default_transaction_execution_time * 1000, - ::eosio::chain_plugin::default_received_block_transaction_execution_time * 1000, - ::eosio::chain_plugin::default_create_block_transaction_execution_time * 1000, - chain_controller::txn_msg_limits{}), - db(db), - fixture(fixture) {} - -testing_blockchain::testing_blockchain(chainbase::database& db, fork_database& fork_db, block_log& blocklog, - chain_initializer_interface& initializer, testing_fixture& fixture, - uint32_t transaction_execution_time_msec, - uint32_t received_block_execution_time_msec, - uint32_t create_block_execution_time_msec, - const chain_controller::txn_msg_limits& rate_limits) - : chain_controller(db, fork_db, blocklog, initializer, native_contract::make_administrator(), - transaction_execution_time_msec * 1000, - received_block_execution_time_msec * 1000, - create_block_execution_time_msec * 1000, - rate_limits), - db(db), - fixture(fixture) {} - -void testing_blockchain::produce_blocks(uint32_t count, uint32_t blocks_to_miss) { - if (count == 0) - return; - - for (int i = 0; i < count; ++i) { - auto slot = blocks_to_miss + 1; - auto producer = get_producer(get_scheduled_producer(slot)); - auto private_key = fixture.get_private_key(producer.signing_key); - generate_block(get_slot_time(slot), producer.owner, private_key, block_schedule::in_single_thread, - chain_controller::created_block | (skip_trx_sigs? chain_controller::skip_transaction_signatures : 0)); - } -} - -void testing_blockchain::sync_with(testing_blockchain& other) { - // Already in sync? - if (head_block_id() == other.head_block_id()) - return; - // If other has a longer chain than we do, sync it to us first - if (head_block_num() < other.head_block_num()) - return other.sync_with(*this); - - auto sync_dbs = [](testing_blockchain& a, testing_blockchain& b) { - for (int i = 1; i <= a.head_block_num(); ++i) { - auto block = a.fetch_block_by_number(i); - if (block && !b.is_known_block(block->id())) { - b.push_block(*block, chain_controller::validation_steps::created_block); - } - } - }; - - sync_dbs(*this, other); - sync_dbs(other, *this); -} - -types::asset testing_blockchain::get_liquid_balance(const types::account_name& account) { - return get_database().get(account).balance; -} - -types::asset testing_blockchain::get_staked_balance(const types::account_name& account) { - return get_database().get(account).staked_balance; -} - -types::asset testing_blockchain::get_unstaking_balance(const types::account_name& account) { - return get_database().get(account).unstaking_balance; -} - -std::set testing_blockchain::get_approved_producers(const types::account_name& account) { - const auto& sbo = get_database().get(account); - if (sbo.producer_votes.contains()) { - auto range = sbo.producer_votes.get().range(); - return {range.begin(), range.end()}; - } - return {}; -} - -types::public_key testing_blockchain::get_block_signing_key(const types::account_name& producerName) { - return get_database().get(producerName).signing_key; -} - -void testing_blockchain::sign_transaction(signed_transaction& trx) const { - auto keys = get_required_keys(trx, fixture.available_keys()); - for (const auto& k : keys) { - // TODO: Use a real chain_id here - trx.sign(fixture.get_private_key(k), chain_id_type{}); - } -} - -fc::optional testing_blockchain::push_transaction(signed_transaction trx, uint32_t skip_flags) { - if (skip_trx_sigs) - skip_flags |= chain_controller::skip_transaction_signatures; - - if (auto_sign_trxs) { - sign_transaction(trx); - } - - if (hold_for_review) { - review_storage = std::make_pair(trx, skip_flags); - return {}; - } - return chain_controller::push_transaction(trx, skip_flags | chain_controller::pushed_transaction); -} - -vector testing_blockchain::assemble_wast( const std::string& wast ) { - // std::cout << "\n" << wast << "\n"; - IR::Module module; - std::vector parseErrors; - WAST::parseModule(wast.c_str(),wast.size(),module,parseErrors); - if(parseErrors.size()) - { - // Print any parse errors; - std::cerr << "Error parsing WebAssembly text file:" << std::endl; - for(auto& error : parseErrors) - { - std::cerr << ":" << error.locus.describe() << ": " << error.message.c_str() << std::endl; - std::cerr << error.locus.sourceLine << std::endl; - std::cerr << std::setw(error.locus.column(8)) << "^" << std::endl; - } - FC_ASSERT( !"error parsing wast" ); - } - - try - { - // Serialize the WebAssembly module. - Serialization::ArrayOutputStream stream; - WASM::serialize(stream,module); - return stream.getBytes(); - } - catch(Serialization::FatalSerializationException exception) - { - std::cerr << "Error serializing WebAssembly binary file:" << std::endl; - std::cerr << exception.message << std::endl; - throw; - } -} - - - -void testing_network::connect_blockchain(testing_blockchain& new_database) { - if (blockchains.count(&new_database)) - return; - - // If the network isn't empty, sync the new database with one of the old ones. The old ones are already in sync with - // each other, so just grab one arbitrarily. The old databases are connected to the propagation signals, so when one - // of them gets synced, it will propagate blocks to the others as well. - if (!blockchains.empty()) { - blockchains.begin()->first->sync_with(new_database); - } - - // The new database is now in sync with any old ones; go ahead and connect the propagation signal. - blockchains[&new_database] = new_database.applied_block.connect([this, &new_database](const signed_block& block) { - propagate_block(block, new_database); - }); -} - -void testing_network::disconnect_database(testing_blockchain& leaving_database) { - blockchains.erase(&leaving_database); -} - -void testing_network::disconnect_all() { - blockchains.clear(); -} - -void testing_network::propagate_block(const signed_block& block, const testing_blockchain& skip_db) { - for (const auto& pair : blockchains) { - if (pair.first == &skip_db) continue; - boost::signals2::shared_connection_block blocker(pair.second); - pair.first->push_block(block, chain_controller::created_block); - } -} - -} } // eosio::chain diff --git a/tests/common/expect.hpp b/tests/common/expect.hpp deleted file mode 100644 index 323aedef3cb..00000000000 --- a/tests/common/expect.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -namespace eosio { namespace test { - -template -struct expector { - expector(Eval _eval, const T& _expected, const char* const _msg) - : expected(_expected) - , eval(_eval) - , msg(_msg) - {} - - template - void operator() (const Input& input) { - BOOST_TEST_INFO(msg); - auto actual = eval(input); - BOOST_CHECK_EQUAL(Pred()(actual, expected), true); - } - - T expected; - Eval eval; - char const * const msg; -}; - - -template -auto expect(Eval&& eval, T expected, char const * const msg) { - return expector(eval, expected, msg); -} - -template -auto expect(Eval&& eval, T expected, char const * const msg) { - return expector, Eval, T>(eval, expected, msg); -} - -template -auto expect(Eval&& eval, char const * const msg) { - return expector, Eval, bool>(eval, true, msg); -} - -#define _NUM_ARGS(A,B,C,N, ...) N -#define NUM_ARGS(...) _NUM_ARGS(__VA_ARGS__, 3, 2, 1) -#define EXPECT(...) EXPECT_(NUM_ARGS(__VA_ARGS__),__VA_ARGS__) -#define EXPECT_(N, ...) EXPECT__(N, __VA_ARGS__) -#define EXPECT__(N, ...) EXPECT_##N(__VA_ARGS__) -#define EXPECT_3(P, E, T) eosio::test::expect

(E,T,"EXPECT<" #P ">(" #E "," #T ")") -#define EXPECT_2(E, T) eosio::test::expect(E,T,"EXPECT(" #E "," #T ")") -#define EXPECT_1(E) eosio::test::expect(E,"EXPECT(" #E ")") - -}} \ No newline at end of file diff --git a/tests/common/testing_macros.hpp b/tests/common/testing_macros.hpp deleted file mode 100644 index 649eb0b755c..00000000000 --- a/tests/common/testing_macros.hpp +++ /dev/null @@ -1,369 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include - -#include "macro_support.hpp" - -/// Some helpful macros to reduce boilerplate when making testcases -/// @{ - -/** - * @brief Create/Open a testing_blockchain, optionally with an ID - * - * Creates and opens a testing_blockchain with the first argument as its name, and, if present, the second argument as - * its ID. The ID should be provided without quotes. - * - * Example: - * @code{.cpp} - * // Create testing_blockchain chain1 - * Make_Blockchain(chain1) - * - * // The above creates the following objects: - * chainbase::database chain1_db; - * block_log chain1_log; - * fork_database chain1_fdb; - * native_contract::native_contract_chain_initializer chain1_initializer; - * testing_blockchain chain1; - * @endcode - */ -#define Make_Blockchain(...) BOOST_PP_OVERLOAD(MKCHAIN, __VA_ARGS__)(__VA_ARGS__) -/** - * @brief Similar to @ref Make_Blockchain, but works with several chains at once - * - * Creates and opens several testing_blockchains - * - * Example: - * @code{.cpp} - * // Create testing_blockchains chain1 and chain2, with chain2 having ID "id2" - * Make_Blockchains((chain1)(chain2, id2)) - * @endcode - */ -#define Make_Blockchains(...) BOOST_PP_SEQ_FOR_EACH(MKCHAINS_MACRO, _, __VA_ARGS__) - -/** - * @brief Make_Network is a shorthand way to create a testing_network and connect some testing_blockchains to it. - * - * Example usage: - * @code{.cpp} - * // Create and open testing_blockchains named alice, bob, and charlie - * MKDBS((alice)(bob)(charlie)) - * // Create a testing_network named net and connect alice and bob to it - * Make_Network(net, (alice)(bob)) - * - * // Connect charlie to net, then disconnect alice - * net.connect_blockchain(charlie); - * net.disconnect_blockchain(alice); - * - * // Create a testing_network named net2 with no blockchains connected - * Make_Network(net2) - * @endcode - */ -#define Make_Network(...) BOOST_PP_OVERLOAD(MKNET, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Make_Key is a shorthand way to create a keypair - * - * @code{.cpp} - * // This line: - * Make_Key(a_key) - * // ...defines these objects: - * private_key_type a_key_private_key; - * public_key a_key_public_key; - * // The private key is generated off of the sha256 hash of "a_key_private_key", so it should be unique from all - * // other keys created with Make_Key in the same scope. - * @endcode - */ -#ifdef HAVE_DATABASE_FIXTURE -#define Make_Key(name) auto name ## _private_key = private_key_type::regenerate(fc::digest(#name "_private_key")); \ - store_private_key(name ## _private_key); \ - public_key name ## _public_key = name ## _private_key.get_public_key(); \ - BOOST_TEST_CHECKPOINT("Created key " #name "_public_key"); -#else -#define Make_Key(name) auto name ## _private_key = private_key_type::regenerate(fc::digest(#name "_private_key")); \ - public_key name ## _public_key = name ## _private_key.get_public_key(); \ - BOOST_TEST_CHECKPOINT("Created key " #name "_public_key"); -#endif - -/** - * @brief Key_Authority is a shorthand way to create an inline Authority based on a key - * - * Invoke Key_Authority passing the name of a public key in the current scope, and Key_Authority will resolve inline to - * an authority which can be satisfied by a signature generated by the corresponding private key. - */ -#define Key_Authority(pubkey) (authority{1, {{pubkey, 1}}, {}}) -/** - * @brief Account_Authority is a shorthand way to create an inline Authority based on an account - * - * Invoke Account_Authority passing the name of an account, and Account_Authority will resolve inline to an authority - * which can be satisfied by the provided account's active authority. - */ -#define Account_Authority(account) (authority{1, {}, {{{#account, "active"}, 1}}}) -/** - * @brief Complex_Authority is a shorthand way to create an arbitrary inline @ref Authority - * - * Invoke Complex_Authority passing the weight threshold necessary to satisfy the authority, a bubble list of keys and - * weights, and a bubble list of accounts and weights. - * - * Key bubbles are structured as ((key_name, key_weight)) - * Account bubbles are structured as (("account_name", "account_authority", weight)) - * - * Example: - * @code{.cpp} - * // Create an authority which can be satisfied with a master key, or with any three of: - * // - key_1 - * // - key_2 - * // - key_3 - * // - Account alice's "test_multisig" authority - * // - Account bob's "test_multisig" authority - * Make_Key(master_key) - * Make_Key(key_1) - * Make_Key(key_2) - * Make_Key(key_3) - * auto auth = Complex_Authority(5, ((master_key, 5))((key_1, 2))((key_2, 2))((key_3, 2)), - * (("alice", "test_multisig", 2))(("bob", "test_multisig", 2)); - * @endcode - */ -#define Complex_Authority(THRESHOLD, KEY_BUBBLES, ACCOUNT_BUBBLES) \ - [&]{ \ - authority x; \ - x.threshold = THRESHOLD; \ - BOOST_PP_SEQ_FOR_EACH(Complex_Authority_macro_Key, x, KEY_BUBBLES) \ - BOOST_PP_SEQ_FOR_EACH(Complex_Authority_macro_Account, x, ACCOUNT_BUBBLES) \ - return x; \ - }() - -/** - * @brief Make_Account is a shorthand way to create an account - * - * Use Make_Account to create an account, including keys. The changes will be applied via a transaction applied to the - * provided blockchain object. The changes will not be incorporated into a block; they will be left in the pending - * state. - * - * Unless overridden, new accounts are created with a balance of asset(100) - * - * Example: - * @code{.cpp} - * Make_Account(chain, joe) - * // ... creates these objects: - * private_key_type joe_private_key; - * public_key joe_public_key; - * // ...and also registers the account joe with owner and active authorities satisfied by these keys, created by - * // init0, with init0's active authority as joe's recovery authority, and initially endowed with asset(100) - * @endcode - * - * You may specify a third argument for the creating account: - * @code{.cpp} - * // Same as MKACCT(chain, joe) except that sam will create joe's account instead of init0 - * Make_Account(chain, joe, sam) - * @endcode - * - * You may specify a fourth argument for the amount to transfer in account creation: - * @code{.cpp} - * // Same as MKACCT(chain, joe, sam) except that sam will send joe asset(100) during creation - * Make_Account(chain, joe, sam, asset(100)) - * @endcode - * - * You may specify a fifth argument, which will be used as the owner authority (must be an Authority, NOT a key!). - * - * You may specify a sixth argument, which will be used as the active authority. If six or more arguments are provided, - * the default keypair will NOT be created or put into scope. - * - * You may specify a seventh argument, which will be used as the recovery authority. - */ -#define Make_Account(...) BOOST_PP_OVERLOAD(MKACCT, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to set the code for an account - * - * @code{.cpp} - * char* wast = //... - * Set_Code(chain, codeacct, wast); - * @endcode - */ -#define Set_Code(...) BOOST_PP_OVERLOAD(SETCODE, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to create or update named authority on an account - * - * @code{.cpp} - * // Add a new authority named "money" to account "alice" as a child of her active authority - * authority newAuth = //... - * Set_Authority(chain, alice, "money", "active", newAuth); - * @endcode - */ -#define Set_Authority(...) BOOST_PP_OVERLOAD(SETAUTH, __VA_ARGS__)(__VA_ARGS__) -/** - * @brief Shorthand way to delete named authority from an account - * - * @code{.cpp} - * // Delete authority named "money" from account "alice" - * Delete_Authority(chain, alice, "money"); - * @endcode - */ -#define Delete_Authority(...) BOOST_PP_OVERLOAD(DELAUTH, __VA_ARGS__)(__VA_ARGS__) -/** - * @brief Shorthand way to link named authority with a contract/message type - * - * @code{.cpp} - * // Link alice's "money" authority with eosio::transfer - * Link_Authority(chain, alice, "money", eos, "transfer"); - * // Set alice's "native" authority as default for eos contract - * Link_Authority(chain, alice, "money", eos); - * @endcode - */ -#define Link_Authority(...) BOOST_PP_OVERLOAD(LINKAUTH, __VA_ARGS__)(__VA_ARGS__) -/** - * @brief Shorthand way to unlink named authority from a contract/message type - * - * @code{.cpp} - * // Unlink alice's authority for eosio::transfer - * Unlink_Authority(chain, alice, eos, "transfer"); - * // Unset alice's default authority for eos contract - * Unlink_Authority(chain, alice, eos); - * @endcode - */ -#define Unlink_Authority(...) BOOST_PP_OVERLOAD(UNLINKAUTH, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to transfer funds - * - * Use Transfer_Asset to send funds from one account to another: - * @code{.cpp} - * // Send 10 EOS from alice to bob - * Transfer_Asset(chain, alice, bob, asset(10)); - * - * // Send 10 EOS from alice to bob with memo "Thanks for all the fish!" - * Transfer_Asset(chain, alice, bob, asset(10), "Thanks for all the fish!"); - * @endcode - * - * The changes will be applied via a transaction applied to the provided blockchain object. The changes will not be - * incorporated into a block; they will be left in the pending state. - */ -#define Transfer_Asset(...) BOOST_PP_OVERLOAD(XFER, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to convert liquid funds to staked funds - * - * Use Stake_Asset to stake liquid funds: - * @code{.cpp} - * // Convert 10 of bob's EOS from liquid to staked - * Stake_Asset(chain, bob, asset(10).amount); - * - * // Stake and transfer 10 EOS from alice to bob (alice pays liquid EOS and bob receives stake) - * Stake_Asset(chain, alice, bob, asset(10).amount); - * @endcode - */ -#define Stake_Asset(...) BOOST_PP_OVERLOAD(STAKE, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to begin conversion from staked funds to liquid funds - * - * Use Unstake_Asset to begin unstaking funds: - * @code{.cpp} - * // Begin unstaking 10 of bob's EOS - * Unstake_Asset(chain, bob, asset(10).amount); - * @endcode - * - * This can also be used to cancel an unstaking in progress, by passing asset(0) as the amount. - */ -#define Begin_Unstake_Asset(...) BOOST_PP_OVERLOAD(BEGIN_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to claim unstaked EOS as liquid - * - * Use Finish_Unstake_Asset to liquidate unstaked funds: - * @code{.cpp} - * // Reclaim as liquid 10 of bob's unstaked EOS - * Unstake_Asset(chain, bob, asset(10).amount); - * @endcode - */ -#define Finish_Unstake_Asset(...) BOOST_PP_OVERLOAD(FINISH_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) - - -/** - * @brief Shorthand way to set voting proxy - * - * Use Set_Proxy to set what account a stakeholding account proxies its voting power to - * @code{.cpp} - * // Proxy sam's votes to bob - * Set_Proxy(chain, sam, bob); - * - * // Unproxy sam's votes - * Set_Proxy(chain, sam, sam); - * @endcode - */ -#define Set_Proxy(chain, stakeholder, proxy) \ -{ \ - eosio::chain::signed_transaction trx; \ - if (std::string(#stakeholder) != std::string(#proxy)) \ - transaction_emplace_message(trx, config::eos_contract_name, \ - vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ - else \ - transaction_emplace_message(trx, config::eos_contract_name, \ - vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ - trx.expiration = chain.head_block_time() + 100; \ - transaction_set_reference_block(trx, chain.head_block_id()); \ - chain.push_transaction(trx); \ -} - -/** - * @brief Shorthand way to create a block producer - * - * Use Make_Producer to create a block producer: - * @code{.cpp} - * // Create a block producer belonging to joe using signing_key as the block signing key and config as the producer's - * // vote for a @ref BlockchainConfiguration: - * Make_Producer(chain, joe, signing_key, config); - * - * // Create a block producer belonging to joe using signing_key as the block signing key: - * Make_Producer(chain, joe, signing_key); - * - * // Create a block producer belonging to joe, using a new key as the block signing key: - * Make_Producer(chain, joe); - * // ... creates the objects: - * private_key_type joe_producer_private_key; - * public_key joe_producer_public_key; - * @endcode - */ -#define Make_Producer(...) BOOST_PP_OVERLOAD(MKPDCR, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to set approval of a block producer - * - * Use Approve_Producer to change an account's approval of a block producer: - * @code{.cpp} - * // Set joe's approval for pete's block producer to Approve - * Approve_Producer(chain, joe, pete, true); - * // Set joe's approval for pete's block producer to Disapprove - * Approve_Producer(chain, joe, pete, false); - * @endcode - */ -#define Approve_Producer(...) BOOST_PP_OVERLOAD(APPDCR, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to update a block producer - * - * @note Unlike with the Make_* macros, the Update_* macros take an expression as the owner/name field, so be sure to - * wrap names like this in quotes. You may also pass a normal C++ expression to be evaulated here instead. The reason - * for this discrepancy is that the Make_* macros add identifiers to the current scope based on the owner/name field; - * moreover, which can't be done with C++ expressions; however, the Update_* macros do not add anything to the scope, - * and it's more likely that these will be used in a loop or other context where it is inconvenient to know the - * owner/name at compile time. - * - * Use Update_Producer to update a block producer: - * @code{.cpp} - * // Update a block producer belonging to joe using signing_key as the new block signing key, and config as the - * // producer's new vote for a @ref BlockchainConfiguration: - * Update_Producer(chain, "joe", signing_key, config) - * - * // Update a block producer belonging to joe using signing_key as the new block signing key: - * Update_Producer(chain, "joe", signing_key) - * @endcode - */ -#define Update_Producer(...) BOOST_PP_OVERLOAD(UPPDCR, __VA_ARGS__)(__VA_ARGS__) - -/// @} diff --git a/tests/tests/config.hpp.in b/tests/config.hpp.in similarity index 100% rename from tests/tests/config.hpp.in rename to tests/config.hpp.in diff --git a/tests/distributed-transactions-test.py b/tests/distributed-transactions-test.py index 078339576e9..7f75ea0bef9 100755 --- a/tests/distributed-transactions-test.py +++ b/tests/distributed-transactions-test.py @@ -62,6 +62,8 @@ def errorExit(msg="", errorCode=1): else: cluster.killall() cluster.cleanup() + walletMgr.killall() + walletMgr.cleanup() Print ("producing nodes: %s, non-producing nodes: %d, topology: %s, delay between nodes launch(seconds): %d" % (pnodes, total_nodes-pnodes, topo, delay)) @@ -70,11 +72,14 @@ def errorExit(msg="", errorCode=1): if cluster.launch(pnodes, total_nodes, topo=topo, delay=delay) is False: errorExit("Failed to stand up eos cluster.") + #exit(0) Print ("Wait for Cluster stabilization") # wait for cluster to start producing blocks if not cluster.waitOnClusterBlockNumSync(3): errorExit("Cluster never stabilized") + #exit(0) + Print("Stand up EOS wallet keosd") if walletMgr.launch() is False: errorExit("Failed to stand up keosd.") @@ -92,14 +97,12 @@ def errorExit(msg="", errorCode=1): initaAccount=cluster.initaAccount initbAccount=cluster.initbAccount + eosioAccount=cluster.eosioAccount - Print("Importing keys for account %s into wallet %s." % (initaAccount.name, wallet.name)) - if not walletMgr.importKey(initaAccount, wallet): - errorExit("Failed to import key for account %s" % (initaAccount.name)) - - Print("Create accounts.") - if not cluster.createAccounts(initaAccount): - errorExit("Accounts creation failed.") + # TBD: get account is currently failing. Enable when ready + # Print("Create accounts.") + # if not cluster.createAccounts(eosioAccount): + # errorExit("Accounts creation failed.") # TBD: Known issue (Issue 2043) that 'get currency balance' doesn't return balance. # Uncomment when functional diff --git a/tests/common/main.cpp b/tests/main.cpp similarity index 100% rename from tests/common/main.cpp rename to tests/main.cpp diff --git a/tests/nodeos_run_remote_test.py b/tests/nodeos_run_remote_test.py index 8b92fcd1598..62deb7172b5 100755 --- a/tests/nodeos_run_remote_test.py +++ b/tests/nodeos_run_remote_test.py @@ -14,6 +14,7 @@ def errorExit(msg="", errorCode=1): parser = argparse.ArgumentParser() parser.add_argument("-v", help="verbose", action='store_true') parser.add_argument("--dont-kill", help="Leave cluster running after test finishes", action='store_true') +parser.add_argument("--only-bios", help="Limit testing to bios node.", action='store_true') parser.add_argument("--dump-error-details", help="Upon error print etc/eosio/node_*/config.ini and var/lib/node_*/stderr.log to stdout", action='store_true') @@ -22,6 +23,7 @@ def errorExit(msg="", errorCode=1): debug=args.v dontKill=args.dont_kill dumpErrorDetails=args.dump_error_details +onlyBios=args.only_bios testUtils.Utils.Debug=debug @@ -43,7 +45,7 @@ def errorExit(msg="", errorCode=1): Print ("producing nodes: %s, non-producing nodes: %d, topology: %s, delay between nodes launch(seconds): %d" % (pnodes, total_nodes-pnodes, topo, delay)) Print("Stand up cluster") - if cluster.launch(pnodes, total_nodes, prodCount, topo, delay) is False: + if cluster.launch(pnodes, total_nodes, prodCount, topo, delay, onlyBios=onlyBios, dontKill=dontKill) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") @@ -55,15 +57,19 @@ def errorExit(msg="", errorCode=1): initaPrvtKey=producerKeys["inita"]["private"] initbPrvtKey=producerKeys["initb"]["private"] - cmd="%s --dont-launch --inita_prvt_key %s --initb_prvt_key %s %s %s" % (actualTest, initaPrvtKey, initbPrvtKey, "-v" if debug else "", "--dont-kill" if dontKill else "") + cmd="%s --dont-launch --inita_prvt_key %s --initb_prvt_key %s %s %s %s" % (actualTest, initaPrvtKey, initbPrvtKey, "-v" if debug else "", "--dont-kill" if dontKill else "", "--only-bios" if onlyBios else "") Print("Starting up %s test: %s" % ("nodeos", actualTest)) Print("cmd: %s\n" % (cmd)) if 0 != subprocess.call(cmd, shell=True): errorExit("failed to run cmd.") testSuccessful=True - Print("\nEND") finally: + if testSuccessful: + Print("Test succeeded.") + else: + Print("Test failed.") + if not testSuccessful and dumpErrorDetails: cluster.dumpErrorDetails() Print("== Errors see above ==") diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index eb74bc3330f..94b3d50b71d 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -4,7 +4,6 @@ import decimal import argparse -import random import re ############################################################### @@ -17,8 +16,8 @@ errorExit=testUtils.Utils.errorExit -def cmdError(name, code=0, exitNow=False): - msg="FAILURE - %s%s" % (name, ("" if code == 0 else (" returned error code %d" % code))) +def cmdError(name, cmdCode=0, exitNow=False): + msg="FAILURE - %s%s" % (name, ("" if cmdCode == 0 else (" returned error code %d" % cmdCode))) if exitNow: errorExit(msg, True) else: @@ -50,6 +49,7 @@ def cmdError(name, code=0, exitNow=False): action='store_true') parser.add_argument("-v", help="verbose logging", action='store_true') parser.add_argument("--dont-kill", help="Leave cluster running after test finishes", action='store_true') +parser.add_argument("--only-bios", help="Limit testing to bios node.", action='store_true') args = parser.parse_args() testOutputFile=args.output @@ -64,6 +64,7 @@ def cmdError(name, code=0, exitNow=False): dontLaunch=args.dont_launch dontKill=args.dont_kill prodCount=args.prod_count +onlyBios=args.only_bios testUtils.Utils.Debug=debug localTest=True if server == LOCAL_HOST else False @@ -79,27 +80,27 @@ def cmdError(name, code=0, exitNow=False): try: Print("BEGIN") - print("TEST_OUTPUT: %s" % (testOutputFile)) - print("SERVER: %s" % (server)) - print("PORT: %d" % (port)) + Print("TEST_OUTPUT: %s" % (testOutputFile)) + Print("SERVER: %s" % (server)) + Print("PORT: %d" % (port)) if enableMongo and not cluster.isMongodDbRunning(): errorExit("MongoDb doesn't seem to be running.") + walletMgr.killall() + walletMgr.cleanup() + if localTest and not dontLaunch: cluster.killall() cluster.cleanup() Print("Stand up cluster") - if cluster.launch(prodCount=prodCount) is False: + if cluster.launch(prodCount=prodCount, onlyBios=onlyBios, dontKill=dontKill) is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") else: cluster.initializeNodes(initaPrvtKey=initaPrvtKey, initbPrvtKey=initbPrvtKey) killEosInstances=False - walletMgr.killall() - walletMgr.cleanup() - accounts=testUtils.Cluster.createAccountKeys(3) if accounts is None: errorExit("FAILURE - create keys") @@ -192,7 +193,7 @@ def cmdError(name, code=0, exitNow=False): expectedkeys.append(account.activePrivateKey) noMatch=list(set(expectedkeys) - set(actualKeys)) if len(noMatch) > 0: - errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=true) + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) Print("Locking all wallets.") if not walletMgr.lockAllWallets(): @@ -214,14 +215,14 @@ def cmdError(name, code=0, exitNow=False): expectedkeys=[initaAccount.ownerPrivateKey] noMatch=list(set(expectedkeys) - set(actualKeys)) if len(noMatch) > 0: - errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=true) + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) node=cluster.getNode(0) if node is None: errorExit("Cluster in bad state, received None node") Print("Create new account %s via %s" % (testeraAccount.name, initaAccount.name)) - transId=node.createAccount(testeraAccount, initaAccount, stakedDeposit=0, waitForTransBlock=False) + transId=node.createInitializeAccount(testeraAccount, initaAccount, stakedDeposit=0, waitForTransBlock=False) if transId is None: cmdError("%s create account" % (ClientName)) errorExit("Failed to create account %s" % (testeraAccount.name)) @@ -230,47 +231,43 @@ def cmdError(name, code=0, exitNow=False): if not node.verifyAccount(testeraAccount): errorExit("FAILURE - account creation failed.", raw=True) - transferAmount=975321 - Print("Transfer funds %d from account %s to %s" % (transferAmount, initaAccount.name, testeraAccount.name)) + transferAmount="97.5321 EOS" + Print("Transfer funds %s from account %s to %s" % (transferAmount, initaAccount.name, testeraAccount.name)) if node.transferFunds(initaAccount, testeraAccount, transferAmount, "test transfer") is None: cmdError("%s transfer" % (ClientName)) errorExit("Failed to transfer funds %d from account %s to %s" % ( transferAmount, initaAccount.name, testeraAccount.name)) - # TBD: Known issue (Issue 2043) that 'get currency balance' doesn't return balance. - # Uncomment when functional - # expectedAmount=transferAmount - # Print("Verify transfer, Expected: %d" % (expectedAmount)) - # actualAmount=node.getAccountBalance(testeraAccount.name) - # if expectedAmount != actualAmount: - # cmdError("FAILURE - transfer failed") - # errorExit("Transfer verification failed. Excepted %d, actual: %d" % (expectedAmount, actualAmount)) - - transferAmount=100 - Print("Force transfer funds %d from account %s to %s" % ( + expectedAmount=transferAmount + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + transferAmount="0.0100 EOS" + Print("Force transfer funds %s from account %s to %s" % ( transferAmount, initaAccount.name, testeraAccount.name)) if node.transferFunds(initaAccount, testeraAccount, transferAmount, "test transfer", force=True) is None: cmdError("%s transfer" % (ClientName)) errorExit("Failed to force transfer funds %d from account %s to %s" % ( transferAmount, initaAccount.name, testeraAccount.name)) - # TBD: Known issue (Issue 2043) that 'get currency balance' doesn't return balance. - # Uncomment when functional - # expectedAmount=975421 - # Print("Verify transfer, Expected: %d" % (expectedAmount)) - # actualAmount=node.getAccountBalance(testeraAccount.name) - # if expectedAmount != actualAmount: - # cmdError("FAILURE - transfer failed") - # errorExit("Transfer verification failed. Excepted %d, actual: %d" % (expectedAmount, actualAmount)) + expectedAmount="97.5421 EOS" + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) Print("Create new account %s via %s" % (currencyAccount.name, initbAccount.name)) - transId=node.createAccount(currencyAccount, initbAccount, stakedDeposit=5000) + transId=node.createInitializeAccount(currencyAccount, initbAccount, stakedDeposit=5000) if transId is None: cmdError("%s create account" % (ClientName)) errorExit("Failed to create account %s" % (currencyAccount.name)) Print("Create new account %s via %s" % (exchangeAccount.name, initaAccount.name)) - transId=node.createAccount(exchangeAccount, initaAccount, waitForTransBlock=True) + transId=node.createInitializeAccount(exchangeAccount, initaAccount, waitForTransBlock=True) if transId is None: cmdError("%s create account" % (ClientName)) errorExit("Failed to create account %s" % (exchangeAccount.name)) @@ -285,69 +282,79 @@ def cmdError(name, code=0, exitNow=False): cmdError("%s wallet unlock" % (ClientName)) errorExit("Failed to unlock wallet %s" % (testWallet.name)) - transferAmount=975311 - Print("Transfer funds %d from account %s to %s" % ( + transferAmount="97.5311 EOS" + Print("Transfer funds %s from account %s to %s" % ( transferAmount, testeraAccount.name, currencyAccount.name)) trans=node.transferFunds(testeraAccount, currencyAccount, transferAmount, "test transfer a->b") if trans is None: cmdError("%s transfer" % (ClientName)) errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, initaAccount.name, testeraAccount.name)) + transferAmount, testeraAccount.name, currencyAccount.name)) transId=testUtils.Node.getTransId(trans) - # TBD: Known issue (Issue 2043) that 'get currency balance' doesn't return balance. - # Uncomment when functional - # expectedAmount=975311+5000 # 5000 initial deposit - # Print("Verify transfer, Expected: %d" % (expectedAmount)) - # actualAmount=node.getAccountBalance(currencyAccount.name) - # if actualAmount is None: - # cmdError("%s get account currency" % (ClientName)) - # errorExit("Failed to retrieve balance for account %s" % (currencyAccount.name)) - # if expectedAmount != actualAmount: - # cmdError("FAILURE - transfer failed") - # errorExit("Transfer verification failed. Excepted %d, actual: %d" % (expectedAmount, actualAmount)) - - expectedAccounts=[testeraAccount.name, currencyAccount.name, exchangeAccount.name] - Print("Get accounts by key %s, Expected: %s" % (PUB_KEY3, expectedAccounts)) - actualAccounts=node.getAccountsArrByKey(PUB_KEY3) - if actualAccounts is None: - cmdError("%s get accounts pub_key3" % (ClientName)) - errorExit("Failed to retrieve accounts by key %s" % (PUB_KEY3)) - noMatch=list(set(expectedAccounts) - set(actualAccounts)) - if len(noMatch) > 0: - errorExit("FAILURE - Accounts lookup by key %s. Expected: %s, Actual: %s" % ( - PUB_KEY3, expectedAccounts, actualAccounts), raw=True) - - expectedAccounts=[testeraAccount.name] - Print("Get accounts by key %s, Expected: %s" % (PUB_KEY1, expectedAccounts)) - actualAccounts=node.getAccountsArrByKey(PUB_KEY1) - if actualAccounts is None: - cmdError("%s get accounts pub_key1" % (ClientName)) - errorExit("Failed to retrieve accounts by key %s" % (PUB_KEY1)) - noMatch=list(set(expectedAccounts) - set(actualAccounts)) - if len(noMatch) > 0: - errorExit("FAILURE - Accounts lookup by key %s. Expected: %s, Actual: %s" % ( - PUB_KEY1, expectedAccounts, actualAccounts), raw=True) - - expectedServants=[testeraAccount.name, currencyAccount.name] - Print("Get %s servants, Expected: %s" % (initaAccount.name, expectedServants)) - actualServants=node.getServantsArr(initaAccount.name) - if actualServants is None: - cmdError("%s get servants testera" % (ClientName)) - errorExit("Failed to retrieve %s servants" % (initaAccount.name)) - noMatch=list(set(expectedAccounts) - set(actualAccounts)) - if len(noMatch) > 0: - errorExit("FAILURE - %s servants. Expected: %s, Actual: %s" % ( - initaAccount.name, expectedServants, actualServants), raw=True) - - Print("Get %s servants, Expected: []" % (testeraAccount.name)) - actualServants=node.getServantsArr(testeraAccount.name) - if actualServants is None: - cmdError("%s get servants testera" % (ClientName)) - errorExit("Failed to retrieve %s servants" % (testeraAccount.name)) - if len(actualServants) > 0: - errorExit("FAILURE - %s servants. Expected: [], Actual: %s" % ( - testeraAccount.name, actualServants), raw=True) + expectedAmount="98.0311 EOS" # 5000 initial deposit + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(currencyAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + Print("Validate last action for account %s" % (testeraAccount.name)) + actions=node.getActions(testeraAccount, -1, -1) + assert(actions) + try: + assert(actions["actions"][0]["action_trace"]["act"]["name"] == "transfer") + except (AssertionError, KeyError) as e: + Print("Last action validation failed. Actions: %s" % (actions)) + raise + + # Pre-mature exit on slim branch. This will pushed futher out as code stablizes. + # testSuccessful=True + # exit(0) + + # This API (get accounts) is no longer supported (Issue 2876) + # expectedAccounts=[testeraAccount.name, currencyAccount.name, exchangeAccount.name] + # Print("Get accounts by key %s, Expected: %s" % (PUB_KEY3, expectedAccounts)) + # actualAccounts=node.getAccountsArrByKey(PUB_KEY3) + # if actualAccounts is None: + # cmdError("%s get accounts pub_key3" % (ClientName)) + # errorExit("Failed to retrieve accounts by key %s" % (PUB_KEY3)) + # noMatch=list(set(expectedAccounts) - set(actualAccounts)) + # if len(noMatch) > 0: + # errorExit("FAILURE - Accounts lookup by key %s. Expected: %s, Actual: %s" % ( + # PUB_KEY3, expectedAccounts, actualAccounts), raw=True) + # + # expectedAccounts=[testeraAccount.name] + # Print("Get accounts by key %s, Expected: %s" % (PUB_KEY1, expectedAccounts)) + # actualAccounts=node.getAccountsArrByKey(PUB_KEY1) + # if actualAccounts is None: + # cmdError("%s get accounts pub_key1" % (ClientName)) + # errorExit("Failed to retrieve accounts by key %s" % (PUB_KEY1)) + # noMatch=list(set(expectedAccounts) - set(actualAccounts)) + # if len(noMatch) > 0: + # errorExit("FAILURE - Accounts lookup by key %s. Expected: %s, Actual: %s" % ( + # PUB_KEY1, expectedAccounts, actualAccounts), raw=True) + # + # This API (get servants) is no longer supported. + # expectedServants=[testeraAccount.name, currencyAccount.name] + # Print("Get %s servants, Expected: %s" % (initaAccount.name, expectedServants)) + # actualServants=node.getServantsArr(initaAccount.name) + # if actualServants is None: + # cmdError("%s get servants testera" % (ClientName)) + # errorExit("Failed to retrieve %s servants" % (initaAccount.name)) + # noMatch=list(set(expectedAccounts) - set(actualAccounts)) + # if len(noMatch) > 0: + # errorExit("FAILURE - %s servants. Expected: %s, Actual: %s" % ( + # initaAccount.name, expectedServants, actualServants), raw=True) + # + # Print("Get %s servants, Expected: []" % (testeraAccount.name)) + # actualServants=node.getServantsArr(testeraAccount.name) + # if actualServants is None: + # cmdError("%s get servants testera" % (ClientName)) + # errorExit("Failed to retrieve %s servants" % (testeraAccount.name)) + # if len(actualServants) > 0: + # errorExit("FAILURE - %s servants. Expected: [], Actual: %s" % ( + # testeraAccount.name, actualServants), raw=True) node.waitForTransIdOnNode(transId) @@ -362,25 +369,31 @@ def cmdError(name, code=0, exitNow=False): typeVal=None amountVal=None - if not enableMongo: - typeVal= transaction["transaction"]["transaction"]["actions"][0]["name"] - amountVal=transaction["transaction"]["transaction"]["actions"][0]["data"]["quantity"] - amountVal=int(decimal.Decimal(amountVal.split()[0])*10000) - else: - typeVal= transaction["name"] - amountVal=transaction["data"]["quantity"] - amountVal=int(decimal.Decimal(amountVal.split()[0])*10000) + assert(transaction) + try: + if not enableMongo: + typeVal= transaction["traces"][0]["act"]["name"] + amountVal=transaction["traces"][0]["act"]["data"]["quantity"] + amountVal=int(decimal.Decimal(amountVal.split()[0])*10000) + else: + typeVal= transaction["name"] + amountVal=transaction["data"]["quantity"] + amountVal=int(decimal.Decimal(amountVal.split()[0])*10000) + except (AssertionError, KeyError) as e: + Print("Transaction validation parsing failed. Transaction: %s" % (transaction)) + raise if typeVal != "transfer" or amountVal != 975311: errorExit("FAILURE - get transaction trans_id failed: %s %s %s" % (transId, typeVal, amountVal), raw=True) - Print("Get transactions for account %s" % (testeraAccount.name)) - actualTransactions=node.getTransactionsArrByAccount(testeraAccount.name) - if actualTransactions is None: - cmdError("%s get transactions testera" % (ClientName)) - errorExit("Failed to get transactions by account %s" % (testeraAccount.name)) - if transId not in actualTransactions: - errorExit("FAILURE - get transactions testera failed", raw=True) + # This API (get transactions) is no longer supported. + # Print("Get transactions for account %s" % (testeraAccount.name)) + # actualTransactions=node.getTransactionsArrByAccount(testeraAccount.name) + # if actualTransactions is None: + # cmdError("%s get transactions testera" % (ClientName)) + # errorExit("Failed to get transactions by account %s" % (testeraAccount.name)) + # if transId not in actualTransactions: + # errorExit("FAILURE - get transactions testera failed", raw=True) Print("Currency Contract Tests") Print("verify no contract in place") @@ -451,13 +464,10 @@ def cmdError(name, code=0, exitNow=False): errorExit("FAILURE - Wrong currency balance", raw=True) Print("Verify currency contract has proper initial balance (via get currency balance)") - res=node.getCurrencyBalance(contract, currencyAccount.name, "CUR") - if res is None: - cmdError("%s get currency balance" % (ClientName)) - errorExit("Failed to retrieve CUR balance from contract %s account %s" % (contract, currencyAccount.name)) + amountStr=node.getNodeAccountBalance("currency", currencyAccount.name) expected="100000.0000 CUR" - actual=res.strip() + actual=amountStr if actual != expected: errorExit("FAILURE - get currency balance failed. Recieved response: <%s>" % (res), raw=True) @@ -488,29 +498,18 @@ def cmdError(name, code=0, exitNow=False): cmdError("%s get transaction trans_id" % (ClientName)) errorExit("Failed to verify push message transaction id.") - # TODO need to update eosio.system contract to use new currency and update cleos and chain_plugin for interaction Print("read current contract balance") - contract="currency" - table="accounts" - row0=node.getTableRow(contract, initaAccount.name, table, 0) - if row0 is None: - cmdError("%s get currency table inita account" % (ClientName)) - errorExit("Failed to retrieve contract %s table %s" % (contract, table)) + amountStr=node.getNodeAccountBalance("currency", initaAccount.name) - balanceKey="balance" - keyKey="key" expected="0.0050 CUR" - actual=row0[balanceKey] + actual=amountStr if actual != expected: errorExit("FAILURE - Wrong currency balance (expected=%s, actual=%s)" % (str(expected), str(actual)), raw=True) - row0=node.getTableRow(contract, currencyAccount.name, table, 0) - if row0 is None: - cmdError("%s get currency table currency account" % (ClientName)) - errorExit("Failed to retrieve contract %s table %s" % (contract, table)) + amountStr=node.getNodeAccountBalance("currency", currencyAccount.name) expected="99999.9950 CUR" - actual=row0[balanceKey] + actual=amountStr if actual != expected: errorExit("FAILURE - Wrong currency balance (expected=%s, actual=%s)" % (str(expected), str(actual)), raw=True) @@ -604,7 +603,7 @@ def cmdError(name, code=0, exitNow=False): # errorExit("mongo get messages by transaction id %s" % (transId)) - Print("Request invalid block numbered %d" % (currentBlockNum+1000)) + Print("Request invalid block numbered %d. This will generate an extpected error message." % (currentBlockNum+1000)) block=node.getBlock(currentBlockNum+1000, silentErrors=True, retry=False) if block is not None: errorExit("ERROR: Received block where not expected") @@ -627,8 +626,11 @@ def cmdError(name, code=0, exitNow=False): #errorExit("FAILURE - Assert in var/lib/node_00/stderr.txt") testSuccessful=True - Print("END") finally: + if testSuccessful: + Print("Test succeeded.") + else: + Print("Test failed.") if not testSuccessful and dumpErrorDetails: cluster.dumpErrorDetails() walletMgr.dumpErrorDetails() diff --git a/tests/testUtils.py b/tests/testUtils.py index 14d66b9d3a9..6281c0cd067 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -4,20 +4,18 @@ import time import glob import shutil -import time import os import platform from collections import namedtuple import re import string import signal -import time import datetime import inspect import sys import random -import io import json +import shlex ########################################################################################### class Utils: @@ -38,8 +36,8 @@ class Utils: @staticmethod def Print(*args, **kwargs): stackDepth=len(inspect.stack())-2 - str=' '*stackDepth - sys.stdout.write(str) + s=' '*stackDepth + sys.stdout.write(s) print(*args, **kwargs) SyncStrategy=namedtuple("ChainSyncStrategy", "name id arg") @@ -83,7 +81,8 @@ def getChainStrategies(): @staticmethod def checkOutput(cmd): assert(isinstance(cmd, list)) - retStr=subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode("utf-8") + #retStr=subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode("utf-8") + retStr=subprocess.check_output(cmd).decode("utf-8") return retStr @staticmethod @@ -111,26 +110,14 @@ def waitForObj(lam, timeout=None): @staticmethod def waitForBool(lam, timeout=None): myLam = lambda: True if lam() else None - ret=Utils.waitForObj(myLam) + ret=Utils.waitForObj(myLam, timeout) return False if ret is None else ret -########################################################################################### -class Table(object): - def __init__(self, name): - self.name=name - self.keys=[] - self.data=[] - -########################################################################################### -class Transaction(object): - def __init__(self, transId): - self.transId=transId - self.tType=None - self.amount=0 - ########################################################################################### class Account(object): + # pylint: disable=too-few-public-methods + def __init__(self, name): self.name=name self.balance=0 @@ -145,8 +132,11 @@ def __str__(self): return "Name: %s" % (self.name) ########################################################################################### +# pylint: disable=too-many-public-methods class Node(object): + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-arguments def __init__(self, host, port, pid=None, cmd=None, enableMongo=False, mongoHost="localhost", mongoPort=27017, mongoDb="EOStest"): self.host=host self.port=port @@ -167,33 +157,52 @@ def __str__(self): #return "Host: %s, Port:%d, Pid:%s, Cmd:\"%s\"" % (self.host, self.port, self.pid, self.cmd) return "Host: %s, Port:%d" % (self.host, self.port) + @staticmethod + def validateTransaction(trans): + assert trans + assert isinstance(trans, dict), print("Input type is %s" % type(trans)) + + def printTrans(trans): + Utils.Print("ERROR: Failure in transaction validation.") + Utils.Print("Transaction: %s" % (json.dumps(trans, indent=1))) + + assert trans["processed"]["receipt"]["status"] == "executed", printTrans(trans) + @staticmethod def runCmdReturnJson(cmd, trace=False): - retStr=Utils.checkOutput(cmd.split()) + cmdArr=shlex.split(cmd) + retStr=Utils.checkOutput(cmdArr) jStr=Node.filterJsonObject(retStr) - trace and Utils.Print ("RAW > %s"% retStr) - trace and Utils.Print ("JSON> %s"% jStr) + if trace: Utils.Print ("RAW > %s"% (retStr)) + if trace: Utils.Print ("JSON> %s"% (jStr)) + if not jStr: + msg="Expected JSON response" + Utils.Print ("ERROR: "+ msg) + Utils.Print ("RAW > %s"% retStr) + raise TypeError(msg) + try: jsonData=json.loads(jStr) + return jsonData except json.decoder.JSONDecodeError as ex: + Utils.Print (ex) Utils.Print ("RAW > %s"% retStr) Utils.Print ("JSON> %s"% jStr) - - return jsonData + raise @staticmethod def __runCmdArrReturnJson(cmdArr, trace=False): retStr=Utils.checkOutput(cmdArr) jStr=Node.filterJsonObject(retStr) - trace and Utils.Print ("RAW > %s"% retStr) - trace and Utils.Print ("JSON> %s"% jStr) + if trace: Utils.Print ("RAW > %s"% (retStr)) + if trace: Utils.Print ("JSON> %s"% (jStr)) jsonData=json.loads(jStr) return jsonData @staticmethod def runCmdReturnStr(cmd, trace=False): retStr=Node.__checkOutput(cmd.split()) - trace and Utils.Print ("RAW > %s"% retStr) + if trace: Utils.Print ("RAW > %s"% (retStr)) return retStr @staticmethod @@ -235,7 +244,7 @@ def normalizeJsonObject(extJStr): @staticmethod def runMongoCmdReturnJson(cmdArr, subcommand, trace=False): - retId,outs,errs=Node.stdinAndCheckOutput(cmdArr, subcommand) + retId,outs=Node.stdinAndCheckOutput(cmdArr, subcommand) if retId is not 0: return None outStr=Node.byteArrToStr(outs) @@ -247,24 +256,17 @@ def runMongoCmdReturnJson(cmdArr, subcommand, trace=False): jStr=Node.normalizeJsonObject(extJStr) if not jStr: return None - trace and Utils.Print ("RAW > %s"% outStr) + if trace: Utils.Print ("RAW > %s"% (outStr)) #trace and Utils.Print ("JSON> %s"% jStr) jsonData=json.loads(jStr) return jsonData - @staticmethod - def getTransId_nj(trans): - """Retrieve transaction id from cleos non-json output.""" - # parse out transaction id - pattern="executed transaction:\s+(\w+)\s+" - m=re.search(pattern, trans, re.MULTILINE) - assert(m is not None) - return m.group(1) - @staticmethod def getTransId(trans): """Retrieve transaction id from dictionary object.""" - # TBD: add assert for trans is Dictionary + assert trans + assert isinstance(trans, dict), print("Input type is %s" % type(trans)) + #Utils.Print("%s" % trans) transId=trans["transaction_id"] return transId @@ -276,10 +278,11 @@ def byteArrToStr(arr): def setWalletEndpointArgs(self, args): self.endpointArgs="--url http://%s:%d %s" % (self.host, self.port, args) + # pylint: disable=too-many-branches def getBlock(self, blockNum, retry=True, silentErrors=False): if not self.enableMongo: cmd="%s %s get block %s" % (Utils.EosClientPath, self.endpointArgs, blockNum) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -289,10 +292,10 @@ def getBlock(self, blockNum, retry=True, silentErrors=False): Utils.Print("ERROR: Exception during get block. %s" % (msg)) return None else: - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Blocks.findOne( { "block_num": %s } )' % (blockNum) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) if trans is not None: @@ -305,16 +308,16 @@ def getBlock(self, blockNum, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None def getBlockById(self, blockId, retry=True, silentErrors=False): - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Blocks.findOne( { "block_id": "%s" } )' % (blockId) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) if trans is not None: @@ -327,12 +330,11 @@ def getBlockById(self, blockId, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None - def doesNodeHaveBlockNum(self, blockNum): assert isinstance(blockNum, int) @@ -345,10 +347,11 @@ def doesNodeHaveBlockNum(self, blockNum): else: return True + # pylint: disable=too-many-branches def getTransaction(self, transId, retry=True, silentErrors=False): if not self.enableMongo: cmd="%s %s get transaction %s" % (Utils.EosClientPath, self.endpointArgs, transId) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -361,10 +364,10 @@ def getTransaction(self, transId, retry=True, silentErrors=False): Utils.Print("ERROR: Exception during transaction retrieval. %s" % (msg)) return None else: - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Transactions.findOne( { $and : [ { "transaction_id": "%s" }, {"pending":false} ] } )' % (transId) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) return trans @@ -376,16 +379,16 @@ def getTransaction(self, transId, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None def getTransByBlockId(self, blockId, retry=True, silentErrors=False): - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Transactions.find( { "block_id": "%s" } )' % (blockId) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand, True) if trans is not None: @@ -398,7 +401,7 @@ def getTransByBlockId(self, blockId, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None @@ -406,10 +409,10 @@ def getTransByBlockId(self, blockId, retry=True, silentErrors=False): def getActionFromDb(self, transId, retry=True, silentErrors=False): - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Actions.findOne( { "transaction_id": "%s" } )' % (transId) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) if trans is not None: @@ -422,16 +425,16 @@ def getActionFromDb(self, transId, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None def getMessageFromDb(self, transId, retry=True, silentErrors=False): - for i in range(2): + for _ in range(2): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Messages.findOne( { "transaction_id": "%s" } )' % (transId) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) if trans is not None: @@ -444,7 +447,7 @@ def getMessageFromDb(self, transId, retry=True, silentErrors=False): if not retry: break if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None @@ -456,14 +459,40 @@ def doesNodeHaveTransId(self, transId): blockNum=None if not self.enableMongo: - blockNum=int(trans["transaction"]["transaction"]["ref_block_num"]) + blockNum=int(trans["trx"]["trx"]["ref_block_num"]) else: blockNum=int(trans["ref_block_num"]) blockNum += 1 - Utils.Debug and Utils.Print("Check if block %d is irreversible." % (blockNum)) + if Utils.Debug: Utils.Print("Check if block %d is irreversible." % (blockNum)) return self.doesNodeHaveBlockNum(blockNum) + # Create & initialize account and return creation transactions. Return transaction json object + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False): + cmd='%s %s system newaccount -j %s %s %s %s --stake-net "100 EOS" --stake-cpu "100 EOS" --buy-ram-EOS "100 EOS"' % ( + Utils.EosClientPath, self.endpointArgs, creatorAccount.name, account.name, + account.ownerPublicKey, account.activePublicKey) + + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) + trans=None + try: + trans=Node.runCmdReturnJson(cmd) + transId=Node.getTransId(trans) + except subprocess.CalledProcessError as ex: + msg=ex.output.decode("utf-8") + Utils.Print("ERROR: Exception during account creation. %s" % (msg)) + return None + + if stakedDeposit > 0: + self.waitForTransIdOnNode(transId) # seems like account creation needs to be finlized before transfer can happen + trans = self.transferFunds(creatorAccount, account, "%0.04f EOS" % (stakedDeposit/10000), "init") + transId=Node.getTransId(trans) + + if waitForTransBlock and not self.waitForTransIdOnNode(transId): + return None + + return trans + # Create account and return creation transactions. Return transaction json object # waitForTransBlock: wait on creation transaction id to appear in a block def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False): @@ -471,7 +500,7 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran Utils.EosClientPath, self.endpointArgs, creatorAccount.name, account.name, account.ownerPublicKey, account.activePublicKey) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None try: trans=Node.runCmdReturnJson(cmd) @@ -483,7 +512,7 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran if stakedDeposit > 0: self.waitForTransIdOnNode(transId) # seems like account creation needs to be finlized before transfer can happen - trans = self.transferFunds(creatorAccount, account, stakedDeposit, "init") + trans = self.transferFunds(creatorAccount, account, "%0.04f EOS" % (stakedDeposit/10000), "init") transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransIdOnNode(transId): @@ -492,8 +521,8 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran return trans def getEosAccount(self, name): - cmd="%s %s get account %s" % (Utils.EosClientPath, self.endpointArgs, name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + cmd="%s %s get account -j %s" % (Utils.EosClientPath, self.endpointArgs, name) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -505,7 +534,7 @@ def getEosAccount(self, name): def getEosAccountFromDb(self, name): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.Accounts.findOne({"name" : "%s"})' % (name) - Utils.Debug and Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) return trans @@ -514,32 +543,33 @@ def getEosAccountFromDb(self, name): Utils.Print("ERROR: Exception during get account from db. %s" % (msg)) return None - - def getEosCurrencyBalance(self, name): - cmd="%s %s get currency balance eosio %s EOS" % (Utils.EosClientPath, self.endpointArgs, name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + def getTable(self, contract, scope, table): + cmd="%s %s get table %s %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, scope, table) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: - trans=Node.runCmdReturnStr(cmd) + trans=Node.runCmdReturnJson(cmd) return trans except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get EOS balance. %s" % (msg)) + Utils.Print("ERROR: Exception during table retrieval. %s" % (msg)) return None - def getCurrencyBalance(self, contract, account, symbol): - cmd="%s %s get currency balance %s %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, account, symbol) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + #def getNodeAccountEosBalance(self, scope): + def getNodeAccountBalance(self, contract, scope): + assert(isinstance(contract, str)) + assert(isinstance(scope, str)) + table="accounts" + trans = self.getTable(contract, scope, table) + assert(trans) try: - trans=Node.runCmdReturnStr(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get currency balance. %s" % (msg)) - return None + return trans["rows"][0]["balance"] + except (AssertionError, KeyError) as e: + print("Transaction parsing failed. Transaction: %s" % (trans)) + raise def getCurrencyStats(self, contract, symbol=""): cmd="%s %s get currency stats %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, symbol) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -559,7 +589,7 @@ def verifyAccount(self, account): return None return ret else: - for i in range(2): + for _ in range(2): ret=self.getEosAccountFromDb(account.name) if ret is not None: account_name=ret["name"] @@ -568,7 +598,7 @@ def verifyAccount(self, account): return None return ret if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) return None @@ -591,14 +621,17 @@ def waitForNextBlock(self, timeout=None): # Trasfer funds. Returns "transfer" json return object def transferFunds(self, source, destination, amount, memo="memo", force=False): - cmd="%s %s -v transfer -j %s %s %d" % ( - Utils.EosClientPath, self.endpointArgs, source.name, destination.name, amount) + assert isinstance(amount, str) + + cmd="%s %s -v transfer -j %s %s" % ( + Utils.EosClientPath, self.endpointArgs, source.name, destination.name) cmdArr=cmd.split() + cmdArr.append(amount) cmdArr.append(memo) if force: cmdArr.append("-f") s=" ".join(cmdArr) - Utils.Debug and Utils.Print("cmd: %s" % (s)) + if Utils.Debug: Utils.Print("cmd: %s" % (s)) trans=None try: trans=Node.__runCmdArrReturnJson(cmdArr) @@ -609,9 +642,9 @@ def transferFunds(self, source, destination, amount, memo="memo", force=False): return None def validateSpreadFundsOnNode(self, adminAccount, accounts, expectedTotal): - actualTotal=self.getAccountBalance(adminAccount.name) + actualTotal=self.getAccountEosBalance(adminAccount.name) for account in accounts: - fund = self.getAccountBalance(account.name) + fund = self.getAccountEosBalance(account.name) if fund != account.balance: Utils.Print("ERROR: validateSpreadFunds> Expected: %d, actual: %d for account %s" % (account.balance, fund, account.name)) @@ -626,15 +659,15 @@ def validateSpreadFundsOnNode(self, adminAccount, accounts, expectedTotal): return True def getSystemBalance(self, adminAccount, accounts): - balance=self.getAccountBalance(adminAccount.name) + balance=self.getAccountEosBalance(adminAccount.name) for account in accounts: - balance += self.getAccountBalance(account.name) + balance += self.getAccountEosBalance(account.name) return balance # Gets accounts mapped to key. Returns json object def getAccountsByKey(self, key): cmd="%s %s get accounts %s" % (Utils.EosClientPath, self.endpointArgs, key) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -643,15 +676,32 @@ def getAccountsByKey(self, key): Utils.Print("ERROR: Exception during accounts by key retrieval. %s" % (msg)) return None + # Get actions mapped to an account (cleos get actions) + def getActions(self, account, pos=-1, offset=-1): + assert(isinstance(account, Account)) + assert(isinstance(pos, int), isinstance(offset, int)) + + cmd="%s %s get actions -j %s %d %d" % (Utils.EosClientPath, self.endpointArgs, account.name, pos, offset) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) + try: + actions=Node.runCmdReturnJson(cmd) + return actions + except subprocess.CalledProcessError as ex: + msg=ex.output.decode("utf-8") + Utils.Print("ERROR: Exception during actions by account retrieval. %s" % (msg)) + return None + # Gets accounts mapped to key. Returns array def getAccountsArrByKey(self, key): + assert(trans) + assert("account_names" in trans) trans=self.getAccountsByKey(key) accounts=trans["account_names"] return accounts def getServants(self, name): cmd="%s %s get servants %s" % (Utils.EosClientPath, self.endpointArgs, name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -665,31 +715,37 @@ def getServantsArr(self, name): servants=trans["controlled_accounts"] return servants - def getAccountBalance(self, name): + def getAccountEosBalanceStr(self, scope): + """Returns EOS currency account balance from cleos get table command. Returned balance is string following syntax "98.0311 EOS". """ + assert isinstance(scope, str) if not self.enableMongo: - amount=self.getEosCurrencyBalance(name) - Utils.Debug and Utils.Print("getEosCurrencyBalance %s %s", name, amount) - balanceStr=amount.split()[0] - balance=int(decimal.Decimal(balanceStr[1:])*10000) - return balance + amount=self.getNodeAccountBalance("eosio.token", scope) + if Utils.Debug: Utils.Print("getNodeAccountEosBalance %s %s" % (scope, amount)) + assert isinstance(amount, str) + return amount else: if self.mongoSyncTime is not None: - Utils.Debug and Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) + if Utils.Debug: Utils.Print("cmd: sleep %d seconds" % (self.mongoSyncTime)) time.sleep(self.mongoSyncTime) - account=self.getEosAccountFromDb(name) + account=self.getEosAccountFromDb(scope) if account is not None: - field=account["eos_balance"] - balanceStr=field.split()[0] - balance=int(float(balanceStr)*10000) + balance=account["eos_balance"] return balance return None + def getAccountEosBalance(self, scope): + """Returns EOS currency account balance from cleos get table command. Returned balance is an integer e.g. 980311. """ + balanceStr=self.getAccountEosBalanceStr(scope) + balanceStr=balanceStr.split()[0] + balance=int(decimal.Decimal(balanceStr[1:])*10000) + return balance + # transactions lookup by id. Returns json object def getTransactionsByAccount(self, name): cmd="%s %s get transactions -j %s" % (Utils.EosClientPath, self.endpointArgs, name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -704,17 +760,17 @@ def getTransactionsArrByAccount(self, name): transactions=trans["transactions"] transArr=[] for transaction in transactions: - id=transaction["transaction_id"] - transArr.append(id) + transId=transaction["transaction_id"] + transArr.append(transId) return transArr def getAccountCodeHash(self, account): cmd="%s %s get code %s" % (Utils.EosClientPath, self.endpointArgs, account) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: retStr=Utils.checkOutput(cmd.split()) #Utils.Print ("get code> %s"% retStr) - p=re.compile('code\shash: (\w+)\n', re.MULTILINE) + p=re.compile(r'code\shash: (\w+)\n', re.MULTILINE) m=p.search(retStr) if m is None: msg="Failed to parse code hash." @@ -722,8 +778,6 @@ def getAccountCodeHash(self, account): return None return m.group(1) - trans=Node.runCmdReturnJson(cmd, True) - return trans except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") Utils.Print("ERROR: Exception during code hash retrieval. %s" % (msg)) @@ -734,10 +788,10 @@ def publishContract(self, account, contractDir, wastFile, abiFile, waitForTransB cmd="%s %s -v set contract -j %s %s" % (Utils.EosClientPath, self.endpointArgs, account, contractDir) cmd += "" if wastFile is None else (" "+ wastFile) cmd += "" if abiFile is None else (" " + abiFile) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None try: - trans=Node.runCmdReturnJson(cmd) + trans=Node.runCmdReturnJson(cmd, trace=False) except subprocess.CalledProcessError as ex: if not shouldFail: msg=ex.output.decode("utf-8") @@ -757,22 +811,12 @@ def publishContract(self, account, contractDir, wastFile, abiFile, waitForTransB Utils.Print("ERROR: The publish contract did not fail as expected.") return None + Node.validateTransaction(trans) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransIdOnNode(transId): return None return trans - def getTable(self, contract, scope, table): - cmd="%s %s get table %s %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, scope, table) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) - try: - trans=Node.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during table retrieval. %s" % (msg)) - return None - def getTableRows(self, contract, scope, table): jsonData=self.getTable(contract, scope, table) if jsonData is None: @@ -797,15 +841,15 @@ def getTableColumns(self, contract, scope, table): return keys # returns tuple with transaction and - def pushMessage(self, contract, action, data, opts, silentErrors=False): - cmd="%s %s push action -j %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, action) + def pushMessage(self, account, action, data, opts, silentErrors=False): + cmd="%s %s push action -j %s %s" % (Utils.EosClientPath, self.endpointArgs, account, action) cmdArr=cmd.split() if data is not None: cmdArr.append(data) if opts is not None: cmdArr += opts.split() s=" ".join(cmdArr) - Utils.Debug and Utils.Print("cmd: %s" % (s)) + if Utils.Debug: Utils.Print("cmd: %s" % (s)) try: trans=Node.__runCmdArrReturnJson(cmdArr) return (True, trans) @@ -818,7 +862,7 @@ def pushMessage(self, contract, action, data, opts, silentErrors=False): def setPermission(self, account, code, pType, requirement, waitForTransBlock=False): cmd="%s %s set action permission -j %s %s %s %s" % ( Utils.EosClientPath, self.endpointArgs, account, code, pType, requirement) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None try: trans=Node.runCmdReturnJson(cmd) @@ -834,7 +878,7 @@ def setPermission(self, account, code, pType, requirement, waitForTransBlock=Fal def getInfo(self, silentErrors=False): cmd="%s %s get info" % (Utils.EosClientPath, self.endpointArgs) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: trans=Node.runCmdReturnJson(cmd) return trans @@ -847,7 +891,7 @@ def getInfo(self, silentErrors=False): def getBlockFromDb(self, idx): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand="db.Blocks.find().sort({\"_id\":%d}).limit(1).pretty()" % (idx) - Utils.Debug and Utils.Print("cmd: echo \"%s\" | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("cmd: echo \"%s\" | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) return trans @@ -858,10 +902,7 @@ def getBlockFromDb(self, idx): def checkPulse(self): info=self.getInfo(True) - if info is not None: - return True - else: - return False + return False if info is None else True def getHeadBlockNum(self): if not self.enableMongo: @@ -889,30 +930,30 @@ def getIrreversibleBlockNum(self): return None def kill(self, killSignal): + if Utils.Debug: Utils.Print("Killing node: %s" % (self.cmd)) + assert(self.pid is not None) try: - Utils.Debug and Utils.Print("Killing node: %s" % (self.cmd)) os.kill(self.pid, killSignal) + except OSError as ex: + Utils.Print("ERROR: Failed to kill node (%d)." % (self.cmd), ex) + return False - # wait for kill validation - def myFunc(): - try: - os.kill(self.pid, 0) #check if process with pid is running - except Exception as ex: - return True - return False - - lam = lambda: myFunc() - if not Utils.waitForBool(lam): - Utils.Print("ERROR: Failed to kill node (%s)." % (self.cmd), ex) - return False + # wait for kill validation + def myFunc(): + try: + os.kill(self.pid, 0) #check if process with pid is running + except OSError as _: + return True + return False - # mark node as killed - self.killed=True - return True - except Exception as ex: - Utils.Print("ERROR: Failed to kill node (%d)." % (self.cmd), ex) + if not Utils.waitForBool(myFunc): + Utils.Print("ERROR: Failed to kill node (%s)." % (self.cmd), ex) + return False - return False + # mark node as killed + self.pid=None + self.killed=True + return True # TBD: make nodeId an internal property def relaunch(self, nodeId, chainArg): @@ -920,13 +961,13 @@ def relaunch(self, nodeId, chainArg): running=True try: os.kill(self.pid, 0) #check if process with pid is running - except Exception as ex: + except OSError as _: running=False if running: Utils.Print("WARNING: A process with pid (%d) is already running." % (self.pid)) else: - Utils.Debug and Utils.Print("Launching node process, Id: %d" % (nodeId)) + if Utils.Debug: Utils.Print("Launching node process, Id: %d" % (nodeId)) dataDir="var/lib/node_%02d" % (nodeId) dt = datetime.datetime.now() dateStr="%d_%02d_%02d_%02d_%02d_%02d" % ( @@ -942,41 +983,16 @@ def relaunch(self, nodeId, chainArg): self.killed=False return True - def relaunchEosInstances(self): - - chainArg=self.__chainSyncStrategy.arg - - for i in range(0, len(self.nodes)): - node=self.nodes[i] - running=True - try: - os.kill(node.pid, 0) #check if instance is running - except Exception as ex: - running=False - - if running is False: - dataDir="var/lib/node_%02d" % (i) - dt = datetime.datetime.now() - dateStr="%d_%02d_%02d_%02d_%02d_%02d" % ( - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) - stdoutFile="%s/stdout.%s.txt" % (dataDir, dateStr) - stderrFile="%s/stderr.%s.txt" % (dataDir, dateStr) - with open(stdoutFile, 'w') as sout, open(stderrFile, 'w') as serr: - cmd=node.cmd + ("" if chainArg is None else (" " + chainArg)) - Utils.Print("cmd: %s" % (cmd)) - popen=subprocess.Popen(cmd.split(), stdout=sout, stderr=serr) - self.nodes[i].pid=popen.pid - - return self.updateNodesStatus() - ########################################################################################### Wallet=namedtuple("Wallet", "name password host port") +# pylint: disable=too-many-instance-attributes class WalletMgr(object): __walletLogFile="test_keosd_output.log" __walletDataDir="test_wallet_0" + # pylint: disable=too-many-arguments # walletd [True|False] True=Launch wallet(keosd) process; False=Manage launch process externally. def __init__(self, walletd, nodeosPort=8888, nodeosHost="localhost", port=8899, host="localhost"): self.walletd=walletd @@ -999,7 +1015,7 @@ def launch(self): cmd="%s --data-dir %s --config-dir %s --http-server-address=%s:%d" % ( Utils.EosWalletPath, WalletMgr.__walletDataDir, WalletMgr.__walletDataDir, self.host, self.port) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) with open(WalletMgr.__walletLogFile, 'w') as sout, open(WalletMgr.__walletLogFile, 'w') as serr: popen=subprocess.Popen(cmd.split(), stdout=sout, stderr=serr) self.__walletPid=popen.pid @@ -1011,11 +1027,11 @@ def launch(self): def create(self, name): wallet=self.wallets.get(name) if wallet is not None: - Utils.Debug and Utils.Print("Wallet \"%s\" already exists. Returning same." % name) + if Utils.Debug: Utils.Print("Wallet \"%s\" already exists. Returning same." % name) return wallet - p = re.compile('\n\"(\w+)\"\n', re.MULTILINE) + p = re.compile(r'\n\"(\w+)\"\n', re.MULTILINE) cmd="%s %s wallet create --name %s" % (Utils.EosClientPath, self.endpointArgs, name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) retStr=subprocess.check_output(cmd.split()).decode("utf-8") #Utils.Print("create: %s" % (retStr)) m=p.search(retStr) @@ -1032,9 +1048,9 @@ def importKey(self, account, wallet): warningMsg="Key already in wallet" cmd="%s %s wallet import --name %s %s" % ( Utils.EosClientPath, self.endpointArgs, wallet.name, account.ownerPrivateKey) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: - retStr=subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") if warningMsg in msg: @@ -1048,9 +1064,9 @@ def importKey(self, account, wallet): else: cmd="%s %s wallet import --name %s %s" % ( Utils.EosClientPath, self.endpointArgs, wallet.name, account.activePrivateKey) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) try: - retStr=subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") if warningMsg in msg: @@ -1064,7 +1080,7 @@ def importKey(self, account, wallet): def lockWallet(self, wallet): cmd="%s %s wallet lock --name %s" % (Utils.EosClientPath, self.endpointArgs, wallet.name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull): Utils.Print("ERROR: Failed to lock wallet %s." % (wallet.name)) return False @@ -1073,9 +1089,9 @@ def lockWallet(self, wallet): def unlockWallet(self, wallet): cmd="%s %s wallet unlock --name %s" % (Utils.EosClientPath, self.endpointArgs, wallet.name) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) popen=subprocess.Popen(cmd.split(), stdout=Utils.FNull, stdin=subprocess.PIPE) - outs, errs = popen.communicate(input=wallet.password.encode("utf-8")) + _, errs = popen.communicate(input=wallet.password.encode("utf-8")) if 0 != popen.wait(): Utils.Print("ERROR: Failed to unlock wallet %s: %s" % (wallet.name, errs.decode("utf-8"))) return False @@ -1084,7 +1100,7 @@ def unlockWallet(self, wallet): def lockAllWallets(self): cmd="%s %s wallet lock_all" % (Utils.EosClientPath, self.endpointArgs) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull): Utils.Print("ERROR: Failed to lock all wallets.") return False @@ -1094,9 +1110,9 @@ def lockAllWallets(self): def getOpenWallets(self): wallets=[] - p = re.compile('\s+\"(\w+)\s\*\",?\n', re.MULTILINE) + p = re.compile(r'\s+\"(\w+)\s\*\",?\n', re.MULTILINE) cmd="%s %s wallet list" % (Utils.EosClientPath, self.endpointArgs) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) retStr=subprocess.check_output(cmd.split()).decode("utf-8") #Utils.Print("retStr: %s" % (retStr)) m=p.findall(retStr) @@ -1110,9 +1126,9 @@ def getOpenWallets(self): def getKeys(self): keys=[] - p = re.compile('\n\s+\"(\w+)\"\n', re.MULTILINE) + p = re.compile(r'\n\s+\"(\w+)\"\n', re.MULTILINE) cmd="%s %s wallet keys" % (Utils.EosClientPath, self.endpointArgs) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) retStr=subprocess.check_output(cmd.split()).decode("utf-8") #Utils.Print("retStr: %s" % (retStr)) m=p.findall(retStr) @@ -1132,12 +1148,14 @@ def dumpErrorDetails(self): with open(WalletMgr.__walletLogFile, "r") as f: shutil.copyfileobj(f, sys.stdout) - def killall(self): + @staticmethod + def killall(): cmd="pkill -9 %s" % (Utils.EosWalletName) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) subprocess.call(cmd.split()) - def cleanup(self): + @staticmethod + def cleanup(): dataDir=WalletMgr.__walletDataDir if os.path.isdir(dataDir) and os.path.exists(dataDir): shutil.rmtree(WalletMgr.__walletDataDir) @@ -1147,13 +1165,14 @@ def cleanup(self): ########################################################################################### class Cluster(object): __chainSyncStrategies=Utils.getChainStrategies() + __chainSyncStrategy=None __WalletName="MyWallet" __localHost="localhost" __lastTrans=None __BiosHost="localhost" __BiosPort=8788 - + # pylint: disable=too-many-arguments # walletd [True|False] Is keosd running. If not load the wallet plugin def __init__(self, walletd=False, localCluster=True, host="localhost", port=8888, walletHost="localhost", walletPort=8899, enableMongo=False, mongoHost="localhost", mongoPort=27017, mongoDb="EOStest", initaPrvtKey=None, initbPrvtKey=None, staging=False): """Cluster container. @@ -1205,13 +1224,17 @@ def __init__(self, walletd=False, localCluster=True, host="localhost", port=8888 def setChainStrategy(self, chainSyncStrategy=Utils.SyncReplayTag): self.__chainSyncStrategy=self.__chainSyncStrategies.get(chainSyncStrategy) if self.__chainSyncStrategy is None: - self.__chainSyncStrategy= __chainSyncStrategies.get("none") + self.__chainSyncStrategy=self.__chainSyncStrategies.get("none") def setWalletMgr(self, walletMgr): self.walletMgr=walletMgr # launch local nodes and set self.nodes - def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): + # pylint: disable=too-many-locals + # pylint: disable=too-many-return-statements + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1, onlyBios=False, dontKill=False): """Launch cluster. pnodes: producer nodes count totalNodes: producer + non-producer nodes count @@ -1233,7 +1256,7 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): if self.staging: cmdArr.append("--nogen") - nodeosArgs="" + nodeosArgs="--max-transaction-time 5000" if not self.walletd: nodeosArgs += " --plugin eosio::wallet_api_plugin" if self.enableMongo: @@ -1244,12 +1267,12 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): cmdArr.append(nodeosArgs) s=" ".join(cmdArr) - Utils.Debug and Utils.Print("cmd: %s" % (s)) + if Utils.Debug: Utils.Print("cmd: %s" % (s)) if 0 != subprocess.call(cmdArr): Utils.Print("ERROR: Launcher failed to launch.") return False - self.nodes=range(totalNodes) # placeholder for cleanup purposes only + self.nodes=list(range(totalNodes)) # placeholder for cleanup purposes only nodes=self.discoverLocalNodes(totalNodes, timeout=Utils.systemWaitTimeout) if nodes is None or totalNodes != len(nodes): @@ -1259,6 +1282,15 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): self.nodes=nodes + if onlyBios: + biosNode=Node(Cluster.__BiosHost, Cluster.__BiosPort) + biosNode.setWalletEndpointArgs(self.walletEndpointArgs) + if not biosNode.checkPulse(): + Utils.Print("ERROR: Bios node doesn't appear to be running...") + return False + + self.nodes=[biosNode] + # ensure cluster node are inter-connected by ensuring everyone has block 1 Utils.Print("Cluster viability smoke test. Validate every cluster node has block 1. ") if not self.waitOnClusterBlockNumSync(1): @@ -1266,10 +1298,12 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): return False Utils.Print("Bootstrap cluster.") - if not Cluster.bootstrap(totalNodes, prodCount, Cluster.__BiosHost, Cluster.__BiosPort): + if not Cluster.bootstrap(totalNodes, prodCount, Cluster.__BiosHost, Cluster.__BiosPort, dontKill, onlyBios): Utils.Print("ERROR: Bootstrap failed.") return False + # validate iniX accounts can be retrieved + producerKeys=Cluster.parseClusterKeys(totalNodes) if producerKeys is None: Utils.Print("ERROR: Unable to parse cluster info") @@ -1292,10 +1326,12 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1): return True # Initialize the default nodes (at present just the root node) - def initializeNodes(self, initaPrvtKey=None, initbPrvtKey=None): + def initializeNodes(self, initaPrvtKey=None, initbPrvtKey=None, onlyBios=False): + port=Cluster.__BiosPort if onlyBios else self.port + host=Cluster.__BiosHost if onlyBios else self.host node=Node(self.host, self.port, enableMongo=self.enableMongo, mongoHost=self.mongoHost, mongoPort=self.mongoPort, mongoDb=self.mongoDb) node.setWalletEndpointArgs(self.walletEndpointArgs) - Utils.Debug and Utils.Print("Node:", node) + if Utils.Debug: Utils.Print("Node:", node) node.checkPulse() self.nodes=[node] @@ -1335,7 +1371,7 @@ def initializeNodesFromJson(self, nodesJsonStr): host=n["host"] node=Node(host, port) node.setWalletEndpointArgs(self.walletEndpointArgs) - Utils.Debug and Utils.Print("Node:", node) + if Utils.Debug: Utils.Print("Node:", node) node.checkPulse() nodes.append(node) @@ -1351,11 +1387,11 @@ def setNodes(self, nodes): # Wait on this block number on each cluster node def waitOnClusterSync(self, timeout=None): targetHeadBlockNum=self.nodes[0].getHeadBlockNum() #get root nodes head block num - Utils.Debug and Utils.Print("Head block number on root node: %d" % (targetHeadBlockNum)) + if Utils.Debug: Utils.Print("Head block number on root node: %d" % (targetHeadBlockNum)) if targetHeadBlockNum == -1: return False - return self.waitOnClusterBlockNumSync(targetHeadBlockNum) + return self.waitOnClusterBlockNumSync(targetHeadBlockNum, timeout) def waitOnClusterBlockNumSync(self, targetHeadBlockNum, timeout=None): @@ -1374,10 +1410,10 @@ def doNodesHaveBlockNum(nodes, targetHeadBlockNum): def createAccountKeys(count): accounts=[] p = re.compile('Private key: (.+)\nPublic key: (.+)\n', re.MULTILINE) - for i in range(0, count): + for _ in range(0, count): try: cmd="%s create key" % (Utils.EosClientPath) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) keyStr=subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") m=p.match(keyStr) if m is None: @@ -1388,7 +1424,7 @@ def createAccountKeys(count): ownerPublic=m.group(2) cmd="%s create key" % (Utils.EosClientPath) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) keyStr=subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") m=p.match(keyStr) if m is None: @@ -1405,7 +1441,7 @@ def createAccountKeys(count): account.activePrivateKey=activePrivate account.activePublicKey=activePublic accounts.append(account) - Utils.Debug and Utils.Print("name: %s, key: ['%s', '%s]-owner; ['%s', '%s']-active" % (name, ownerPublic, ownerPrivate, activePublic, activePrivate)) + if Utils.Debug: Utils.Print("name: %s, key: ['%s', '%s]-owner; ['%s', '%s']-active" % (name, ownerPublic, ownerPrivate, activePublic, activePrivate)) except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") @@ -1419,6 +1455,7 @@ def createAccountKeys(count): return accounts # create account keys and import into wallet. Wallet initialization will be user responsibility + # also imports inita and initb accounts def populateWallet(self, accountsCount, wallet): if self.walletMgr is None: Utils.Print("ERROR: WalletMgr hasn't been initialized.") @@ -1451,8 +1488,8 @@ def populateWallet(self, accountsCount, wallet): self.accounts=accounts return True - def getNode(self, id=0): - return self.nodes[id] + def getNode(self, nodeId=0): + return self.nodes[nodeId] def getNodes(self): return self.nodes @@ -1476,14 +1513,14 @@ def spreadFunds(self, amount=1): if transId is None: return False self.__lastTrans=transId - Utils.Debug and Utils.Print("Funds transfered on transaction id %s." % (transId)) + if Utils.Debug: Utils.Print("Funds transfered on transaction id %s." % (transId)) self.accounts[0].balance += transferAmount nextEosIdx=-1 for i in range(0, count): account=self.accounts[i] nextInstanceFound=False - for n in range(0, count): + for _ in range(0, count): #Utils.Print("nextEosIdx: %d, n: %d" % (nextEosIdx, n)) nextEosIdx=(nextEosIdx + 1)%count if not self.nodes[nextEosIdx].killed: @@ -1497,7 +1534,7 @@ def spreadFunds(self, amount=1): #Utils.Print("nextEosIdx: %d, count: %d" % (nextEosIdx, count)) node=self.nodes[nextEosIdx] - Utils.Debug and Utils.Print("Wait for trasaction id %s on node port %d" % (transId, node.port)) + if Utils.Debug: Utils.Print("Wait for trasaction id %s on node port %d" % (transId, node.port)) if node.waitForTransIdOnNode(transId) is False: Utils.Print("ERROR: Selected node never received transaction id %s" % (transId)) return False @@ -1513,15 +1550,15 @@ def spreadFunds(self, amount=1): if transId is None: return False self.__lastTrans=transId - Utils.Debug and Utils.Print("Funds transfered on block num %s." % (transId)) + if Utils.Debug: Utils.Print("Funds transfered on block num %s." % (transId)) self.accounts[i].balance -= transferAmount if i < (count-1): - self.accounts[i+1].balance += transferAmount + self.accounts[i+1].balance += transferAmount # As an extra step wait for last transaction on the root node node=self.nodes[0] - Utils.Debug and Utils.Print("Wait for trasaction id %s on node port %d" % (transId, node.port)) + if Utils.Debug: Utils.Print("Wait for trasaction id %s on node port %d" % (transId, node.port)) if node.waitForTransIdOnNode(transId) is False: Utils.Print("ERROR: Selected node never received transaction id %s" % (transId)) return False @@ -1531,7 +1568,7 @@ def spreadFunds(self, amount=1): def validateSpreadFunds(self, expectedTotal): for node in self.nodes: if not node.killed: - Utils.Debug and Utils.Print("Validate funds on %s server port %d." % + if Utils.Debug: Utils.Print("Validate funds on %s server port %d." % (Utils.EosServerName, node.port)) if node.validateSpreadFundsOnNode(self.initaAccount, self.accounts, expectedTotal) is False: Utils.Print("ERROR: Failed to validate funds on eos node port: %d" % (node.port)) @@ -1540,9 +1577,9 @@ def validateSpreadFunds(self, expectedTotal): return True def spreadFundsAndValidate(self, amount=1): - Utils.Debug and Utils.Print("Get system balance.") + if Utils.Debug: Utils.Print("Get system balance.") initialFunds=self.nodes[0].getSystemBalance(self.initaAccount, self.accounts) - Utils.Debug and Utils.Print("Initial system balance: %d" % (initialFunds)) + if Utils.Debug: Utils.Print("Initial system balance: %d" % (initialFunds)) if False == self.spreadFunds(amount): Utils.Print("ERROR: Failed to spread funds across nodes.") @@ -1559,24 +1596,39 @@ def spreadFundsAndValidate(self, amount=1): # create account, verify account and return transaction id def createAccountAndVerify(self, account, creator, stakedDeposit=1000): - if len(self.nodes) == 0: - Utils.Print("ERROR: No nodes initialized.") - return None + assert(len(self.nodes) > 0) node=self.nodes[0] + trans=node.createInitializeAccount(account, creator, stakedDeposit) + assert(trans) + assert(node.verifyAccount(account)) + return trans - transId=node.createAccount(account, creator, stakedDeposit) + # # create account, verify account and return transaction id + # def createAccountAndVerify(self, account, creator, stakedDeposit=1000): + # if len(self.nodes) == 0: + # Utils.Print("ERROR: No nodes initialized.") + # return None + # node=self.nodes[0] - if transId is not None and node.verifyAccount(account) is not None: - return transId - return None + # transId=node.createAccount(account, creator, stakedDeposit) + + # if transId is not None and node.verifyAccount(account) is not None: + # return transId + # return None + + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False): + assert(len(self.nodes) > 0) + node=self.nodes[0] + trans=node.createInitializeAccount(account, creatorAccount, stakedDeposit, waitForTransBlock) + return trans @staticmethod def nodeNameToId(name): - """Convert node name to decimal id. Node name regex is "node_([\d]+)". "node_bios" is a special name which returns -1. Examples: node_00 => 0, node_21 => 21, node_bios => -1. """ + r"""Convert node name to decimal id. Node name regex is "node_([\d]+)". "node_bios" is a special name which returns -1. Examples: node_00 => 0, node_21 => 21, node_bios => -1. """ if name == "node_bios": return -1 - m=re.search("node_([\d]+)", name) + m=re.search(r"node_([\d]+)", name) return int(m.group(1)) @@ -1588,24 +1640,24 @@ def parseProducerKeys(configFile, nodeName): with open(configFile, 'r') as f: configStr=f.read() - pattern="^\s*private-key\s*=\W+(\w+)\W+(\w+)\W+$" + pattern=r"^\s*private-key\s*=\W+(\w+)\W+(\w+)\W+$" m=re.search(pattern, configStr, re.MULTILINE) if m is None: - Utils.Debug and Utils.Print("Failed to find producer keys") + if Utils.Debug: Utils.Print("Failed to find producer keys") return None pubKey=m.group(1) privateKey=m.group(2) - pattern="^\s*producer-name\s*=\W*(\w+)\W*$" + pattern=r"^\s*producer-name\s*=\W*(\w+)\W*$" matches=re.findall(pattern, configStr, re.MULTILINE) if matches is None: - Utils.Debug and Utils.Print("Failed to find producers.") + if Utils.Debug: Utils.Print("Failed to find producers.") return None producerKeys={} for m in matches: - Utils.Debug and Utils.Print ("Found producer : %s" % (m)) + if Utils.Debug: Utils.Print ("Found producer : %s" % (m)) nodeId=Cluster.nodeNameToId(nodeName) keys={"name": m, "node": nodeId, "private": privateKey, "public": pubKey} producerKeys[m]=keys @@ -1618,7 +1670,7 @@ def parseClusterKeys(totalNodes): node="node_bios" configFile="etc/eosio/%s/config.ini" % (node) - Utils.Debug and Utils.Print("Parsing config file %s" % configFile) + if Utils.Debug: Utils.Print("Parsing config file %s" % configFile) producerKeys=Cluster.parseProducerKeys(configFile, node) if producerKeys is None: Utils.Print("ERROR: Failed to parse eosio private keys from cluster config files.") @@ -1627,7 +1679,7 @@ def parseClusterKeys(totalNodes): for i in range(0, totalNodes): node="node_%02d" % (i) configFile="etc/eosio/%s/config.ini" % (node) - Utils.Debug and Utils.Print("Parsing config file %s" % configFile) + if Utils.Debug: Utils.Print("Parsing config file %s" % configFile) keys=Cluster.parseProducerKeys(configFile, node) if keys is not None: @@ -1636,7 +1688,7 @@ def parseClusterKeys(totalNodes): return producerKeys @staticmethod - def bootstrap(totalNodes, prodCount, biosHost, biosPort): + def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBios=False): """Create 'prodCount' init accounts and deposits 10000000000 EOS in each. If prodCount is -1 will initialize all possible producers. Ensure nodes are inter-connected prior to this call. One way to validate this will be to check if every node has block 1.""" @@ -1686,6 +1738,8 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort): Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return False + Node.validateTransaction(trans) + Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) producerKeys.pop(eosioName) for name, keys in producerKeys.items(): @@ -1698,118 +1752,156 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort): if trans is None: Utils.Print("ERROR: Failed to create account %s" % (name)) return False + Node.validateTransaction(trans) + transId=Node.getTransId(trans) biosNode.waitForTransIdOnNode(transId) - if prodCount == -1: - setProdsFile="setprods.json" - Utils.Debug and Utils.Print("Reading in setprods file %s." % (setProdsFile)) - with open(setProdsFile, "r") as f: - setProdsStr=f.read() - - Utils.Print("Setting producers.") + if not onlyBios: + if prodCount == -1: + setProdsFile="setprods.json" + if Utils.Debug: Utils.Print("Reading in setprods file %s." % (setProdsFile)) + with open(setProdsFile, "r") as f: + setProdsStr=f.read() + + Utils.Print("Setting producers.") + opts="--permission eosio@active" + myTrans=biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) + if myTrans is None or not myTrans[0]: + Utils.Print("ERROR: Failed to set producers.") + return False + else: + counts=dict.fromkeys(range(totalNodes), 0) #initialize node prods count to 0 + setProdsStr='{"schedule": [' + firstTime=True + prodNames=[] + for name, keys in producerKeys.items(): + if counts[keys["node"]] >= prodCount: + continue + if firstTime: + firstTime = False + else: + setProdsStr += ',' + + setProdsStr += ' { "producer_name": "%s", "block_signing_key": "%s" }' % (keys["name"], keys["public"]) + prodNames.append(keys["name"]) + counts[keys["node"]] += 1 + + setProdsStr += ' ] }' + if Utils.Debug: Utils.Print("setprods: %s" % (setProdsStr)) + Utils.Print("Setting producers: %s." % (", ".join(prodNames))) opts="--permission eosio@active" trans=biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) if trans is None or not trans[0]: - Utils.Print("ERROR: Failed to set producers.") + Utils.Print("ERROR: Failed to set producer %s." % (keys["name"])) return False - else: - counts=dict.fromkeys(range(totalNodes), 0) #initialize node prods count to 0 - setProdsStr='{ "version": 1, "producers": [' - firstTime=True - prodNames=[] - for name, keys in producerKeys.items(): - if counts[keys["node"]] >= prodCount: - continue - if firstTime: - firstTime = False - else: - setProdsStr += ',' - - setProdsStr += ' { "producer_name": "%s", "block_signing_key": "%s" }' % (keys["name"], keys["public"]) - prodNames.append(keys["name"]) - counts[keys["node"]] += 1 - - setProdsStr += ' ] }' - Utils.Debug and Utils.Print("setprods: %s" % (setProdsStr)) - Utils.Print("Setting producers: %s." % (", ".join(prodNames))) - opts="--permission eosio@active" - trans=biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) - if trans is None or not trans[0]: - Utils.Print("ERROR: Failed to set producer %s." % (keys["name"])) + + trans=trans[1] + transId=Node.getTransId(trans) + if not biosNode.waitForTransIdOnNode(transId): return False - trans=trans[1] - transId=Node.getTransId(trans) - if not biosNode.waitForTransIdOnNode(transId): - return False + # wait for block production handover (essentially a block produced by anyone but eosio). + lam = lambda: biosNode.getInfo()["head_block_producer"] != "eosio" + ret=Utils.waitForBool(lam) + if not ret: + Utils.Print("ERROR: Block production handover failed.") + return False - # wait for block production handover (essentially a block produced by anyone but eosio). - lam = lambda: biosNode.getInfo()["head_block_producer"] != "eosio" - ret=Utils.waitForBool(lam) - if not ret: - Utils.Print("ERROR: Block production handover failed.") + eosioTokenAccount=copy.deepcopy(eosioAccount) + eosioTokenAccount.name="eosio.token" + trans=biosNode.createAccount(eosioTokenAccount, eosioAccount, 0) + if trans is None: + Utils.Print("ERROR: Failed to create account %s" % (eosioTokenAccount.name)) return False - contract="eosio.system" + Node.validateTransaction(trans) + transId=Node.getTransId(trans) + biosNode.waitForTransIdOnNode(transId) + + contract="eosio.token" contractDir="contracts/%s" % (contract) wastFile="contracts/%s/%s.wast" % (contract, contract) abiFile="contracts/%s/%s.abi" % (contract, contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return False - - # TBD: Create currency, followed by issue currency - Utils.Print("push issue action to eosio contract") - contract=eosioAccount.name + # Create currency, followed by issue currency + contract=eosioTokenAccount.name + Utils.Print("push create action to %s contract" % (contract)) + action="create" + data="{\"issuer\":\"%s\",\"maximum_supply\":\"1000000000.0000 EOS\",\"can_freeze\":\"0\",\"can_recall\":\"0\",\"can_whitelist\":\"0\"}" % (eosioTokenAccount.name) + opts="--permission %s@active" % (contract) + trans=biosNode.pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + Utils.Print("ERROR: Failed to push create action to eosio contract.") + return False + + Node.validateTransaction(trans[1]) + + contract=eosioTokenAccount.name + Utils.Print("push issue action to %s contract" % (contract)) action="issue" - data="{\"to\":\"eosio\",\"quantity\":\"1000000000.0000 EOS\"}" - opts="--permission eosio@active" + data="{\"to\":\"%s\",\"quantity\":\"1000000000.0000 EOS\",\"memo\":\"initial issue\"}" % (eosioAccount.name) + opts="--permission %s@active" % (contract) trans=biosNode.pushMessage(contract, action, data, opts) if trans is None or not trans[0]: Utils.Print("ERROR: Failed to push issue action to eosio contract.") return False + Node.validateTransaction(trans[1]) Utils.Print("Wait for issue action transaction to become finalized.") transId=Node.getTransId(trans[1]) biosNode.waitForTransIdOnNode(transId) - # TBD: Known issue (Issue 2043) that 'get currency balance' doesn't return balance. - # Uncomment when functional - # expectedAmount=10000000000000 - # Utils.Print("Verify eosio issue, Expected: %d" % (expectedAmount)) - # actualAmount=biosNode.getAccountBalance(eosioAccount.name) - # if expectedAmount != actualAmount: - # Utils.Print("ERROR: Issue verification failed. Excepted %d, actual: %d" % - # (expectedAmount, actualAmount)) - # return False - - initialFunds=10000000000 - Utils.Print("Transfer initial fund %d to individual accounts." % (initialFunds)) + expectedAmount="1000000000.0000 EOS" + Utils.Print("Verify eosio issue, Expected: %s" % (expectedAmount)) + actualAmount=biosNode.getAccountEosBalanceStr(eosioAccount.name) + if expectedAmount != actualAmount: + Utils.Print("ERROR: Issue verification failed. Excepted %s, actual: %s" % + (expectedAmount, actualAmount)) + return False + + contract="eosio.system" + contractDir="contracts/%s" % (contract) + wastFile="contracts/%s/%s.wast" % (contract, contract) + abiFile="contracts/%s/%s.abi" % (contract, contract) + Utils.Print("Publish %s contract" % (contract)) + trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + if trans is None: + Utils.Print("ERROR: Failed to publish contract %s." % (contract)) + return False + + Node.validateTransaction(trans) + + initialFunds="1000000.0000 EOS" + Utils.Print("Transfer initial fund %s to individual accounts." % (initialFunds)) trans=None + contract=eosioTokenAccount.name + action="transfer" for name, keys in producerKeys.items(): - initx = Account(name) - initx.ownerPrivateKey=keys["private"] - initx.ownerPublicKey=keys["public"] - initx.activePrivateKey=keys["private"] - initx.activePublicKey=keys["public"] - trans = biosNode.transferFunds(eosioAccount, initx, initialFunds, "init transfer") - if trans is None: - Utils.Print("ERROR: Failed to transfer funds from %s to %s." % (eosioAccount.name, name)) + data="{\"from\":\"%s\",\"to\":\"%s\",\"quantity\":\"%s\",\"memo\":\"%s\"}" % (eosioAccount.name, name, initialFunds, "init transfer") + opts="--permission %s@active" % (eosioAccount.name) + trans=biosNode.pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + Utils.Print("ERROR: Failed to transfer funds from %s to %s." % (eosioTokenAccount.name, name)) return False + Node.validateTransaction(trans[1]) + Utils.Print("Wait for last transfer transaction to become finalized.") - transId=Node.getTransId(trans) + transId=Node.getTransId(trans[1]) if not biosNode.waitForTransIdOnNode(transId): return False Utils.Print("Cluster bootstrap done.") finally: - walletMgr.killall() - walletMgr.cleanup() + if not dontKill: + walletMgr.killall() + walletMgr.cleanup() return True @@ -1827,39 +1919,32 @@ def discoverLocalNodes(self, totalNodes, timeout=0): def myFunc(): psOut=None try: - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) psOut=subprocess.check_output(cmd.split()).decode("utf-8") return psOut - except subprocess.CalledProcessError as ex: + except subprocess.CalledProcessError as _: pass return None - lam = lambda: myFunc() - psOut=Utils.waitForObj(lam) + psOut=Utils.waitForObj(myFunc, timeout) if psOut is None: Utils.Print("ERROR: No nodes discovered.") return nodes - Utils.Debug and Utils.Print("pgrep output: \"%s\"" % psOut) + if Utils.Debug: Utils.Print("pgrep output: \"%s\"" % psOut) for i in range(0, totalNodes): - pattern="[\n]?(\d+) (.* --data-dir var/lib/node_%02d)" % (i) + pattern=r"[\n]?(\d+) (.* --data-dir var/lib/node_%02d)" % (i) m=re.search(pattern, psOut, re.MULTILINE) if m is None: Utils.Print("ERROR: Failed to find %s pid. Pattern %s" % (Utils.EosServerName, pattern)) break instance=Node(self.host, self.port + i, pid=int(m.group(1)), cmd=m.group(2), enableMongo=self.enableMongo, mongoHost=self.mongoHost, mongoPort=self.mongoPort, mongoDb=self.mongoDb) instance.setWalletEndpointArgs(self.walletEndpointArgs) - Utils.Debug and Utils.Print("Node>", instance) + if Utils.Debug: Utils.Print("Node>", instance) nodes.append(instance) return nodes - # Check state of running nodeos process and update EosInstanceInfos - #def updateEosInstanceInfos(eosInstanceInfos): - # def updateNodesStatus(self): - # for node in self.nodes: - # node.checkPulse() - # Kills a percentange of Eos instances starting from the tail and update eosInstanceInfos state def killSomeEosInstances(self, killCount, killSignalStr=Utils.SigKillTag): killSignal=signal.SIGKILL @@ -1877,7 +1962,6 @@ def killSomeEosInstances(self, killCount, killSignalStr=Utils.SigKillTag): break time.sleep(1) # Give processes time to stand down - # return self.updateNodesStatus() return True def relaunchEosInstances(self): @@ -1891,7 +1975,8 @@ def relaunchEosInstances(self): return True - def dumpErrorDetailImpl(self,fileName): + @staticmethod + def dumpErrorDetailImpl(fileName): Utils.Print("=================================================================") Utils.Print("Contents of %s:" % (fileName)) if os.path.exists(fileName): @@ -1902,44 +1987,45 @@ def dumpErrorDetailImpl(self,fileName): def dumpErrorDetails(self): fileName="etc/eosio/node_bios/config.ini" - self.dumpErrorDetailImpl(fileName) + Cluster.dumpErrorDetailImpl(fileName) fileName="var/lib/node_bios/stderr.txt" - self.dumpErrorDetailImpl(fileName) + Cluster.dumpErrorDetailImpl(fileName) for i in range(0, len(self.nodes)): fileName="etc/eosio/node_%02d/config.ini" % (i) - self.dumpErrorDetailImpl(fileName) + Cluster.dumpErrorDetailImpl(fileName) fileName="var/lib/node_%02d/stderr.txt" % (i) - self.dumpErrorDetailImpl(fileName) + Cluster.dumpErrorDetailImpl(fileName) def killall(self, silent=True): cmd="%s -k 15" % (Utils.EosLauncherPath) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull): - not silent and Utils.Print("Launcher failed to shut down eos cluster.") + if not silent: Utils.Print("Launcher failed to shut down eos cluster.") # ocassionally the launcher cannot kill the eos server cmd="pkill -9 %s" % (Utils.EosServerName) - Utils.Debug and Utils.Print("cmd: %s" % (cmd)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) if 0 != subprocess.call(cmd.split(), stdout=Utils.FNull): - not silent and Utils.Print("Failed to shut down eos cluster.") + if not silent: Utils.Print("Failed to shut down eos cluster.") # another explicit nodes shutdown for node in self.nodes: try: - os.kill(node.pid, signal.SIGKILL) - except: + if node.pid is not None: + os.kill(node.pid, signal.SIGKILL) + except OSError as _: pass def isMongodDbRunning(self): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand="db.version()" - Utils.Debug and Utils.Print("echo %s | %s" % (subcommand, cmd)) + if Utils.Debug: Utils.Print("echo %s | %s" % (subcommand, cmd)) ret,outs,errs=Node.stdinAndCheckOutput(cmd.split(), subcommand) if ret is not 0: Utils.Print("ERROR: Failed to check database version: %s" % (Node.byteArrToStr(errs)) ) return False - Utils.Debug and Utils.Print("MongoDb response: %s" % (outs)) + if Utils.Debug: Utils.Print("MongoDb response: %s" % (outs)) return True def waitForNextBlock(self, timeout=None): @@ -1957,8 +2043,8 @@ def cleanup(self): if self.enableMongo: cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand="db.dropDatabase()" - Utils.Debug and Utils.Print("echo %s | %s" % (subcommand, cmd)) - ret,outs,errs=Node.stdinAndCheckOutput(cmd.split(), subcommand) + if Utils.Debug: Utils.Print("echo %s | %s" % (subcommand, cmd)) + ret,_,errs=Node.stdinAndCheckOutput(cmd.split(), subcommand) if ret is not 0: Utils.Print("ERROR: Failed to drop database: %s" % (Node.byteArrToStr(errs)) ) @@ -1970,17 +2056,17 @@ def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000): transId=None for account in self.accounts: - Utils.Debug and Utils.Print("Create account %s." % (account.name)) + if Utils.Debug: Utils.Print("Create account %s." % (account.name)) trans=self.createAccountAndVerify(account, creator, stakedDeposit) if trans is None: Utils.Print("ERROR: Failed to create account %s." % (account.name)) return False - Utils.Debug and Utils.Print("Account %s created." % (account.name)) + if Utils.Debug: Utils.Print("Account %s created." % (account.name)) transId=Node.getTransId(trans) if waitForTransBlock and transId is not None: node=self.nodes[0] - Utils.Debug and Utils.Print("Wait for transaction id %s on server port %d." % ( transId, node.port)) + if Utils.Debug: Utils.Print("Wait for transaction id %s on server port %d." % ( transId, node.port)) if node.waitForTransIdOnNode(transId) is False: Utils.Print("ERROR: Failed waiting for transaction id %s on server port %d." % ( transId, node.port)) diff --git a/tests/tests/block_schedule_tests.cpp b/tests/tests/block_schedule_tests.cpp deleted file mode 100644 index 716245d69d8..00000000000 --- a/tests/tests/block_schedule_tests.cpp +++ /dev/null @@ -1,361 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#include - -#include -#include "../common/expect.hpp" - -using namespace eosio; -using namespace chain; - -/* - * templated meta schedulers for fuzzing - */ -template -struct shuffled_functor { - shuffled_functor(NEXT &&_next) : next(_next){}; - - block_schedule operator()(const vector& transactions, const global_property_object& properties) { - std::random_device rd; - std::mt19937 rng(rd()); - auto copy = std::vector(transactions); - std::shuffle(copy.begin(), copy.end(), rng); - return next(copy, properties); - } - - NEXT &&next; -}; - -template -static shuffled_functor shuffled(NEXT &&next) { - return shuffled_functor(next); -} - -template -struct lossy_functor { - lossy_functor(NEXT &&_next) : next(_next){}; - - block_schedule operator()(const vector& transactions, const global_property_object& properties) { - std::random_device rd; - std::mt19937 rng(rd()); - std::uniform_real_distribution<> dist(0, 1); - double const cutoff = (double)NUM / (double)DEN; - - auto copy = std::vector(); - copy.reserve(transactions.size()); - std::copy_if (transactions.begin(), transactions.end(), copy.begin(), [&](const pending_transaction& trx){ - return dist(rng) >= cutoff; - }); - - return next(copy, properties); - } - - NEXT &&next; -}; - -template -static lossy_functor lossy(NEXT &&next) { - return lossy_functor(next); -} - -/* - * Policy based Fixtures for chain properties - */ -class common_fixture { -public: - struct test_transaction { - test_transaction(const std::initializer_list& _scopes) - : scopes(_scopes) - { - } - - const std::initializer_list& scopes; - }; - -protected: - auto create_transactions( const std::initializer_list& transactions ) { - std::vector result; - for (const auto& t: transactions) { - signed_transaction st; - st.scope.reserve(t.scopes.size()); - st.scope.insert(st.scope.end(), t.scopes.begin(), t.scopes.end()); - result.emplace_back(st); - } - return result; - } - - auto create_pending( const std::vector& transactions ) { - std::vector result; - for (const auto& t: transactions) { - result.emplace_back(std::reference_wrapper {t}); - } - return result; - } -}; - -template -class compose_fixture: public common_fixture { -public: - template - void schedule_and_validate(SCHED_FN sched_fn, const std::initializer_list& transactions, VALIDATORS ...validators) { - try { - auto signed_transactions = create_transactions(transactions); - auto pending = create_pending(signed_transactions); - auto schedule = sched_fn(pending, properties_policy.properties); - validate(schedule, validators...); - } FC_LOG_AND_RETHROW() - } - -private: - template - void validate(const block_schedule& schedule, VALIDATOR validator) { - validator(schedule); - } - - template - void validate(const block_schedule& schedule, VALIDATOR validator, VALIDATORS ... others) { - validate(schedule, validator); - validate(schedule, others...); - } - - - PROPERTIES_POLICY properties_policy; -}; - -static void null_global_property_object_constructor(const global_property_object& ) -{} - -static chainbase::allocator null_global_property_object_allocator(nullptr); - -struct base_properties { - base_properties() - : properties(null_global_property_object_constructor, null_global_property_object_allocator) - { - } - - global_property_object properties; -}; - -struct default_properties : public base_properties { - default_properties() { - properties.configuration.max_blk_size = 256 * 1024; - } -}; - -struct small_block_properties : public base_properties { - small_block_properties() { - properties.configuration.max_blk_size = 512; - } -}; - -typedef compose_fixture default_fixture; - -/* - * Evaluators for expect - */ -static uint transaction_count(const block_schedule& schedule) { - uint result = 0; - for (const auto& c : schedule.cycles) { - for (const auto& t: c) { - result += t.transactions.size(); - } - } - - return result; -} - -static uint cycle_count(const block_schedule& schedule) { - return schedule.cycles.size(); -} - - - -static bool schedule_is_valid(const block_schedule& schedule) { - for (const auto& c : schedule.cycles) { - std::vector scope_in_use; - for (const auto& t: c) { - std::set thread_bits; - size_t max_bit = 0; - for(const auto& pt: t.transactions) { - auto scopes = pt.visit(scope_extracting_visitor()); - for (const auto& s : scopes) { - size_t bit = boost::numeric_cast((uint64_t)s); - thread_bits.emplace(bit); - max_bit = std::max(max_bit, bit); - } - } - - if (scope_in_use.size() <= max_bit) { - scope_in_use.resize(max_bit + 1); - } - - for ( auto b: thread_bits ) { - if(scope_in_use.at(b)) { - return false; - }; - scope_in_use.at(b) = true; - } - } - } - - return true; -} - -/* - * Test Cases - */ - -BOOST_AUTO_TEST_SUITE(block_schedule_tests) - -BOOST_FIXTURE_TEST_CASE(no_conflicts, default_fixture) { - // ensure the scheduler can handle basic non-conflicted transactions - // in a single cycle - schedule_and_validate( - block_schedule::by_threading_conflicts, - { - {0x1ULL, 0x2ULL}, - {0x3ULL, 0x4ULL}, - {0x5ULL, 0x6ULL}, - {0x7ULL, 0x8ULL}, - {0x9ULL, 0xAULL} - }, - EXPECT(schedule_is_valid), - EXPECT(transaction_count, 5), - EXPECT(cycle_count, 1) - ); -} - -BOOST_FIXTURE_TEST_CASE(some_conflicts, default_fixture) { - // ensure the scheduler can handle conflicted transactions - // using multiple cycles - schedule_and_validate( - block_schedule::by_threading_conflicts, - { - {0x1ULL, 0x2ULL}, - {0x3ULL, 0x2ULL}, - {0x5ULL, 0x1ULL}, - {0x7ULL, 0x1ULL}, - {0x1ULL, 0x7ULL} - }, - EXPECT(schedule_is_valid), - EXPECT(transaction_count, 5), - EXPECT(std::greater, cycle_count, 1) - ); -} - -BOOST_FIXTURE_TEST_CASE(basic_cycle, default_fixture) { - // ensure the scheduler can handle a basic scope cycle - schedule_and_validate( - block_schedule::by_threading_conflicts, - { - {0x1ULL, 0x2ULL}, - {0x2ULL, 0x3ULL}, - {0x3ULL, 0x1ULL}, - }, - EXPECT(schedule_is_valid), - EXPECT(transaction_count, 3) - ); -} - -BOOST_FIXTURE_TEST_CASE(small_block, compose_fixture) { - // ensure the scheduler can handle basic block size restrictions - schedule_and_validate( - block_schedule::by_threading_conflicts, - { - {0x1ULL, 0x2ULL}, - {0x3ULL, 0x4ULL}, - {0x5ULL, 0x6ULL}, - {0x7ULL, 0x8ULL}, - {0x9ULL, 0xAULL}, - {0xBULL, 0xCULL}, - {0xDULL, 0xEULL}, - {0x11ULL, 0x12ULL}, - {0x13ULL, 0x14ULL}, - {0x15ULL, 0x16ULL}, - {0x17ULL, 0x18ULL}, - {0x19ULL, 0x1AULL}, - {0x1BULL, 0x1CULL}, - {0x1DULL, 0x1EULL}, - {0x21ULL, 0x22ULL}, - {0x23ULL, 0x24ULL}, - {0x25ULL, 0x26ULL}, - {0x27ULL, 0x28ULL}, - {0x29ULL, 0x2AULL}, - {0x2BULL, 0x2CULL}, - {0x2DULL, 0x2EULL}, - }, - EXPECT(schedule_is_valid), - EXPECT(std::less, transaction_count, 21) - ); -} - -BOOST_FIXTURE_TEST_CASE(no_conflicts_shuffled, default_fixture) { - // stochastically verify that the order of non-conflicting transactions - // does not affect the ability to schedule them in a single cycle - for (int i = 0; i < 3000; i++) { - schedule_and_validate( - shuffled(block_schedule::by_threading_conflicts), - { - {0x1ULL, 0x2ULL}, - {0x3ULL, 0x4ULL}, - {0x5ULL, 0x6ULL}, - {0x7ULL, 0x8ULL}, - {0x9ULL, 0xAULL}, - {0xBULL, 0xCULL}, - {0xDULL, 0xEULL}, - {0x11ULL, 0x12ULL}, - {0x13ULL, 0x14ULL}, - {0x15ULL, 0x16ULL}, - {0x17ULL, 0x18ULL}, - {0x19ULL, 0x1AULL}, - {0x1BULL, 0x1CULL}, - {0x1DULL, 0x1EULL}, - {0x21ULL, 0x22ULL}, - {0x23ULL, 0x24ULL}, - {0x25ULL, 0x26ULL}, - {0x27ULL, 0x28ULL}, - {0x29ULL, 0x2AULL}, - {0x2BULL, 0x2CULL}, - {0x2DULL, 0x2EULL}, - }, - EXPECT(schedule_is_valid), - EXPECT(transaction_count, 21), - EXPECT(cycle_count, 1) - ); - } -} - -BOOST_FIXTURE_TEST_CASE(some_conflicts_shuffled, default_fixture) { - // stochastically verify that the order of conflicted transactions - // does not affect the ability to schedule them in multiple cycles - for (int i = 0; i < 3000; i++) { - schedule_and_validate( - shuffled(block_schedule::by_threading_conflicts), - { - {0x1ULL, 0x2ULL}, - {0x3ULL, 0x2ULL}, - {0x5ULL, 0x1ULL}, - {0x7ULL, 0x1ULL}, - {0x1ULL, 0x7ULL}, - {0x11ULL, 0x12ULL}, - {0x13ULL, 0x12ULL}, - {0x15ULL, 0x11ULL}, - {0x17ULL, 0x11ULL}, - {0x11ULL, 0x17ULL}, - {0x21ULL, 0x22ULL}, - {0x23ULL, 0x22ULL}, - {0x25ULL, 0x21ULL}, - {0x27ULL, 0x21ULL}, - {0x21ULL, 0x27ULL} - }, - EXPECT(schedule_is_valid), - EXPECT(transaction_count, 15), - EXPECT(std::greater, cycle_count, 1) - ); - } -} - -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/tests/contracts/rate_limit_auth/CMakeLists.txt b/tests/tests/contracts/rate_limit_auth/CMakeLists.txt deleted file mode 100644 index e36cdc701c8..00000000000 --- a/tests/tests/contracts/rate_limit_auth/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_wast_target(rate_limit_auth "${CMAKE_SOURCE_DIR}/contracts" ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/tests/contracts/rate_limit_auth/rate_limit_auth.cpp b/tests/tests/contracts/rate_limit_auth/rate_limit_auth.cpp deleted file mode 100644 index e7d3703fd78..00000000000 --- a/tests/tests/contracts/rate_limit_auth/rate_limit_auth.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include - -extern "C" { - void init() - { - } - - void test_auths(const currency::transfer& auth) - { - require_auth( auth.from ); - require_auth( auth.to ); - } - - /// The apply method implements the dispatch of events to this contract - void apply( uint64_t code, uint64_t action ) - { - if( code == N(test1) || code == N(test5) ) - { - if( action == N(transfer) ) - { - test_auths( eosio::current_message< currency::transfer >() ); - } - } - } -} diff --git a/tests/tests/types_tests.cpp b/tests/tests/types_tests.cpp deleted file mode 100644 index 80fd0cbe1bf..00000000000 --- a/tests/tests/types_tests.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include - -#include - -namespace eosio { namespace types { - -BOOST_AUTO_TEST_SUITE(types_tests) - - -/// Put the SimpleSymbolTable through its paces -BOOST_AUTO_TEST_CASE(basic_simple_symbol_table_test) -{ try { - simple_symbol_table table; - - BOOST_CHECK_THROW(table.get_type("struct_t"), type_exception); - BOOST_CHECK_THROW(table.get_type("Foo"), unknown_type_exception); - - BOOST_CHECK_THROW(table.add_type({"test type", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Testtype[]", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Testtype]", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Testtype[", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Test[]type", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Test]type", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Test[type", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Test!type", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"!Testtype", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"!testtype", "", {{"f1", "string"}}}), invalid_type_name_exception); - BOOST_CHECK_THROW(table.add_type({"Testtype!", "", {{"f1", "string"}}}), invalid_type_name_exception); - table.add_type({"Testtype", "", {{"f1", "string"}}}); - table.add_type({"Testtype1", "", {{"f1", "string"}}}); - table.add_type({"Test2type", "", {{"f1", "string"}}}); - table.add_type({"test_type", "", {{"f1", "string"}}}); - table.add_type({"_test_type", "", {{"f1", "string"}}}); - table.add_type({"test_type_", "", {{"f1", "string"}}}); - BOOST_CHECK_THROW(table.add_type({"Testtype", "", {{"f1", "string"}}}), duplicate_type_exception); - - BOOST_CHECK_THROW(table.add_type_def("Testtype", "Testtype"), duplicate_type_exception); - BOOST_CHECK_THROW(table.add_type_def("Testtype5", "NewType"), unknown_type_exception); - table.add_type_def("Testtype", "TestType"); - BOOST_CHECK_THROW(table.add_type_def("Testtype", "TestType"), duplicate_type_exception); - BOOST_CHECK_THROW(table.add_type_def("Testtype", "NewType[]"), type_exception); - - BOOST_CHECK(table.is_known_type("Testtype")); - BOOST_CHECK(table.is_known_type("Testtype1")); - BOOST_CHECK(table.is_known_type("Test2type")); - BOOST_CHECK(table.is_known_type("TestType")); - BOOST_CHECK(table.is_known_type("Testtype[]")); - BOOST_CHECK(!table.is_known_type("Testtype5")); - BOOST_CHECK(!table.is_known_type("NewType")); - BOOST_CHECK(!table.is_known_type("NewType[]")); - - BOOST_CHECK_THROW(table.add_type({"Foo", "Bar", {{"f1", "string"}}}), unknown_type_exception); - BOOST_CHECK_THROW(table.add_type({"Foo", "Foo", {{"f1", "string"}}}), unknown_type_exception); - BOOST_CHECK_THROW(table.add_type({"Foo", "", {{"f1", "Bar"}}}), unknown_type_exception); - BOOST_CHECK_THROW(table.add_type({"Foo", "", {{"f1", "Foo"}}}), unknown_type_exception); - table.add_type({"Foo", "", {{"f1", "string"}}}); - table.add_type({"FooBar", "Foo", {{"f2", "int32"}}}); - BOOST_CHECK(table.is_known_type("Foo")); - BOOST_CHECK(table.is_known_type("FooBar")); - BOOST_CHECK(!table.is_known_type("Bar")); - - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{"F1", "struct_t"}}}), invalid_field_name_exception); - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{"f!", "struct_t"}}}), invalid_field_name_exception); - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{"fg ", "struct_t"}}}), invalid_field_name_exception); - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{"f g", "struct_t"}}}), invalid_field_name_exception); - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{" fg", "struct_t"}}}), invalid_field_name_exception); - BOOST_CHECK_THROW(table.add_type({"Bar", "", {{"0fg", "struct_t"}}}), invalid_field_name_exception); - table.add_type({"Bar", "", {{"f0g", "struct_t"}}}); - BOOST_CHECK(table.is_known_type("Bar")); -} FC_LOG_AND_RETHROW() } - -/// Check that SimpleSymbolTable can handle a basic schema parse -BOOST_AUTO_TEST_CASE(simple_symbol_table_parse_test) -{ try { - simple_symbol_table table; - std::string schema = R"x( -struct foo - bar string - baz int8 -struct Bar - foo int32 - baz signature -typedef Bar Baz -typedef Baz Qux -)x"; - std::istringstream schema_string(schema); - table.parse(schema_string); - - BOOST_CHECK(table.is_known_type("foo")); - BOOST_CHECK(table.is_known_type("Bar")); - BOOST_CHECK(table.is_known_type("Baz")); - - struct_t foo = {"foo", "", {{"bar", "string"}, {"baz", "int8"}}}; - struct_t bar = {"Bar", "", {{"foo", "int32"}, {"baz", "signature"}}}; - BOOST_CHECK(table.get_type("foo") == foo); - BOOST_CHECK(table.get_type("Bar") == bar); - BOOST_CHECK_EQUAL(table.resolve_type_def("Baz"), "Bar"); - BOOST_CHECK_EQUAL(table.resolve_type_def("Qux"), "Bar"); - BOOST_CHECK(table.get_type("Baz") == bar); - BOOST_CHECK(table.get_type("Qux") == bar); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_SUITE_END() - -}} // namespace eosio::types diff --git a/tests/tests/wasm_tests/wasm_tests.cpp b/tests/tests/wasm_tests/wasm_tests.cpp deleted file mode 100644 index 7f6ae73043b..00000000000 --- a/tests/tests/wasm_tests/wasm_tests.cpp +++ /dev/null @@ -1,513 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include - -#include "../../common/database_fixture.hpp" - -#include -#include - -using namespace eosio; -using namespace chain; - -BOOST_AUTO_TEST_SUITE(wasm_tests) - -//Test rate limiting with single authority -BOOST_FIXTURE_TEST_CASE(rate_limit_single_authority_test, testing_fixture) -{ try { - Make_Blockchain(chain); - chain.produce_blocks(10); - Make_Account(chain, currency); - Make_Account(chain, test1); - Make_Account(chain, test2); - chain.produce_blocks(1); - - - types::setcode handler; - handler.account = "currency"; - - auto wasm = testing_blockchain::assemble_wast( currency_wast ); - handler.code.resize(wasm.size()); - memcpy( handler.code.data(), wasm.data(), wasm.size() ); - - { - eosio::chain::signed_transaction txn; - txn.scope = {"currency"}; - txn.messages.resize(1); - txn.messages[0].code = config::eos_contract_name; - txn.messages[0].authorization.emplace_back(types::account_permission{"currency","active"}); - transaction_set_message(txn, 0, "setcode", handler); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - chain.push_transaction(txn); - chain.produce_blocks(1); - - txn.scope = sort_names({"test1","currency"}); - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"currency","active"} }, - "transfer", types::transfer{"currency", "test1", 10000000,""}); - chain.push_transaction(txn); - - txn.scope = sort_names({"test2","currency"}); - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"currency","active"} }, - "transfer", types::transfer{"currency", "test2", 10000000,""}); - chain.push_transaction(txn); - chain.produce_blocks(1); - } - - eosio::chain::signed_transaction txn; - txn.scope = sort_names({"test1","inita"}); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - - eosio::chain::signed_transaction txn2; - txn2.scope = sort_names({"test2","inita"}); - txn2.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn2, chain.head_block_id()); - - // sending 1800 transaction messages for 2 authorization accounts, which is the rate limit for this second - // since there were no previous messages before this - uint32_t i = 0; - for (; i < 1800; ++i) - { - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"test1","active"} }, - "transfer", types::transfer{"test1", "inita", 1+i,""}); - chain.push_transaction(txn); - - txn2.messages.clear(); - transaction_emplace_message(txn2, "currency", - vector{ {"test2","active"} }, - "transfer", types::transfer{"test2", "inita", 1+i,""}); - chain.push_transaction(txn2); - } - - // at rate limit for test1, so it will be rejected - try - { - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"test1","active"} }, - "transfer", types::transfer{"test1", "inita", i+1,""}); - chain.push_transaction(txn); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - - // at rate limit for test2, so it will be rejected - try - { - txn.messages.clear(); - transaction_emplace_message(txn2, "currency", - vector{ {"test2","active"} }, - "transfer", types::transfer{"test2", "inita", i+1,""}); - chain.push_transaction(txn2); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - -} FC_LOG_AND_RETHROW() } - -//Test rate limiting with multiple authorities -BOOST_FIXTURE_TEST_CASE(rate_limit_multi_authority_test, testing_fixture) -{ try { - Make_Blockchain(chain); - chain.produce_blocks(10); - Make_Account(chain, currency); - Make_Account(chain, test1); - Make_Account(chain, test2); - Make_Account(chain, test3); - Make_Account(chain, test4); - Make_Account(chain, test5); - Make_Account(chain, test11); - Make_Account(chain, test12); - Make_Account(chain, test13); - Make_Account(chain, test14); - Make_Account(chain, test15); - Make_Account(chain, test21); - Make_Account(chain, test22); - Make_Account(chain, test23); - Make_Account(chain, test24); - Make_Account(chain, test25); - Make_Account(chain, test31); - Make_Account(chain, test32); - Make_Account(chain, test33); - Make_Account(chain, test34); - Make_Account(chain, test35); - Make_Account(chain, test41); - Make_Account(chain, test42); - Make_Account(chain, test43); - Make_Account(chain, test44); - Make_Account(chain, test45); - chain.produce_blocks(1); - - - types::setcode handler; - handler.account = "currency"; - - auto wasm = testing_blockchain::assemble_wast( currency_wast ); - handler.code.resize(wasm.size()); - memcpy( handler.code.data(), wasm.data(), wasm.size() ); - - types::setcode handler2; - handler2.account = "test1"; - - auto wasm2 = testing_blockchain::assemble_wast( rate_limit_auth_wast ); - handler2.code.resize(wasm2.size()); - memcpy( handler2.code.data(), wasm2.data(), wasm2.size() ); - - - // setup currency code, and transfer tokens so accounts can do transfers - if (false) - { - eosio::chain::signed_transaction txn; - txn.scope = {"currency"}; - txn.messages.resize(1); - txn.messages[0].code = config::eos_contract_name; - txn.messages[0].authorization.emplace_back(types::account_permission{"currency","active"}); - transaction_set_message(txn, 0, "setcode", handler); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - chain.push_transaction(txn); - chain.produce_blocks(1); - } - - - // setup test1 code account, this will be used for most messages, to reach code account rate limit - { - eosio::chain::signed_transaction txn; - txn.scope = {"test1"}; - txn.messages.resize(1); - txn.messages[0].code = config::eos_contract_name; - txn.messages[0].authorization.emplace_back(types::account_permission{"test1","active"}); - transaction_set_message(txn, 0, "setcode", handler2); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - chain.push_transaction(txn); - chain.produce_blocks(1); - } - - - // setup test5 code account, this will be used to verify that code account rate limit only affects individual code accounts - { - handler2.account = "test5"; - eosio::chain::signed_transaction txn; - txn.scope = {"test5"}; - txn.messages.resize(1); - txn.messages[0].code = config::eos_contract_name; - txn.messages[0].authorization.emplace_back(types::account_permission{"test5","active"}); - transaction_set_message(txn, 0, "setcode", handler2); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - chain.push_transaction(txn); - chain.produce_blocks(1); - } - - - // setup transactions - eosio::chain::signed_transaction txn; - txn.scope = sort_names({"test1","test2"}); - txn.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn, chain.head_block_id()); - - eosio::chain::signed_transaction txn2; - txn2.scope = sort_names({"test2","test3"}); - txn2.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn2, chain.head_block_id()); - - eosio::chain::signed_transaction txn3; - txn3.scope = sort_names({"test1","test4"}); - txn3.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn3, chain.head_block_id()); - - // looping 900 times to put some accounts (test1 and test2) at 1800 rate limit - for (uint32_t i = 0; i < 900; ++i) - { - txn.messages.clear(); - transaction_emplace_message(txn, "test1", - vector{ {"test1","active"},{"test2","active"} }, - "transfer", types::transfer{"test1", "test2", i+1, ""}); - chain.push_transaction(txn); - - txn2.messages.clear(); - transaction_emplace_message(txn2, "test1", - vector{ {"test2","active"},{"test3","active"} }, - "transfer", types::transfer{"test2", "test3", i+1, ""}); - chain.push_transaction(txn2); - - txn3.messages.clear(); - transaction_emplace_message(txn3, "test1", - vector{ {"test1","active"},{"test4","active"} }, - "transfer", types::transfer{"test1", "test4", i+1, ""}); - chain.push_transaction(txn3); - - } - // auth test1 - 1800 transaction messages - // auth test2 - 1800 transaction messages - // auth test3 - 900 transaction messages - // auth test4 - 900 transaction messages - // code test1 - 5400 transaction messages - - // test1 at rate limit, should be rejected - try - { - txn.scope = sort_names({"test1"}); - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"test1","active"} }, - "transfer", types::transfer{"test1", "test4", 1000,""}); - chain.push_transaction(txn); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - - - // test2 at rate limit, should be rejected - try - { - txn2.scope = sort_names({"test2"}); - txn2.messages.clear(); - transaction_emplace_message(txn2, "currency", - vector{ {"test2","active"} }, - "transfer", types::transfer{"test2", "test3", 1000,""}); - chain.push_transaction(txn2); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - - - eosio::chain::signed_transaction txn4; - txn4.scope = sort_names({"test3","test4"}); - txn4.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn4, chain.head_block_id()); - - - // looping 900 times to put remaining accounts at 1800 rate limit - for (uint32_t i = 0; i < 900; ++i) - { - txn4.messages.clear(); - transaction_emplace_message(txn4, "test1", - vector{ {"test3","active"},{"test4","active"} }, - "transfer", types::transfer{"test3", "test4", i+1, ""}); - chain.push_transaction(txn4); - - } - // auth test1 - 1800 transaction messages - // auth test2 - 1800 transaction messages - // auth test3 - 1800 transaction messages - // auth test4 - 1800 transaction messages - // code test1 - 7200 transaction messages - - - // test3 at rate limit, should be rejected - try - { - txn.scope = sort_names({"test3"}); - txn.messages.clear(); - transaction_emplace_message(txn, "currency", - vector{ {"test3","active"} }, - "transfer", types::transfer{"test3", "test5", 1000,""}); - chain.push_transaction(txn); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - - - // test4 at rate limit, should be rejected - try - { - txn2.scope = sort_names({"test4"}); - txn2.messages.clear(); - transaction_emplace_message(txn2, "currency", - vector{ {"test4","active"} }, - "transfer", types::transfer{"test4", "test5", 1000,""}); - chain.push_transaction(txn2); - BOOST_FAIL("Should have gotten tx_msgs_auth_exceeded exception."); - } - catch (const tx_msgs_auth_exceeded& ex) - { - } - - - eosio::chain::signed_transaction txn11; - txn11.scope = sort_names({"test11","test31"}); - txn11.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn11, chain.head_block_id()); - - eosio::chain::signed_transaction txn12; - txn12.scope = sort_names({"test12","test32"}); - txn12.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn12, chain.head_block_id()); - - eosio::chain::signed_transaction txn13; - txn13.scope = sort_names({"test13","test33"}); - txn13.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn13, chain.head_block_id()); - - eosio::chain::signed_transaction txn14; - txn14.scope = sort_names({"test14","test34"}); - txn14.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn14, chain.head_block_id()); - - eosio::chain::signed_transaction txn15; - txn15.scope = sort_names({"test15","test35"}); - txn15.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn15, chain.head_block_id()); - - eosio::chain::signed_transaction txn21; - txn21.scope = sort_names({"test21","test41"}); - txn21.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn21, chain.head_block_id()); - - eosio::chain::signed_transaction txn22; - txn22.scope = sort_names({"test22","test42"}); - txn22.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn22, chain.head_block_id()); - - eosio::chain::signed_transaction txn23; - txn23.scope = sort_names({"test23","test43"}); - txn23.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn23, chain.head_block_id()); - - eosio::chain::signed_transaction txn24; - txn24.scope = sort_names({"test24","test44"}); - txn24.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn24, chain.head_block_id()); - - eosio::chain::signed_transaction txn25; - txn25.scope = sort_names({"test25","test45"}); - txn25.expiration = chain.head_block_time() + 100; - transaction_set_reference_block(txn25, chain.head_block_id()); - - // looping 1440 times with 10 different transactions to get to the 18000 code account rate limit for test1 - uint32_t i = 0; - for (; i < 1440; ++i) - { - txn11.messages.clear(); - transaction_emplace_message(txn11, "test1", - vector{ {"test11","active"},{"test31","active"} }, - "transfer", types::transfer{"test11", "test31", i+1, ""}); - chain.push_transaction(txn11); - - txn12.messages.clear(); - transaction_emplace_message(txn12, "test1", - vector{ {"test12","active"},{"test32","active"} }, - "transfer", types::transfer{"test12", "test32", i+1, ""}); - chain.push_transaction(txn12); - - txn13.messages.clear(); - transaction_emplace_message(txn13, "test1", - vector{ {"test13","active"},{"test33","active"} }, - "transfer", types::transfer{"test13", "test33", i+1, ""}); - chain.push_transaction(txn13); - - txn14.messages.clear(); - transaction_emplace_message(txn14, "test1", - vector{ {"test14","active"},{"test34","active"} }, - "transfer", types::transfer{"test14", "test34", i+1, ""}); - chain.push_transaction(txn14); - - txn15.messages.clear(); - transaction_emplace_message(txn15, "test1", - vector{ {"test15","active"},{"test35","active"} }, - "transfer", types::transfer{"test15", "test35", i+1, ""}); - chain.push_transaction(txn15); - - txn21.messages.clear(); - transaction_emplace_message(txn21, "test1", - vector{ {"test21","active"},{"test41","active"} }, - "transfer", types::transfer{"test21", "test41", i+1, ""}); - chain.push_transaction(txn21); - - txn22.messages.clear(); - transaction_emplace_message(txn22, "test1", - vector{ {"test22","active"},{"test42","active"} }, - "transfer", types::transfer{"test22", "test42", i+1, ""}); - chain.push_transaction(txn22); - - txn23.messages.clear(); - transaction_emplace_message(txn23, "test1", - vector{ {"test23","active"},{"test43","active"} }, - "transfer", types::transfer{"test23", "test43", i+1, ""}); - chain.push_transaction(txn23); - - txn24.messages.clear(); - transaction_emplace_message(txn24, "test1", - vector{ {"test24","active"},{"test44","active"} }, - "transfer", types::transfer{"test24", "test44", i+1, ""}); - chain.push_transaction(txn24); - - txn25.messages.clear(); - transaction_emplace_message(txn25, "test1", - vector{ {"test25","active"},{"test45","active"} }, - "transfer", types::transfer{"test25", "test45", i+1, ""}); - chain.push_transaction(txn25); - - } - // code test1 - 18000 transaction messages - - - // reached rate limit, should be rejected - try - { - txn11.messages.clear(); - transaction_emplace_message(txn11, "test1", - vector{ {"test11","active"},{"test21","active"} }, - "transfer", types::transfer{"test11", "test21", i+1, ""}); - chain.push_transaction(txn11); - BOOST_FAIL("Should have gotten tx_msgs_code_exceeded exception."); - } - catch (const tx_msgs_code_exceeded& ex) - { - } - - // verify that only test1 is blocked - txn11.messages.clear(); - transaction_emplace_message(txn11, "test5", - vector{ {"test11","active"},{"test31","active"} }, - "transfer", types::transfer{"test11", "test31", i+1, ""}); - chain.push_transaction(txn11); - - - // still should be rejected - try - { - txn11.messages.clear(); - transaction_emplace_message(txn11, "test1", - vector{ {"test11","active"},{"test21","active"} }, - "transfer", types::transfer{"test11", "test21", i+1, ""}); - chain.push_transaction(txn11); - BOOST_FAIL("Should have gotten tx_msgs_code_exceeded exception."); - } - catch (const tx_msgs_code_exceeded& ex) - { - } - - chain.produce_blocks(1); - - // rate limit calculation will now be at least at a new second, so will always be able to handle a new transaction message - txn11.messages.clear(); - transaction_emplace_message(txn11, "test1", - vector{ {"test11","active"},{"test21","active"} }, - "transfer", types::transfer{"test11", "test21", i+1, ""}); - chain.push_transaction(txn11); - -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/wallet_tests.cpp b/tests/wallet_tests.cpp similarity index 98% rename from tests/tests/wallet_tests.cpp rename to tests/wallet_tests.cpp index afff7d743ae..35767d8d5f4 100644 --- a/tests/tests/wallet_tests.cpp +++ b/tests/wallet_tests.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace eosio { @@ -96,6 +97,7 @@ BOOST_AUTO_TEST_CASE(wallet_manager_test) wm.lock("test"); BOOST_CHECK(wm.list_wallets().at(0).find("*") == std::string::npos); wm.unlock("test", pw); + BOOST_CHECK_THROW(wm.unlock("test", pw), chain::wallet_unlocked_exception); BOOST_CHECK(wm.list_wallets().at(0).find("*") != std::string::npos); wm.import_key("test", key1); BOOST_CHECK_EQUAL(2, wm.list_keys().size()); diff --git a/tests/wasm_tests/eosio.system_tests.cpp b/tests/wasm_tests/eosio.system_tests.cpp deleted file mode 100644 index e29b44c49e9..00000000000 --- a/tests/wasm_tests/eosio.system_tests.cpp +++ /dev/null @@ -1,1507 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#include - -#ifdef NON_VALIDATING_TEST -#define TESTER tester -#else -#define TESTER validating_tester -#endif - -using namespace eosio::testing; -using namespace eosio; -using namespace eosio::chain; -using namespace eosio::chain::contracts; -using namespace eosio::chain_apis; -using namespace eosio::testing; -using namespace fc; - -using mvo = fc::mutable_variant_object; - -class eosio_system_tester : public TESTER { -public: - - eosio_system_tester() { - - produce_blocks( 2 ); - - create_accounts( { N(eosio.token) } ); - create_accounts( { N(alice), N(bob), N(carol) } ); - produce_blocks( 1000 ); - - set_code( config::system_account_name, eosio_system_wast ); - set_abi( config::system_account_name, eosio_system_abi ); - - set_code( N(eosio.token), eosio_token_wast ); - set_abi( N(eosio.token), eosio_token_abi ); - - create_currency( N(eosio.token), config::system_account_name, asset::from_string("20000000.0000 EOS") ); - issue(config::system_account_name, "10000000.0000 EOS"); - - produce_blocks(); - - const auto& accnt = control->get_database().get( config::system_account_name ); - abi_def abi; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); - abi_ser.set_abi(abi); - /* - const global_property_object &gpo = control->get_global_properties(); - FC_ASSERT(0 < gpo.active_producers.producers.size(), "No producers"); - producer_name = (string)gpo.active_producers.producers.front().producer_name; - */ - } - - action_result push_action( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) { - string action_type_name = abi_ser.get_action_type(name); - - action act; - act.account = config::system_account_name; - act.name = name; - act.data = abi_ser.variant_to_binary( action_type_name, data ); - - return base_tester::push_action( std::move(act), auth ? uint64_t(signer) : signer == N(bob) ? N(alice) : N(bob) ); - } - - action_result stake( const account_name& from, const account_name& to, const string& net, const string& cpu, const string& storage ) { - return push_action( name(from), N(delegatebw), mvo() - ("from", from) - ("receiver", to) - ("stake_net_quantity", net) - ("stake_cpu_quantity", cpu) - ("stake_storage_quantity", storage) - ); - } - - action_result stake( const account_name& acnt, const string& net, const string& cpu, const string& storage ) { - return stake( acnt, acnt, net, cpu, storage ); - } - - action_result unstake( const account_name& from, const account_name& to, const string& net, const string& cpu, uint64_t bytes ) { - return push_action( name(from), N(undelegatebw), mvo() - ("from", from) - ("receiver", to) - ("unstake_net_quantity", net) - ("unstake_cpu_quantity", cpu) - ("unstake_storage_bytes", bytes) - ); - } - - action_result unstake( const account_name& acnt, const string& net, const string& cpu, uint64_t bytes ) { - return unstake( acnt, acnt, net, cpu, bytes ); - } - - static fc::variant_object producer_parameters_example( int n ) { - return mutable_variant_object() - ("base_per_transaction_net_usage", 100 + n) - ("base_per_transaction_cpu_usage", 100 + n) - ("base_per_action_cpu_usage", 100 + n) - ("base_setcode_cpu_usage", 100 + n) - ("per_signature_cpu_usage", 100 + n) - ("per_lock_net_usage", 100 + n ) - ("context_free_discount_cpu_usage_num", 1 + n ) - ("context_free_discount_cpu_usage_den", 100 + n ) - ("max_transaction_cpu_usage", 1000000 + n ) - ("max_transaction_net_usage", 1000000 + n ) - ("max_block_cpu_usage", 10000000 + n ) - ("target_block_cpu_usage_pct", 10 + n ) - ("max_block_net_usage", 10000000 + n ) - ("target_block_net_usage_pct", 10 + n ) - ("max_transaction_lifetime", 3600 + n) - ("max_transaction_exec_time", 9900 + n) - ("max_authority_depth", 6 + n) - ("max_inline_depth", 4 + n) - ("max_inline_action_size", 4096 + n) - ("max_generated_transaction_count", 10 + n) - ("max_transaction_delay", 10*86400+n) - ("max_storage_size", (n % 10 + 1) * 1024 * 1024) - ("percent_of_max_inflation_rate", 50 + n) - ("storage_reserve_ratio", 100 + n); - } - - action_result regproducer( const account_name& acnt, int params_fixture = 1 ) { - return push_action( acnt, N(regproducer), mvo() - ("producer", acnt ) - ("producer_key", fc::raw::pack( get_public_key( acnt, "active" ) ) ) - ("prefs", producer_parameters_example( params_fixture ) ) - ); - } - - uint32_t last_block_time() const { - return time_point_sec( control->head_block_time() ).sec_since_epoch(); - } - - asset get_balance( const account_name& act ) { - //return get_currency_balance( config::system_account_name, symbol(SY(4,EOS)), act ); - //temporary code. current get_currency_balancy uses table name N(accounts) from currency.h - //generic_currency table name is N(account). - const auto& db = control->get_database(); - const auto* tbl = db.find(boost::make_tuple(N(eosio.token), act, N(accounts))); - share_type result = 0; - - // the balance is implied to be 0 if either the table or row does not exist - if (tbl) { - const auto *obj = db.find(boost::make_tuple(tbl->id, symbol(SY(4,EOS)).to_symbol_code())); - if (obj) { - // balance is the first field in the serialization - fc::datastream ds(obj->value.data(), obj->value.size()); - fc::raw::unpack(ds, result); - } - } - return asset( result, symbol(SY(4,EOS)) ); - } - - fc::variant get_total_stake( const account_name& act ) { - vector data = get_row_by_account( config::system_account_name, act, N(totalband), act ); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "total_resources", data ); - } - - fc::variant get_voter_info( const account_name& act ) { - vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(voters), act ); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "voter_info", data ); - } - - fc::variant get_producer_info( const account_name& act ) { - vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(producerinfo), act ); - return abi_ser.binary_to_variant( "producer_info", data ); - } - - void create_currency( name contract, name manager, asset maxsupply ) { - auto act = mutable_variant_object() - ("issuer", manager ) - ("maximum_supply", maxsupply ) - ("can_freeze", 0) - ("can_recall", 0) - ("can_whitelist", 0); - - base_tester::push_action(contract, N(create), contract, act ); - } - - void issue( name to, const string& amount, name manager = config::system_account_name ) { - base_tester::push_action( N(eosio.token), N(issue), manager, mutable_variant_object() - ("to", to ) - ("quantity", asset::from_string(amount) ) - ("memo", "") - ); - } - - abi_serializer abi_ser; -}; - -fc::mutable_variant_object voter( account_name acct ) { - return mutable_variant_object() - ("owner", acct) - ("proxy", name(0).to_string()) - ("is_proxy", 0) - ("staked", asset()) - ("unstaking", asset()) - ("unstake_per_week", asset()) - ("proxied_votes", 0) - ("producers", variants() ) - ("deferred_trx_id", 0) - ("last_unstake", 0); -} - -fc::mutable_variant_object voter( account_name acct, const string& vote_stake ) { - return voter( acct )( "staked", asset::from_string( vote_stake ) ); -} - -fc::mutable_variant_object proxy( account_name acct ) { - return voter( acct )( "is_proxy", 1 ); -} - -inline uint64_t M( const string& eos_str ) { - return asset::from_string( eos_str ).amount; -} - -BOOST_AUTO_TEST_SUITE(eosio_system_tests) - -BOOST_FIXTURE_TEST_CASE( stake_unstake, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); - - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "200.0000 EOS", "100.0000 EOS", "500.0000 EOS" ) ); - - auto total = get_total_stake( "alice" ); - - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS").amount, total["net_weight"].as().amount ); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS").amount, total["cpu_weight"].as().amount ); - BOOST_REQUIRE_EQUAL( asset::from_string("500.0000 EOS").amount, total["storage_stake"].as().amount ); - - REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS"), get_voter_info( "alice" ) ); - - auto bytes = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, 0 < bytes ); - - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), get_balance( "alice" ) ); - - //unstake - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "200.0000 EOS", "100.0000 EOS", bytes ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 EOS").amount, total["net_weight"].as().amount); - BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 EOS").amount, total["cpu_weight"].as().amount); - BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 EOS").amount, total["storage_stake"].as().amount); - BOOST_REQUIRE_EQUAL( 0, total["storage_bytes"].as_uint64()); - REQUIRE_MATCHING_OBJECT( voter( "alice", "0.0000 EOS" ), get_voter_info( "alice" ) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), get_balance( "alice" ) ); - - //after 2 days balance should not be available yet - produce_block( fc::hours(3*24-1) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), get_balance( "alice" ) ); - //after 3 days funds should be released - produce_block( fc::hours(1) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( fail_without_auth, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - - BOOST_REQUIRE_EQUAL( error("missing authority of alice"), - push_action( N(alice), N(delegatebw), mvo() - ("from", "alice") - ("receiver", "bob") - ("stake_net_quantity", "10.0000 EOS") - ("stake_cpu_quantity", "10.0000 EOS") - ("stake_storage_quantity", "10.0000 EOS"), - false - ) - ); - - BOOST_REQUIRE_EQUAL( error("missing authority of alice"), - push_action(N(alice), N(undelegatebw), mvo() - ("from", "alice") - ("receiver", "bob") - ("unstake_net_quantity", "200.0000 EOS") - ("unstake_cpu_quantity", "100.0000 EOS") - ("unstake_storage_bytes", 0) - ,false - ) - ); - //REQUIRE_MATCHING_OBJECT( , get_voter_info( "alice" ) ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( stake_negative, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), - stake( "alice", "-0.0001 EOS", "0.0000 EOS", "0.0000 EOS" ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), - stake( "alice", "0.0000 EOS", "-0.0001 EOS", "0.0000 EOS" ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), - stake( "alice", "00.0000 EOS", "00.0000 EOS", "0.0000 EOS" ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), - stake( "alice", "0.0000 EOS", "00.0000 EOS", "0.0000 EOS" ) - ); - - BOOST_REQUIRE_EQUAL( true, get_voter_info( "alice" ).is_null() ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( unstake_negative, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "200.0001 EOS", "100.0001 EOS", "300.0000 EOS" ) ); - - auto total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0001 EOS"), total["net_weight"].as()); - REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0002 EOS" ), get_voter_info( "alice" ) ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), - unstake( "alice", "bob", "-1.0000 EOS", "0.0000 EOS", 0 ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), - unstake( "alice", "bob", "0.0000 EOS", "-1.0000 EOS", 0 ) - ); - - //unstake all zeros - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), - unstake( "alice", "bob", "0.0000 EOS", "0.0000 EOS", 0 ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( unstake_more_than_at_stake, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "200.0000 EOS", "100.0000 EOS", "150.0000 EOS" ) ); - - auto total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("150.0000 EOS"), total["storage_stake"].as()); - auto bytes = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, 0 < bytes ); - - BOOST_REQUIRE_EQUAL( asset::from_string("550.0000 EOS"), get_balance( "alice" ) ); - - //trying to unstake more net bandwith than at stake - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked net bandwidth"), - unstake( "alice", "200.0001 EOS", "0.0000 EOS", 0 ) - ); - - //trying to unstake more cpu bandwith than at stake - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked cpu bandwidth"), - unstake( "alice", "000.0000 EOS", "100.0001 EOS", 0 ) - ); - - //trying to unstake more storage than at stake - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked storage"), - unstake( "alice", "000.0000 EOS", "0.0000 EOS", bytes+1 ) - ); - - //check that nothing has changed - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("150.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes, total["storage_bytes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( asset::from_string("550.0000 EOS"), get_balance( "alice" ) ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( delegate_to_another_user, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - - BOOST_REQUIRE_EQUAL( success(), stake ( "alice", "bob", "200.0000 EOS", "100.0000 EOS", "80.0000 EOS" ) ); - - auto total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("200.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("80.0000 EOS"), total["storage_stake"].as()); - auto bytes = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, 0 < bytes ); - BOOST_REQUIRE_EQUAL( asset::from_string("620.0000 EOS"), get_balance( "alice" ) ); - //all voting power goes to alice - REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); - //but not to bob - BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob" ).is_null() ); - - //bob should not be able to unstake what was staked by alice - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: unable to find key"), - unstake( "bob", "0.0000 EOS", "10.0000 EOS", bytes ) - ); - - issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "bob", "20.0000 EOS", "10.0000 EOS", "8.0000 EOS" ) ); - total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("220.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("88.0000 EOS"), total["storage_stake"].as()); - auto bytes2 = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, bytes < bytes2 ); - BOOST_REQUIRE_EQUAL( asset::from_string("962.0000 EOS"), get_balance( "carol" ) ); - REQUIRE_MATCHING_OBJECT( voter( "carol", "30.0000 EOS" ), get_voter_info( "carol" ) ); - - //alice should not be able to unstake money staked by carol - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked net bandwidth"), - unstake( "alice", "bob", "2001.0000 EOS", "1.0000 EOS", bytes-1 ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked cpu bandwidth"), - unstake( "alice", "bob", "1.0000 EOS", "101.0000 EOS", bytes-1 ) - ); - - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked storage"), - unstake( "alice", "bob", "1.0000 EOS", "1.0000 EOS", bytes+1 ) - ); - - total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("220.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("88.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes2, total["storage_bytes"].as_uint64()); - //balance should not change after unsuccessfull attempts to unstake - BOOST_REQUIRE_EQUAL( asset::from_string("620.0000 EOS"), get_balance( "alice" ) ); - //voting power too - REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); - REQUIRE_MATCHING_OBJECT( voter( "carol", "30.0000 EOS" ), get_voter_info( "carol" ) ); - BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob" ).is_null() ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( stake_unstake_separate, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); - - //everything at once - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "10.0000 EOS", "20.0000 EOS", "30.0000 EOS" ) ); - auto total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["storage_stake"].as()); - auto bytes = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, 0 < bytes ); - - //cpu - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "100.0000 EOS", "0.0000 EOS", "0.0000 EOS" ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes, total["storage_bytes"].as_uint64() ); - - //net - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "0.0000 EOS", "200.0000 EOS", "0.0000 EOS" ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("220.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes, total["storage_bytes"].as_uint64() ); - - //storage - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "0.0000 EOS", "0.0000 EOS", "300.0000 EOS" ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("220.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("330.0000 EOS"), total["storage_stake"].as()); - auto bytes2 = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, bytes < bytes2 ); - - //unstake cpu - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "100.0000 EOS", "0.0000 EOS", 0 ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("220.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("330.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes2, total["storage_bytes"].as_uint64()); - - //unstake net - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "0.0000 EOS", "200.0000 EOS", 0 ) ); - total = get_total_stake( "alice" ); - BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("330.0000 EOS"), total["storage_stake"].as()); - BOOST_REQUIRE_EQUAL( bytes2, total["storage_bytes"].as_uint64()); - - //unstake cpu - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "0.0000 EOS", "0.0000 EOS", bytes2 / 2 ) ); - total = get_total_stake( "alice" ); - auto storage_left = M("330.0000 EOS") - M("330.0000 EOS") * (bytes2 / 2) / bytes2; - BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( storage_left, total["storage_stake"].as().amount); - BOOST_REQUIRE_EQUAL( bytes2 - bytes2/2, total["storage_bytes"].as_uint64()); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( adding_stake_partial_unstake, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "200.0000 EOS", "100.0000 EOS", "80.0000 EOS" ) ); - - auto total = get_total_stake( "bob" ); - auto bytes0 = total["storage_bytes"].as_uint64(); - REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); - - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "100.0000 EOS", "50.0000 EOS", "40.0000 EOS" ) ); - - total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("300.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("150.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS"), total["storage_stake"].as()); - auto bytes = total["storage_bytes"].as_uint64(); - BOOST_REQUIRE_EQUAL( true, bytes0 < bytes ); - REQUIRE_MATCHING_OBJECT( voter( "alice", "450.0000 EOS" ), get_voter_info( "alice" ) ); - BOOST_REQUIRE_EQUAL( asset::from_string("430.0000 EOS"), get_balance( "alice" ) ); - - //unstake a share - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "bob", "150.0000 EOS", "75.0000 EOS", bytes / 2 ) ); - - total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("150.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("75.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( bytes-bytes/2, total["storage_bytes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS").amount - asset::from_string("120.0000 EOS").amount * (bytes/2)/bytes, - total["storage_stake"].as().amount); - REQUIRE_MATCHING_OBJECT( voter( "alice", "225.0000 EOS" ), get_voter_info( "alice" ) ); - - //unstake more - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "bob", "50.0000 EOS", "25.0000 EOS", 0 ) ); - total = get_total_stake( "bob" ); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS"), total["net_weight"].as()); - BOOST_REQUIRE_EQUAL( asset::from_string("50.0000 EOS"), total["cpu_weight"].as()); - BOOST_REQUIRE_EQUAL( bytes-bytes/2, total["storage_bytes"].as_uint64() ); - REQUIRE_MATCHING_OBJECT( voter( "alice", "150.0000 EOS" ), get_voter_info( "alice" ) ); - - //combined amount should be available only in 3 days - produce_block( fc::days(2) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( asset::from_string("430.0000 EOS"), get_balance( "alice" ) ); - produce_block( fc::days(1) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( asset::from_string("790.0000 EOS"), get_balance( "alice" ) ); - -} FC_LOG_AND_RETHROW() - -// Tests for voting - -BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( fc::crypto::public_key( std::string("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV") ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - - auto info = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); - BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_uint64() ); - REQUIRE_MATCHING_OBJECT( params, info["prefs"] ); - BOOST_REQUIRE_EQUAL( string(key.begin(), key.end()), to_string(info["packed_key"]) ); - - - //call regproducer again to change parameters - fc::variant params2 = producer_parameters_example(2); - - vector key2 = fc::raw::pack( fc::crypto::public_key( std::string("PUB_R1_6EPHFSKVYHBjQgxVGQPrwCxTg7BbZ69H9i4gztN9deKTEXYne4") ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key2 ) - ("prefs", params2) - ) - ); - - info = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); - BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_uint64() ); - REQUIRE_MATCHING_OBJECT( params2, info["prefs"] ); - BOOST_REQUIRE_EQUAL( string(key2.begin(), key2.end()), to_string(info["packed_key"]) ); - - //unregister producer - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(unregprod), mvo() - ("producer", "alice") - ) - ); - info = get_producer_info( "alice" ); - //key should be empty - BOOST_REQUIRE_EQUAL( true, to_string(info["packed_key"]).empty() ); - //everything else should stay the same - BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); - BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_uint64() ); - REQUIRE_MATCHING_OBJECT( params2, info["prefs"] ); - - //unregister bob who is not a producer - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer not found" ), - push_action( N(bob), N(unregprod), mvo() - ("producer", "bob") - ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( vote_for_producer, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - auto prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( "alice", prod["owner"].as_string() ); - BOOST_REQUIRE_EQUAL( 0, prod["total_votes"].as_uint64() ); - REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); - BOOST_REQUIRE_EQUAL( string(key.begin(), key.end()), to_string(prod["packed_key"]) ); - - issue( "bob", "2000.0000 EOS", config::system_account_name ); - issue( "carol", "3000.0000 EOS", config::system_account_name ); - - //bob makes stake - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "11.0000 EOS", "0.1111 EOS", "0.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( asset::from_string("1988.8889 EOS"), get_balance( "bob" ) ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "11.1111 EOS" ), get_voter_info( "bob" ) ); - - //bob votes for alice - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - - //check that producer parameters stay the same after voting - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 111111, prod["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( "alice", prod["owner"].as_string() ); - REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); - BOOST_REQUIRE_EQUAL( string(key.begin(), key.end()), to_string(prod["packed_key"]) ); - - //carol makes stake - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "22.0000 EOS", "0.2222 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( voter( "carol", "22.2222 EOS" ), get_voter_info( "carol" ) ); - BOOST_REQUIRE_EQUAL( asset::from_string("2977.7778 EOS"), get_balance( "carol" ) ); - //carol votes for alice - BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - //new stake votes be added to alice's total_votes - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 333333, prod["total_votes"].as_uint64() ); - - //bob increases his stake - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "55.0000 EOS", "0.5555 EOS", "0.0000 EOS" ) ); - //should increase alice's total_votes - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 888888, prod["total_votes"].as_uint64() ); - - //carol unstakes part of the stake - BOOST_REQUIRE_EQUAL( success(), unstake( "carol", "2.0000 EOS", "0.0002 EOS", 0 ) ); - //should decrease alice's total_votes - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 868886, prod["total_votes"].as_uint64() ); - - //bob revokes his vote - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector() ) - ) - ); - //should decrease alice's total_votes - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 202220, prod["total_votes"].as_uint64() ); - //but eos should still be at stake - BOOST_REQUIRE_EQUAL( asset::from_string("1933.3334 EOS"), get_balance( "bob" ) ); - - //carol unstakes rest of eos - BOOST_REQUIRE_EQUAL( success(), unstake( "carol", "20.0000 EOS", "0.2220 EOS", 0 ) ); - //should decrease alice's total_votes to zero - prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 0, prod["total_votes"].as_uint64() ); - //check that the producer parameters stay the same after all - BOOST_REQUIRE_EQUAL( "alice", prod["owner"].as_string() ); - REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); - BOOST_REQUIRE_EQUAL( string(key.begin(), key.end()), to_string(prod["packed_key"]) ); - //carol should receive funds in 3 days - produce_block( fc::days(3) ); - produce_block(); - BOOST_REQUIRE_EQUAL( asset::from_string("3000.0000 EOS"), get_balance( "carol" ) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( unregistered_producer_voting, eosio_system_tester ) try { - issue( "bob", "2000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); - - //bob should not be able to vote for alice who is not a producer - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer is not registered" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - - //alice registers as a producer - issue( "alice", "1000.0000 EOS", config::system_account_name ); - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - //and then unregisters - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unregprod), mvo() - ("producer", "alice") - ) - ); - //key should be empty - auto prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( true, to_string(prod["packed_key"]).empty() ); - - //bob should not be able to vote for alice who is an unregistered producer - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer is not currently registered" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( more_than_30_producer_voting, eosio_system_tester ) try { - issue( "bob", "2000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); - - //bob should not be able to vote for alice who is not a producer - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: attempt to vote for too many producers" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector(31, N(alice)) ) - ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( vote_same_producer_30_times, eosio_system_tester ) try { - issue( "bob", "2000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "50.0000 EOS", "50.0000 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "100.0000 EOS" ), get_voter_info( "bob" ) ); - - //alice becomes a producer - issue( "alice", "1000.0000 EOS", config::system_account_name ); - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - - //bob should not be able to vote for alice who is not a producer - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer votes must be unique and sorted" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector(30, N(alice)) ) - ) - ); - - auto prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 0, prod["total_votes"].as_uint64() ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( producer_keep_votes, eosio_system_tester ) try { - issue( "alice", "1000.0000 EOS", config::system_account_name ); - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - - //bob makes stake - issue( "bob", "2000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); - - //bob votes for alice - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - - auto prod = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 135791, prod["total_votes"].as_uint64() ); - - //unregister producer - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(unregprod), mvo() - ("producer", "alice") - ) - ); - prod = get_producer_info( "alice" ); - //key should be empty - BOOST_REQUIRE_EQUAL( true, to_string(prod["packed_key"]).empty() ); - //check parameters just in case - REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); - //votes should stay the same - BOOST_REQUIRE_EQUAL( 135791, prod["total_votes"].as_uint64() ); - - //regtister the same producer again - params = producer_parameters_example(2); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - prod = get_producer_info( "alice" ); - //votes should stay the same - BOOST_REQUIRE_EQUAL( 135791, prod["total_votes"].as_uint64() ); - - //change parameters - params = producer_parameters_example(3); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - prod = get_producer_info( "alice" ); - //votes should stay the same - BOOST_REQUIRE_EQUAL( 135791, prod["total_votes"].as_uint64() ); - //check parameters just in case - REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); - BOOST_REQUIRE_EQUAL( string(key.begin(), key.end()), to_string(prod["packed_key"]) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( vote_for_two_producers, eosio_system_tester ) try { - //alice becomes a producer - fc::variant params = producer_parameters_example(1); - vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - //bob becomes a producer - params = producer_parameters_example(2); - key = fc::raw::pack( get_public_key( N(bob), "active" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproducer), mvo() - ("producer", "bob") - ("producer_key", key ) - ("prefs", params) - ) - ); - - //carol votes for alice and bob - issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "15.0005 EOS", "5.0000 EOS", "0.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice), N(bob) } ) - ) - ); - - auto alice_info = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 200005, alice_info["total_votes"].as_uint64() ); - auto bob_info = get_producer_info( "bob" ); - BOOST_REQUIRE_EQUAL( 200005, bob_info["total_votes"].as_uint64() ); - - //carol votes for alice (but revokes vote for bob) - BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice) } ) - ) - ); - - alice_info = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 200005, alice_info["total_votes"].as_uint64() ); - bob_info = get_producer_info( "bob" ); - BOOST_REQUIRE_EQUAL( 0, bob_info["total_votes"].as_uint64() ); - - //alice votes for herself and bob - issue( "alice", "2.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "1.0000 EOS", "1.0000 EOS", "0.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() - ("voter", "alice") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(alice), N(bob) } ) - ) - ); - - alice_info = get_producer_info( "alice" ); - BOOST_REQUIRE_EQUAL( 220005, alice_info["total_votes"].as_uint64() ); - bob_info = get_producer_info( "bob" ); - BOOST_REQUIRE_EQUAL( 20000, bob_info["total_votes"].as_uint64() ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( proxy_register_unregister_keeps_stake, eosio_system_tester ) try { - //register proxy by first action for this user ever - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); - - //unregister proxy - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(unregproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( voter( "alice" ), get_voter_info( "alice" ) ); - - //stake and then register as a proxy - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "200.0002 EOS", "100.0001 EOS", "80.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproxy), mvo() - ("proxy", "bob") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "bob" )( "staked", "300.0003 EOS" ), get_voter_info( "bob" ) ); - //unrgister and check that stake is still in place - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(unregproxy), mvo() - ("proxy", "bob") - ) - ); - REQUIRE_MATCHING_OBJECT( voter( "bob", "300.0003 EOS" ), get_voter_info( "bob" ) ); - - //register as a proxy and then stake - BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(regproxy), mvo() - ("proxy", "carol") - ) - ); - issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "246.0002 EOS", "531.0001 EOS", "80.0000 EOS" ) ); - //check that both proxy flag and stake a correct - REQUIRE_MATCHING_OBJECT( proxy( "carol" )( "staked", "777.0003 EOS" ), get_voter_info( "carol" ) ); - - //unregister - BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(unregproxy), mvo() - ("proxy", "carol") - ) - ); - REQUIRE_MATCHING_OBJECT( voter( "carol", "777.0003 EOS" ), get_voter_info( "carol" ) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( proxy_stake_unstake_keeps_proxy_flag, eosio_system_tester ) try { - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - issue( "alice", "1000.0000 EOS", config::system_account_name ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); - - //stake - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "100.0000 EOS", "50.0000 EOS", "80.0000 EOS" ) ); - //check that account is still a proxy - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", "150.0000 EOS" ), get_voter_info( "alice" ) ); - - //stake more - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0000 EOS", "20.0000 EOS", "80.0000 EOS" ) ); - //check that account is still a proxy - REQUIRE_MATCHING_OBJECT( proxy( "alice" )("staked", "200.0000 EOS" ), get_voter_info( "alice" ) ); - - //unstake more - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "65.0000 EOS", "35.0000 EOS", 0 ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )("staked", "100.0000 EOS" ), get_voter_info( "alice" ) ); - - //unstake the rest - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "65.0000 EOS", "35.0000 EOS", 0 ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", "0.0000 EOS" ), get_voter_info( "alice" ) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( proxy_actions_affect_producers, eosio_system_tester ) try { - create_accounts( { N(producer1), N(producer2), N(producer3) } ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); - - //register as a proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - - //accumulate proxied votes - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 1500003 ), get_voter_info( "alice" ) ); - - //vote for producers - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() - ("voter", "alice") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer1), N(producer2) } ) - ) - ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //vote for another producers - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() - ("voter", "alice") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer1), N(producer3) } ) - ) - ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //unregister proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unregproxy), mvo() - ("proxy", "alice") - ) - ); - //REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_votes", 1500003 ), get_voter_info( "alice" ) ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //register proxy again - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //stake increase by proxy itself affects producers - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //stake decrease by proxy itself affects producers - BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "10.0001 EOS", "10.0001 EOS", 0 ) ); - BOOST_REQUIRE_EQUAL( 1800003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1800003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester) try { - issue( "alice", "100.0000 EOS", config::system_account_name); - BOOST_REQUIRE_EQUAL( asset::from_string("100.0000 EOS"), get_balance( "alice" ) ); - - fc::variant params = producer_parameters_example(50); - vector key = fc::raw::pack(get_public_key(N(alice), "active")); - - // 1 block produced - - BOOST_REQUIRE_EQUAL(success(), push_action(N(alice), N(regproducer), mvo() - ("producer", "alice") - ("producer_key", key ) - ("prefs", params) - ) - ); - - auto prod = get_producer_info( N(alice) ); - - BOOST_REQUIRE_EQUAL("alice", prod["owner"].as_string()); - BOOST_REQUIRE_EQUAL(0, prod["total_votes"].as_uint64()); - REQUIRE_EQUAL_OBJECTS(params, prod["prefs"]); - BOOST_REQUIRE_EQUAL(string(key.begin(), key.end()), to_string(prod["packed_key"])); - - - issue("bob", "2000.0000 EOS", config::system_account_name); - BOOST_REQUIRE_EQUAL( asset::from_string("2000.0000 EOS"), get_balance( "bob" ) ); - - // bob makes stake - // 1 block produced - - BOOST_REQUIRE_EQUAL(success(), stake("bob", "11.0000 EOS", "10.1111 EOS", "10.1111 EOS")); - - // bob votes for alice - // 1 block produced - BOOST_REQUIRE_EQUAL(success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string()) - ("producers", vector{ N(alice) }) - ) - ); - - produce_blocks(10); - prod = get_producer_info("alice"); - BOOST_REQUIRE(prod["per_block_payments"].as_uint64() > 0); - BOOST_REQUIRE_EQUAL(success(), push_action(N(alice), N(claimrewards), mvo() - ("owner", "alice") - ) - ); - prod = get_producer_info("alice"); - BOOST_REQUIRE_EQUAL(0, prod["per_block_payments"].as_uint64()); - - } FC_LOG_AND_RETHROW() - - - -BOOST_FIXTURE_TEST_CASE( voters_actions_affect_proxy_and_producers, eosio_system_tester ) try { - create_accounts( { N(donald), N(producer1), N(producer2), N(producer3) } ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); - - //alice becomes a producer - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); - - //alice makes stake and votes - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() - ("voter", "alice") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer1), N(producer2) } ) - ) - ); - BOOST_REQUIRE_EQUAL( 500002, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 500002, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(donald), N(regproxy), mvo() - ("proxy", "donald") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "donald" ), get_voter_info( "donald" ) ); - - //bob chooses alice as a proxy - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - BOOST_REQUIRE_EQUAL( 1500003, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //carol chooses alice as a proxy - issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "30.0001 EOS", "20.0001 EOS", "80.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - BOOST_REQUIRE_EQUAL( 2000005, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2500007, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2500007, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - - //proxied voter carol increases stake - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "50.0000 EOS", "70.0000 EOS", "0.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( 3200005, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 3700007, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 3700007, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //proxied voter bob decreases stake - BOOST_REQUIRE_EQUAL( success(), unstake( "bob", "50.0001 EOS", "50.0001 EOS", 0 ) ); - BOOST_REQUIRE_EQUAL( 2200003, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2700005, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 2700005, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //proxied voter carol chooses another proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", "donald" ) - ("producers", vector() ) - ) - ); - BOOST_REQUIRE_EQUAL( 500001, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1700002, get_voter_info( "donald" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1000003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1000003, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - - //bob switches to direct voting and votes for one of the same producers, but not for another one - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "") - ("producers", vector{ N(producer2) } ) - ) - ); - BOOST_REQUIRE_EQUAL( 0, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 500002, get_producer_info( "producer1" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 1000003, get_producer_info( "producer2" )["total_votes"].as_uint64() ); - BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( vote_both_proxy_and_producers, eosio_system_tester ) try { - //alice becomes a proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); - - //carol becomes a producer - BOOST_REQUIRE_EQUAL( success(), regproducer( "carol", 1) ); - - //bob chooses alice as a proxy - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( error("condition: assertion failed: cannot vote for producers and proxy at same time"), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector{ N(carol) } ) - ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( select_invalid_proxy, eosio_system_tester ) try { - //accumulate proxied votes - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - - //selecting account not registered as a proxy - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: proxy not found" ), - push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - - //selecting not existing account as a proxy - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: proxy not found" ), - push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "notexist" ) - ("producers", vector() ) - ) - ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( double_register_unregister_proxy_keeps_votes, eosio_system_tester ) try { - //alice becomes a proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "5.0000 EOS", "5.0000 EOS", "50.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", "10.0000 EOS" ), get_voter_info( "alice" ) ); - - //bob stakes and selects alice as a proxy - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 1500003 )( "staked", "10.0000 EOS" ), get_voter_info( "alice" ) ); - - //double regestering should fail without affecting total votes and stake - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account is already a proxy" ), - push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 1500003 )( "staked", "10.0000 EOS" ), get_voter_info( "alice" ) ); - - //uregister - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unregproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_votes", 1500003 )( "staked", "10.0000 EOS" ), get_voter_info( "alice" ) ); - - //double unregistering should not affect proxied_votes and stake - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account is not a proxy" ), - push_action( N(alice), N(unregproxy), mvo() - ("proxy", "alice") - ) - ); - REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_votes", 1500003 )( "staked", "10.0000 EOS" ), get_voter_info( "alice" ) ); - -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( proxy_cannot_use_another_proxy, eosio_system_tester ) try { - //alice becomes a proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() - ("proxy", "alice") - ) - ); - - //bob becomes a proxy - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproxy), mvo() - ("proxy", "bob") - ) - ); - //proxy should not be able to use a proxy - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account registered as a proxy is not allowed to use a proxy" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - - //voter that uses a proxy should not be allowed to become a proxy - issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() - ("voter", "carol") - ("proxy", "alice" ) - ("producers", vector() ) - ) - ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account that uses a proxy is not allowed to become a proxy" ), - push_action( N(carol), N(regproxy), mvo() - ("proxy", "carol") - ) - ); - - //proxy should not be able to use itself as a proxy - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account registered as a proxy is not allowed to use a proxy" ), - push_action( N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", "bob" ) - ("producers", vector() ) - ) - ); - -} FC_LOG_AND_RETHROW() - -fc::mutable_variant_object config_to_variant( const eosio::chain::chain_config& config ) { - return mutable_variant_object() - ( "base_per_transaction_net_usage", config.base_per_transaction_net_usage ) - ( "base_per_transaction_cpu_usage", config.base_per_transaction_cpu_usage ) - ( "base_per_action_cpu_usage", config.base_per_action_cpu_usage ) - ( "base_setcode_cpu_usage", config.base_setcode_cpu_usage ) - ( "per_signature_cpu_usage", config.per_signature_cpu_usage ) - ( "per_lock_net_usage", config.per_lock_net_usage ) - ( "context_free_discount_cpu_usage_num", config.context_free_discount_cpu_usage_num ) - ( "context_free_discount_cpu_usage_den", config.context_free_discount_cpu_usage_den ) - ( "max_transaction_cpu_usage", config.max_transaction_cpu_usage ) - ( "max_transaction_net_usage", config.max_transaction_net_usage ) - ( "max_block_cpu_usage", config.max_block_cpu_usage ) - ( "target_block_cpu_usage_pct", config.target_block_cpu_usage_pct ) - ( "max_block_net_usage", config.max_block_net_usage ) - ( "target_block_net_usage_pct", config.target_block_net_usage_pct ) - ( "max_transaction_lifetime", config.max_transaction_lifetime ) - ( "max_transaction_exec_time", config.max_transaction_exec_time ) - ( "max_authority_depth", config.max_authority_depth ) - ( "max_inline_depth", config.max_inline_depth ) - ( "max_inline_action_size", config.max_inline_action_size ) - ( "max_generated_transaction_count", config.max_generated_transaction_count ); -} - -BOOST_FIXTURE_TEST_CASE( elect_producers_and_parameters, eosio_system_tester ) try { - create_accounts( { N(producer1), N(producer2), N(producer3) } ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); - BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); - - issue( "alice", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "alice", "100.0000 EOS", "50.0000 EOS", "50.0000 EOS" ) ); - //vote for producers - BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() - ("voter", "alice") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer1) } ) - ) - ); - produce_blocks(50); - auto producer_keys = control->get_global_properties().active_producers.producers; - BOOST_REQUIRE_EQUAL( 1, producer_keys.size() ); - BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[0].producer_name ); - - auto config = config_to_variant( control->get_global_properties().configuration ); - auto prod1_config = testing::filter_fields( config, producer_parameters_example( 1 ) ); - REQUIRE_EQUAL_OBJECTS(prod1_config, config); - - // elect 2 producers - issue( "bob", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "bob", "200.0000 EOS", "100.0000 EOS", "50.0000 EOS" ) ); - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer2) } ) - ) - ); - produce_blocks(50); - producer_keys = control->get_global_properties().active_producers.producers; - BOOST_REQUIRE_EQUAL( 2, producer_keys.size() ); - BOOST_REQUIRE_EQUAL( name("producer2"), producer_keys[0].producer_name ); - BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[1].producer_name ); - config = config_to_variant( control->get_global_properties().configuration ); - auto prod2_config = testing::filter_fields( config, producer_parameters_example( 2 ) ); - REQUIRE_EQUAL_OBJECTS(prod2_config, config); - - // elect 3 producers - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer2), N(producer3) } ) - ) - ); - produce_blocks(50); - producer_keys = control->get_global_properties().active_producers.producers; - BOOST_REQUIRE_EQUAL( 3, producer_keys.size() ); - BOOST_REQUIRE_EQUAL( name("producer3"), producer_keys[0].producer_name ); - BOOST_REQUIRE_EQUAL( name("producer2"), producer_keys[1].producer_name ); - BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[2].producer_name ); - config = config_to_variant( control->get_global_properties().configuration ); - REQUIRE_EQUAL_OBJECTS(prod2_config, config); - - //back to 2 producers - BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() - ("voter", "bob") - ("proxy", name(0).to_string() ) - ("producers", vector{ N(producer3) } ) - ) - ); - produce_blocks(100); - producer_keys = control->get_global_properties().active_producers.producers; - BOOST_REQUIRE_EQUAL( 2, producer_keys.size() ); - BOOST_REQUIRE_EQUAL( name("producer3"), producer_keys[0].producer_name ); - BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[1].producer_name ); - config = config_to_variant( control->get_global_properties().configuration ); - auto prod3_config = testing::filter_fields( config, producer_parameters_example( 3 ) ); - REQUIRE_EQUAL_OBJECTS(prod3_config, config); - -} FC_LOG_AND_RETHROW() - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/multisig_tests.cpp b/tests/wasm_tests/multisig_tests.cpp deleted file mode 100644 index 25f8803fcd6..00000000000 --- a/tests/wasm_tests/multisig_tests.cpp +++ /dev/null @@ -1,281 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#include - -using namespace eosio::testing; -using namespace eosio; -using namespace eosio::chain; -using namespace eosio::chain::contracts; -using namespace eosio::chain_apis; -using namespace eosio::testing; -using namespace fc; - -using mvo = fc::mutable_variant_object; - -class eosio_msig_tester : public tester { -public: - - eosio_msig_tester() { - create_accounts( { N(eosio.msig), N(alice), N(bob), N(carol) } ); - produce_block(); - - auto trace = base_tester::push_action(config::system_account_name, N(setpriv), - config::system_account_name, mutable_variant_object() - ("account", "eosio.msig") - ("is_priv", 1) - ); - - set_code( N(eosio.msig), eosio_msig_wast ); - set_abi( N(eosio.msig), eosio_msig_abi ); - - produce_blocks(); - const auto& accnt = control->get_database().get( N(eosio.msig) ); - abi_def abi; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); - abi_ser.set_abi(abi); - } - - action_result push_action( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) { - string action_type_name = abi_ser.get_action_type(name); - - action act; - act.account = N(eosio.msig); - act.name = name; - act.data = abi_ser.variant_to_binary( action_type_name, data ); - //std::cout << "test:\n" << fc::to_hex(act.data.data(), act.data.size()) << " size = " << act.data.size() << std::endl; - - return base_tester::push_action( std::move(act), auth ? uint64_t(signer) : 0 ); - } - - transaction reqauth( account_name from, const vector& auths ); - - abi_serializer abi_ser; -}; - -transaction eosio_msig_tester::reqauth( account_name from, const vector& auths ) { - fc::variants v; - for ( auto& level : auths ) { - v.push_back(fc::mutable_variant_object() - ("actor", level.actor) - ("permission", level.permission) - ); - } - variant pretty_trx = fc::mutable_variant_object() - ("expiration", "2020-01-01T00:30") - ("region", 0) - ("ref_block_num", 2) - ("ref_block_prefix", 3) - ("max_net_usage_words", 0) - ("max_kcpu_usage", 0) - ("delay_sec", 0) - ("actions", fc::variants({ - fc::mutable_variant_object() - ("account", name(config::system_account_name)) - ("name", "reqauth") - ("authorization", v) - ("data", fc::mutable_variant_object() ("from", from) ) - }) - ); - transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); - return trx; -} - -BOOST_AUTO_TEST_SUITE(eosio_msig_tests) - -BOOST_FIXTURE_TEST_CASE( propose_approve_execute, eosio_msig_tester ) try { - auto trx = reqauth("alice", {permission_level{N(alice), config::active_name}} ); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(propose), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("trx", trx) - ("requested", vector{{ N(alice), config::active_name }}) - )); - - //fail to execute before approval - BOOST_REQUIRE_EQUAL( error("transaction declares authority '{\"actor\":\"alice\",\"permission\":\"active\"}', but does not have signatures for it."), - push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); - - //approve and execute - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(alice), config::active_name }) - )); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); - - auto traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); - BOOST_CHECK_EQUAL( 1, traces.at(0).action_traces.size() ); - BOOST_CHECK_EQUAL( transaction_receipt::executed, traces.at(0).status ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( propose_approve_unapprove, eosio_msig_tester ) try { - auto trx = reqauth("alice", {permission_level{N(alice), config::active_name}} ); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(propose), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("trx", trx) - ("requested", vector{{ N(alice), config::active_name }}) - )); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(alice), config::active_name }) - )); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unapprove), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(alice), config::active_name }) - )); - - BOOST_REQUIRE_EQUAL( error("transaction declares authority '{\"actor\":\"alice\",\"permission\":\"active\"}', but does not have signatures for it."), - push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( propose_approve_by_two, eosio_msig_tester ) try { - auto trx = reqauth("alice", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } } ); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(propose), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("trx", trx) - ("requested", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } }) - )); - - //approve by alice - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(alice), config::active_name }) - )); - - //fail because approval by bob is missing - BOOST_REQUIRE_EQUAL( error("transaction declares authority '{\"actor\":\"bob\",\"permission\":\"active\"}', but does not have signatures for it."), - push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); - - //approve by bob and execute - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(bob), config::active_name }) - )); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); - auto traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); - BOOST_CHECK_EQUAL( 1, traces.at(0).action_traces.size() ); - BOOST_CHECK_EQUAL( transaction_receipt::executed, traces.at(0).status ); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( propose_with_wrong_requested_auth, eosio_msig_tester ) try { - auto trx = reqauth("alice", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } } ); - //try with not enough requested auth - BOOST_REQUIRE_EQUAL( error("transaction declares authority '{\"actor\":\"bob\",\"permission\":\"active\"}', but does not have signatures for it."), - push_action( N(alice), N(propose), mvo() - ("proposer", "alice") - ("proposal_name", "third") - ("trx", trx) - ("requested", vector{ { N(alice), config::active_name } } ) - )); -} FC_LOG_AND_RETHROW() - - -BOOST_FIXTURE_TEST_CASE( big_transaction, eosio_msig_tester ) try { - vector perm = { { N(alice), config::active_name }, { N(bob), config::active_name } }; - auto wasm = wast_to_wasm( exchange_wast ); - - variant pretty_trx = fc::mutable_variant_object() - ("expiration", "2020-01-01T00:30") - ("region", 0) - ("ref_block_num", 2) - ("ref_block_prefix", 3) - ("max_net_usage_words", 0) - ("max_kcpu_usage", 0) - ("delay_sec", 0) - ("actions", fc::variants({ - fc::mutable_variant_object() - ("account", name(config::system_account_name)) - ("name", "setcode") - ("authorization", perm) - ("data", fc::mutable_variant_object() - ("account", "alice") - ("vmtype", 0) - ("vmversion", 0) - ("code", bytes( wasm.begin(), wasm.end() )) - ) - }) - ); - - transaction trx; - contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver()); - - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(propose), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("trx", trx) - ("requested", perm) - )); - - //approve by alice - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(alice), config::active_name }) - )); - //approve by bob and execute - BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(approve), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("level", permission_level{ N(bob), config::active_name }) - )); - BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") - )); - auto traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); - BOOST_CHECK_EQUAL( 1, traces.at(0).action_traces.size() ); - BOOST_CHECK_EQUAL( transaction_receipt::executed, traces.at(0).status ); -} FC_LOG_AND_RETHROW() - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 7e3c9b61f9d..42a6e37b5a0 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,5 +1,6 @@ configure_file( eosiocpp.in eosiocpp @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/llvm-gcov.sh ${CMAKE_CURRENT_BINARY_DIR}/llvm-gcov.sh COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ctestwrapper.sh ${CMAKE_CURRENT_BINARY_DIR}/ctestwrapper.sh COPYONLY) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/eosiocpp DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE diff --git a/tools/ctestwrapper.sh b/tools/ctestwrapper.sh new file mode 100755 index 00000000000..271cb12c556 --- /dev/null +++ b/tools/ctestwrapper.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# run ctest, disregard failure code +ctest --output-on-failure $@ +exit 0 diff --git a/tools/eosiocpp.in b/tools/eosiocpp.in index a36cdb7c049..4dad00491fa 100755 --- a/tools/eosiocpp.in +++ b/tools/eosiocpp.in @@ -28,13 +28,13 @@ function copy_skeleton { function build_contract { set -e workdir=`mktemp -d` -# echo mkdir $workdir/built - mkdir $workdir/built if [[ ${VERBOSE} == "1" ]]; then PRINT_CMDS="set -x" fi + ($PRINT_CMDS; mkdir $workdir/built) + for file in $@; do name=`basename $file` filePath=`dirname $file` @@ -52,15 +52,17 @@ function build_contract { done ($PRINT_CMDS; @WASM_LLVM_LINK@ -only-needed -o $workdir/linked.bc $workdir/built/* \ - ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc \ + ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc \ ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc++.bc \ - ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc + ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc + + ) ($PRINT_CMDS; @WASM_LLC@ -thread-model=single --asm-verbose=false -o $workdir/assembly.s $workdir/linked.bc) ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-s2wasm -o $outname -s 16384 $workdir/assembly.s) + ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-wast2wasm $outname ${outname%.*}.wasm -n) -# echo rm -rf $workdir - rm -rf $workdir + ($PRINT_CMDS; rm -rf $workdir) set +e } @@ -102,6 +104,7 @@ function print_help { echo " OR" echo " -o | --outname [output.wast] [input.cpp ...]" echo " Generate the wast output file based on input cpp files" + echo " The wasm output will also be created as output.wasm" echo " OR" echo " -g | --genabi contract.abi types.hpp" echo " Generate the ABI specification file [EXPERIMENTAL]" diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt new file mode 100644 index 00000000000..e5c9f707224 --- /dev/null +++ b/unittests/CMakeLists.txt @@ -0,0 +1,87 @@ +#file(GLOB COMMON_SOURCES "common/*.cpp") + +find_package( Gperftools QUIET ) +if( GPERFTOOLS_FOUND ) + message( STATUS "Found gperftools; compiling tests with TCMalloc") + list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) +endif() + +find_package(LLVM 4.0 REQUIRED CONFIG) + +link_directories(${LLVM_LIBRARY_DIR}) + +set( CMAKE_CXX_STANDARD 14 ) + +include_directories("${CMAKE_BINARY_DIR}/contracts") +include_directories("${CMAKE_SOURCE_DIR}/contracts") +include_directories("${CMAKE_SOURCE_DIR}/unittests/contracts") +include_directories("${CMAKE_SOURCE_DIR}/libraries/testing/include") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/config.hpp ESCAPE_QUOTES) + +file(GLOB UNIT_TESTS "*.cpp") + +add_executable( unit_test ${UNIT_TESTS} ${WASM_UNIT_TESTS} ) +target_link_libraries( unit_test eosio_chain chainbase eosio_testing eos_utilities abi_generator fc ${PLATFORM_SPECIFIC_LIBS} ) + +target_include_directories( unit_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CMAKE_CURRENT_BINARY_DIR}/tests/contracts ) +target_include_directories( unit_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/wasm_tests ) +target_include_directories( unit_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) +add_dependencies(unit_test asserter test_api test_api_mem test_api_db test_api_multi_index exchange eosio.token proxy identity identity_test stltest infinite eosio.system eosio.token eosio.bios test.inline multi_index_test noop dice eosio.msig payloadless tic_tac_toe) + +#Manually run unit_test for all supported runtimes +#To run unit_test with all log from blockchain displayed, put --verbose after --, i.e. unit_test -- --verbose +add_test(NAME unit_test_binaryen COMMAND unit_test + -t \!wasm_tests/weighted_cpu_limit_tests + --report_level=detailed --color_output -- --binaryen) +add_test(NAME unit_test_wavm COMMAND unit_test + -t \!wasm_tests/weighted_cpu_limit_tests + --report_level=detailed --color_output --catch_system_errors=no -- --wavm) + +if(ENABLE_COVERAGE_TESTING) + + set(Coverage_NAME ${PROJECT_NAME}_ut_coverage) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT LLVMCOV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # no spaces allowed within tests list + set(ctest_tests 'unit_test_binaryen|unit_test_wavm') + set(ctest_exclude_tests '') + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup lcov + COMMAND ${LCOV_PATH} --directory . --zerocounters + + # Run tests + COMMAND ./tools/ctestwrapper.sh -R ${ctest_tests} -E ${ctest_exclude_tests} + + COMMAND ${LCOV_PATH} --directory . --capture --gcov-tool ./tools/llvm-gcov.sh --output-file ${Coverage_NAME}.info + + COMMAND ${LCOV_PATH} -remove ${Coverage_NAME}.info '*/boost/*' '/usr/lib/*' '/usr/include/*' '*/externals/*' '*/fc/*' '*/wasm-jit/*' --output-file ${Coverage_NAME}_filtered.info + + COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}_filtered.info + + COMMAND if [ "$CI" != "true" ]\; then ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.info ${Coverage_NAME}_filtered.info ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned ${PROJECT_BINARY_DIR}/${Coverage_NAME}_filtered.info.cleaned\; fi + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report. Report published in ./${Coverage_NAME}" + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) +endif() diff --git a/tests/tests/abi_tests.cpp b/unittests/abi_tests.cpp similarity index 70% rename from tests/tests/abi_tests.cpp rename to unittests/abi_tests.cpp index 3d948c03481..07080919a46 100644 --- a/tests/tests/abi_tests.cpp +++ b/unittests/abi_tests.cpp @@ -14,15 +14,17 @@ #include #include -#include -#include +#include +#include +#include #include -#include "config.hpp" #include + +#include "config.hpp" + using namespace eosio; using namespace chain; -using namespace chain::contracts; BOOST_AUTO_TEST_SUITE(abi_tests) @@ -42,17 +44,18 @@ fc::variant verify_byte_round_trip_conversion( const abi_serializer& abis, const return var2; } -auto get_resolver(const contracts::abi_def& abi = contracts::abi_def()) +auto get_resolver(const abi_def& abi = abi_def()) { - return [&abi](const account_name &name) -> optional { - return abi_serializer(chain_initializer::eos_contract_abi(abi)); + return [&abi](const account_name &name) -> optional { + return abi_serializer(eosio_contract_abi(abi)); }; } // verify that round trip conversion, via actual class, reproduces the exact same data template fc::variant verify_type_round_trip_conversion( const abi_serializer& abis, const type_name& type, const fc::variant& var ) -{ +{ try { + auto bytes = abis.variant_to_binary(type, var); T obj; @@ -69,9 +72,10 @@ fc::variant verify_type_round_trip_conversion( const abi_serializer& abis, const BOOST_TEST( fc::to_hex(bytes) == fc::to_hex(bytes2) ); return var2; -} +} FC_LOG_AND_RETHROW() } -const char* my_abi = R"=====( + + const char* my_abi = R"=====( { "types": [], "structs": [{ @@ -328,7 +332,7 @@ const char* my_abi = R"=====( ], "actions": [], "tables": [], - "ricardian_clauses": [{"id":"clause A","body":"clause body A"}, + "ricardian_clauses": [{"id":"clause A","body":"clause body A"}, {"id":"clause B","body":"clause body B"}] } )====="; @@ -364,7 +368,7 @@ BOOST_AUTO_TEST_CASE(uint_types) auto abi = fc::json::from_string(currency_abi).as(); - abi_serializer abis(chain_initializer::eos_contract_abi(abi)); + abi_serializer abis(eosio_contract_abi(abi)); abis.validate(); const char* test_data = R"=====( @@ -382,7 +386,7 @@ BOOST_AUTO_TEST_CASE(uint_types) } FC_LOG_AND_RETHROW() } -using namespace eosio::tests::config; +using namespace eosio::unittests::config; struct abi_gen_helper { @@ -419,7 +423,7 @@ struct abi_gen_helper { ); FC_ASSERT(res == true); - abi_serializer(chain_initializer::eos_contract_abi(output)).validate(); + abi_serializer(eosio_contract_abi(output)).validate(); auto abi1 = fc::json::from_string(abi).as(); @@ -453,215 +457,153 @@ BOOST_FIXTURE_TEST_CASE(abigen_unknown_type, abi_gen_helper) } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(abigen_all_types, abi_gen_helper) -{ -#if 0 - try { +{ try { const char* all_types = R"=====( - #include - #include - #include - - typedef int field; - typedef int struct_def; - typedef int fields; - typedef int permission_level; - typedef int action; - typedef int permission_level_weight; - typedef int transaction; - typedef int signed_transaction; - typedef int key_weight; - typedef int authority; - typedef int chain_config; - typedef int type_def; - typedef int action_def; - typedef int table_def; - typedef int abi_def; - typedef int nonce; + #pragma GCC diagnostic ignored "-Wpointer-bool-conversion" + #include + #include + #include + #include + + typedef eosio::symbol_type symbol; //@abi action struct test_struct { - std::string field1; - time field2; - signature field3; - checksum256 field4; - field_name field5; - fixed_string32 field6; - fixed_string16 field7; - type_name field8; - uint8_t field9; - uint16_t field10; - uint32_t field11; - uint64_t field12; - uint128_t field13; - //uint256 field14; - int8_t field15; - int16_t field16; - int32_t field17; - int64_t field18; - eosio::name field19; - field field20; - struct_def field21; - fields field22; - account_name field23; - permission_name field24; - action_name field25; - scope_name field26; - permission_level field27; - action field28; - permission_level_weight field29; - transaction field30; - signed_transaction field31; - key_weight field32; - authority field33; - chain_config field34; - type_def field35; - action_def field36; - table_def field37; - abi_def field38; - public_key field39; - eosio::asset field40; + std::string field1; + time field2; + signature field3; + checksum256 field4; + field_name field5; + fixed_string32 field6; + fixed_string16 field7; + type_name field8; + uint8_t field9; + uint16_t field10; + uint32_t field11; + uint64_t field12; + uint128_t field13; + int8_t field15; + int16_t field16; + int32_t field17; + int64_t field18; + eosio::name field19; + account_name field23; + permission_name field24; + action_name field25; + scope_name field26; + eosio::permission_level field27; + public_key field39; + eosio::asset field40; + eosio::extended_asset field41; + symbol field42; }; )====="; const char* all_types_abi = R"=====( { - "types": [], - "structs": [{ - "name" : "test_struct", - "base" : "", - "fields" : [{ - "name": "field1", - "type": "string" - },{ + "types": [], + "structs": [{ + "name": "test_struct", + "base": "", + "fields": [{ + "name": "field1", + "type": "string" + },{ "name": "field2", "type": "time" - },{ + },{ "name": "field3", "type": "signature" - },{ + },{ "name": "field4", "type": "checksum256" - },{ + },{ "name": "field5", "type": "field_name" - },{ + },{ "name": "field6", "type": "fixed_string32" - },{ + },{ "name": "field7", "type": "fixed_string16" - },{ + },{ "name": "field8", "type": "type_name" - },{ + },{ "name": "field9", "type": "uint8" - },{ + },{ "name": "field10", "type": "uint16" - },{ + },{ "name": "field11", "type": "uint32" - },{ + },{ "name": "field12", "type": "uint64" - },{ + },{ "name": "field13", "type": "uint128" - },{ + },{ "name": "field15", "type": "int8" - },{ + },{ "name": "field16", "type": "int16" - },{ + },{ "name": "field17", "type": "int32" - },{ + },{ "name": "field18", "type": "int64" - },{ + },{ "name": "field19", "type": "name" - },{ - "name": "field20", - "type": "field" - },{ - "name": "field21", - "type": "struct_def" - },{ - "name": "field22", - "type": "fields" - },{ + },{ "name": "field23", "type": "account_name" - },{ + },{ "name": "field24", "type": "permission_name" - },{ + },{ "name": "field25", "type": "action_name" - },{ + },{ "name": "field26", "type": "scope_name" - },{ + },{ "name": "field27", "type": "permission_level" - },{ - "name": "field28", - "type": "action" - },{ - "name": "field29", - "type": "permission_level_weight" - },{ - "name": "field30", - "type": "transaction" - },{ - "name": "field31", - "type": "signed_transaction" - },{ - "name": "field32", - "type": "key_weight" - },{ - "name": "field33", - "type": "authority" - },{ - "name": "field34", - "type": "chain_config" - },{ - "name": "field35", - "type": "type_def" - },{ - "name": "field36", - "type": "action_def" - },{ - "name": "field37", - "type": "table_def" - },{ - "name": "field38", - "type": "abi_def" - },{ + },{ "name": "field39", "type": "public_key" - },{ + },{ "name": "field40", "type": "asset" - }] - }], - "actions": [{ - "name" : "teststruct", - "type" : "test_struct" - }], - "tables": [], - "ricardian_clauses": [] + },{ + "name": "field41", + "type": "extended_asset" + },{ + "name": "field42", + "type": "symbol" + } + ] + } + ], + "actions": [{ + "name": "teststruct", + "type": "test_struct", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [] } )====="; BOOST_TEST( generate_abi(all_types, all_types_abi) == true); -} FC_LOG_AND_RETHROW() -#endif -} +} FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(abigen_double_base, abi_gen_helper) { try { @@ -1786,7 +1728,7 @@ BOOST_FIXTURE_TEST_CASE(abgigen_contract_inheritance, abi_gen_helper) BOOST_AUTO_TEST_CASE(general) { try { - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(my_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(my_abi).as()); abi_serializer abis(abi); abis.validate(); @@ -1862,32 +1804,29 @@ BOOST_AUTO_TEST_CASE(general) "ref_block_num":"1", "ref_block_prefix":"2", "expiration":"2021-12-20T15:30", - "region": "1", "context_free_actions":[{"account":"contextfree1", "name":"cfactionname1", "authorization":[{"actor":"cfacc1","permission":"cfpermname1"}], "data":"778899"}], "actions":[{"account":"accountname1", "name":"actionname1", "authorization":[{"actor":"acc1","permission":"permname1"}], "data":"445566"}], "max_net_usage_words":15, - "max_kcpu_usage":43, + "max_cpu_usage_ms":43, "delay_sec":0 }, "transaction_arr": [{ "ref_block_num":"1", "ref_block_prefix":"2", "expiration":"2021-12-20T15:30", - "region": "1", "context_free_actions":[{"account":"contextfree1", "name":"cfactionname1", "authorization":[{"actor":"cfacc1","permission":"cfpermname1"}], "data":"778899"}], "actions":[{"account":"acc1", "name":"actionname1", "authorization":[{"actor":"acc1","permission":"permname1"}], "data":"445566"}], "max_net_usage_words":15, - "max_kcpu_usage":43, + "max_cpu_usage_ms":43, "delay_sec":0 },{ "ref_block_num":"2", "ref_block_prefix":"3", "expiration":"2021-12-20T15:40", - "region": "1", "context_free_actions":[{"account":"contextfree1", "name":"cfactionname1", "authorization":[{"actor":"cfacc1","permission":"cfpermname1"}], "data":"778899"}], "actions":[{"account":"acc2", "name":"actionname2", "authorization":[{"actor":"acc2","permission":"permname2"}], "data":""}], "max_net_usage_words":21, - "max_kcpu_usage":87, + "max_cpu_usage_ms":87, "delay_sec":0 }], "strx": { @@ -1900,7 +1839,7 @@ BOOST_AUTO_TEST_CASE(general) "context_free_actions":[{"account":"contextfree1", "name":"cfactionname1", "authorization":[{"actor":"cfacc1","permission":"cfpermname1"}], "data":"778899"}], "actions":[{"account":"accountname1", "name":"actionname1", "authorization":[{"actor":"acc1","permission":"permname1"}], "data":"445566"}], "max_net_usage_words":15, - "max_kcpu_usage":43, + "max_cpu_usage_ms":43, "delay_sec":0 }, "strx_arr": [{ @@ -1913,7 +1852,7 @@ BOOST_AUTO_TEST_CASE(general) "context_free_actions":[{"account":"contextfree1", "name":"cfactionname1", "authorization":[{"actor":"cfacc1","permission":"cfpermname1"}], "data":"778899"}], "actions":[{"account":"acc1", "name":"actionname1", "authorization":[{"actor":"acc1","permission":"permname1"}], "data":"445566"}], "max_net_usage_words":15, - "max_kcpu_usage":43, + "max_cpu_usage_ms":43, "delay_sec":0 },{ "ref_block_num":"2", @@ -1925,7 +1864,7 @@ BOOST_AUTO_TEST_CASE(general) "context_free_actions":[{"account":"contextfree2", "name":"cfactionname2", "authorization":[{"actor":"cfacc2","permission":"cfpermname2"}], "data":"667788"}], "actions":[{"account":"acc2", "name":"actionname2", "authorization":[{"actor":"acc2","permission":"permname2"}], "data":""}], "max_net_usage_words":15, - "max_kcpu_usage":43, + "max_cpu_usage_ms":43, "delay_sec":0 }], "keyweight": {"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":"100"}, @@ -1933,16 +1872,19 @@ BOOST_AUTO_TEST_CASE(general) "authority": { "threshold":"10", "keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":100},{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":200}], - "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}] + "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}], + "waits":[] }, "authority_arr": [{ "threshold":"10", "keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":"100"},{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":"200"}], - "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}] + "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}], + "waits":[] },{ "threshold":"10", "keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":"100"},{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "weight":"200"}], - "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}] + "accounts":[{"permission":{"actor":"acc1","permission":"permname1"},"weight":"1"},{"permission":{"actor":"acc2","permission":"permname2"},"weight":"2"}], + "waits":[] }], "typedef" : {"new_type_name":"new", "type":"old"}, "typedef_arr": [{"new_type_name":"new", "type":"old"},{"new_type_name":"new", "type":"old"}], @@ -2022,7 +1964,7 @@ BOOST_AUTO_TEST_CASE(abi_cycle) } )====="; - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(typedef_cycle_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(typedef_cycle_abi).as()); abi_serializer abis(abi); auto is_assert_exception = [](fc::assert_exception const & e) -> bool { @@ -2036,10 +1978,10 @@ BOOST_AUTO_TEST_CASE(abi_cycle) } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(linkauth) +BOOST_AUTO_TEST_CASE(linkauth_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); BOOST_CHECK(true); const char* test_data = R"=====( @@ -2053,27 +1995,27 @@ BOOST_AUTO_TEST_CASE(linkauth) auto var = fc::json::from_string(test_data); - auto linkauth = var.as(); - BOOST_TEST("lnkauth.acct" == linkauth.account); - BOOST_TEST("lnkauth.code" == linkauth.code); - BOOST_TEST("lnkauth.type" == linkauth.type); - BOOST_TEST("lnkauth.rqm" == linkauth.requirement); + auto lauth = var.as(); + BOOST_TEST("lnkauth.acct" == lauth.account); + BOOST_TEST("lnkauth.code" == lauth.code); + BOOST_TEST("lnkauth.type" == lauth.type); + BOOST_TEST("lnkauth.rqm" == lauth.requirement); auto var2 = verify_byte_round_trip_conversion( abis, "linkauth", var ); - auto linkauth2 = var2.as(); - BOOST_TEST(linkauth.account == linkauth2.account); - BOOST_TEST(linkauth.code == linkauth2.code); - BOOST_TEST(linkauth.type == linkauth2.type); - BOOST_TEST(linkauth.requirement == linkauth2.requirement); + auto linkauth2 = var2.as(); + BOOST_TEST(lauth.account == linkauth2.account); + BOOST_TEST(lauth.code == linkauth2.code); + BOOST_TEST(lauth.type == linkauth2.type); + BOOST_TEST(lauth.requirement == linkauth2.requirement); - verify_type_round_trip_conversion( abis, "linkauth", var); + verify_type_round_trip_conversion( abis, "linkauth", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(unlinkauth) +BOOST_AUTO_TEST_CASE(unlinkauth_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); BOOST_CHECK(true); const char* test_data = R"=====( @@ -2086,25 +2028,25 @@ BOOST_AUTO_TEST_CASE(unlinkauth) auto var = fc::json::from_string(test_data); - auto unlinkauth = var.as(); - BOOST_TEST("lnkauth.acct" == unlinkauth.account); - BOOST_TEST("lnkauth.code" == unlinkauth.code); - BOOST_TEST("lnkauth.type" == unlinkauth.type); + auto unlauth = var.as(); + BOOST_TEST("lnkauth.acct" == unlauth.account); + BOOST_TEST("lnkauth.code" == unlauth.code); + BOOST_TEST("lnkauth.type" == unlauth.type); auto var2 = verify_byte_round_trip_conversion( abis, "unlinkauth", var ); - auto unlinkauth2 = var2.as(); - BOOST_TEST(unlinkauth.account == unlinkauth2.account); - BOOST_TEST(unlinkauth.code == unlinkauth2.code); - BOOST_TEST(unlinkauth.type == unlinkauth2.type); + auto unlinkauth2 = var2.as(); + BOOST_TEST(unlauth.account == unlinkauth2.account); + BOOST_TEST(unlauth.code == unlinkauth2.code); + BOOST_TEST(unlauth.type == unlinkauth2.type); - verify_type_round_trip_conversion( abis, "unlinkauth", var); + verify_type_round_trip_conversion( abis, "unlinkauth", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(updateauth) +BOOST_AUTO_TEST_CASE(updateauth_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); BOOST_CHECK(true); const char* test_data = R"=====( @@ -2112,69 +2054,69 @@ BOOST_AUTO_TEST_CASE(updateauth) "account" : "updauth.acct", "permission" : "updauth.prm", "parent" : "updauth.prnt", - "data" : { + "auth" : { "threshold" : "2147483145", "keys" : [ {"key" : "EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im", "weight" : 57005}, {"key" : "EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf", "weight" : 57605} ], "accounts" : [ {"permission" : {"actor" : "prm.acct1", "permission" : "prm.prm1"}, "weight" : 53005 }, - {"permission" : {"actor" : "prm.acct2", "permission" : "prm.prm2"}, "weight" : 53405 }] - }, - "delay" : 0 + {"permission" : {"actor" : "prm.acct2", "permission" : "prm.prm2"}, "weight" : 53405 } ], + "waits" : [] + } } )====="; auto var = fc::json::from_string(test_data); - auto updateauth = var.as(); - BOOST_TEST("updauth.acct" == updateauth.account); - BOOST_TEST("updauth.prm" == updateauth.permission); - BOOST_TEST("updauth.prnt" == updateauth.parent); - BOOST_TEST(2147483145u == updateauth.data.threshold); - - BOOST_TEST_REQUIRE(2 == updateauth.data.keys.size()); - BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)updateauth.data.keys[0].key); - BOOST_TEST(57005u == updateauth.data.keys[0].weight); - BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)updateauth.data.keys[1].key); - BOOST_TEST(57605u == updateauth.data.keys[1].weight); - - BOOST_TEST_REQUIRE(2 == updateauth.data.accounts.size()); - BOOST_TEST("prm.acct1" == updateauth.data.accounts[0].permission.actor); - BOOST_TEST("prm.prm1" == updateauth.data.accounts[0].permission.permission); - BOOST_TEST(53005u == updateauth.data.accounts[0].weight); - BOOST_TEST("prm.acct2" == updateauth.data.accounts[1].permission.actor); - BOOST_TEST("prm.prm2" == updateauth.data.accounts[1].permission.permission); - BOOST_TEST(53405u == updateauth.data.accounts[1].weight); + auto updauth = var.as(); + BOOST_TEST("updauth.acct" == updauth.account); + BOOST_TEST("updauth.prm" == updauth.permission); + BOOST_TEST("updauth.prnt" == updauth.parent); + BOOST_TEST(2147483145u == updauth.auth.threshold); + + BOOST_TEST_REQUIRE(2 == updauth.auth.keys.size()); + BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)updauth.auth.keys[0].key); + BOOST_TEST(57005u == updauth.auth.keys[0].weight); + BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)updauth.auth.keys[1].key); + BOOST_TEST(57605u == updauth.auth.keys[1].weight); + + BOOST_TEST_REQUIRE(2 == updauth.auth.accounts.size()); + BOOST_TEST("prm.acct1" == updauth.auth.accounts[0].permission.actor); + BOOST_TEST("prm.prm1" == updauth.auth.accounts[0].permission.permission); + BOOST_TEST(53005u == updauth.auth.accounts[0].weight); + BOOST_TEST("prm.acct2" == updauth.auth.accounts[1].permission.actor); + BOOST_TEST("prm.prm2" == updauth.auth.accounts[1].permission.permission); + BOOST_TEST(53405u == updauth.auth.accounts[1].weight); auto var2 = verify_byte_round_trip_conversion( abis, "updateauth", var ); - auto updateauth2 = var2.as(); - BOOST_TEST(updateauth.account == updateauth2.account); - BOOST_TEST(updateauth.permission == updateauth2.permission); - BOOST_TEST(updateauth.parent == updateauth2.parent); + auto updateauth2 = var2.as(); + BOOST_TEST(updauth.account == updateauth2.account); + BOOST_TEST(updauth.permission == updateauth2.permission); + BOOST_TEST(updauth.parent == updateauth2.parent); - BOOST_TEST(updateauth.data.threshold == updateauth2.data.threshold); + BOOST_TEST(updauth.auth.threshold == updateauth2.auth.threshold); - BOOST_TEST_REQUIRE(updateauth.data.keys.size() == updateauth2.data.keys.size()); - BOOST_TEST(updateauth.data.keys[0].key == updateauth2.data.keys[0].key); - BOOST_TEST(updateauth.data.keys[0].weight == updateauth2.data.keys[0].weight); - BOOST_TEST(updateauth.data.keys[1].key == updateauth2.data.keys[1].key); - BOOST_TEST(updateauth.data.keys[1].weight == updateauth2.data.keys[1].weight); + BOOST_TEST_REQUIRE(updauth.auth.keys.size() == updateauth2.auth.keys.size()); + BOOST_TEST(updauth.auth.keys[0].key == updateauth2.auth.keys[0].key); + BOOST_TEST(updauth.auth.keys[0].weight == updateauth2.auth.keys[0].weight); + BOOST_TEST(updauth.auth.keys[1].key == updateauth2.auth.keys[1].key); + BOOST_TEST(updauth.auth.keys[1].weight == updateauth2.auth.keys[1].weight); - BOOST_TEST_REQUIRE(updateauth.data.accounts.size() == updateauth2.data.accounts.size()); - BOOST_TEST(updateauth.data.accounts[0].permission.actor == updateauth2.data.accounts[0].permission.actor); - BOOST_TEST(updateauth.data.accounts[0].permission.permission == updateauth2.data.accounts[0].permission.permission); - BOOST_TEST(updateauth.data.accounts[0].weight == updateauth2.data.accounts[0].weight); - BOOST_TEST(updateauth.data.accounts[1].permission.actor == updateauth2.data.accounts[1].permission.actor); - BOOST_TEST(updateauth.data.accounts[1].permission.permission == updateauth2.data.accounts[1].permission.permission); - BOOST_TEST(updateauth.data.accounts[1].weight == updateauth2.data.accounts[1].weight); + BOOST_TEST_REQUIRE(updauth.auth.accounts.size() == updateauth2.auth.accounts.size()); + BOOST_TEST(updauth.auth.accounts[0].permission.actor == updateauth2.auth.accounts[0].permission.actor); + BOOST_TEST(updauth.auth.accounts[0].permission.permission == updateauth2.auth.accounts[0].permission.permission); + BOOST_TEST(updauth.auth.accounts[0].weight == updateauth2.auth.accounts[0].weight); + BOOST_TEST(updauth.auth.accounts[1].permission.actor == updateauth2.auth.accounts[1].permission.actor); + BOOST_TEST(updauth.auth.accounts[1].permission.permission == updateauth2.auth.accounts[1].permission.permission); + BOOST_TEST(updauth.auth.accounts[1].weight == updateauth2.auth.accounts[1].weight); - verify_type_round_trip_conversion( abis, "updateauth", var); + verify_type_round_trip_conversion( abis, "updateauth", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(deleteauth) +BOOST_AUTO_TEST_CASE(deleteauth_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); BOOST_CHECK(true); const char* test_data = R"=====( @@ -2186,23 +2128,23 @@ BOOST_AUTO_TEST_CASE(deleteauth) auto var = fc::json::from_string(test_data); - auto deleteauth = var.as(); - BOOST_TEST("delauth.acct" == deleteauth.account); - BOOST_TEST("delauth.prm" == deleteauth.permission); + auto delauth = var.as(); + BOOST_TEST("delauth.acct" == delauth.account); + BOOST_TEST("delauth.prm" == delauth.permission); auto var2 = verify_byte_round_trip_conversion( abis, "deleteauth", var ); - auto deleteauth2 = var2.as(); - BOOST_TEST(deleteauth.account == deleteauth2.account); - BOOST_TEST(deleteauth.permission == deleteauth2.permission); + auto deleteauth2 = var2.as(); + BOOST_TEST(delauth.account == deleteauth2.account); + BOOST_TEST(delauth.permission == deleteauth2.permission); - verify_type_round_trip_conversion( abis, "deleteauth", var); + verify_type_round_trip_conversion( abis, "deleteauth", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(newaccount) +BOOST_AUTO_TEST_CASE(newaccount_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); BOOST_CHECK(true); const char* test_data = R"=====( @@ -2222,133 +2164,95 @@ BOOST_AUTO_TEST_CASE(newaccount) {"key" : "EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf", "weight" : 57605} ], "accounts" : [ {"permission" : {"actor" : "prm.acct1", "permission" : "prm.prm1"}, "weight" : 53005 }, {"permission" : {"actor" : "prm.acct2", "permission" : "prm.prm2"}, "weight" : 53405 }] - }, - "recovery" : { - "threshold" : 2145483145, - "keys" : [ {"key" : "EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im", "weight" : 57005}, - {"key" : "EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf", "weight" : 57605} ], - "accounts" : [ {"permission" : {"actor" : "prm.acct1", "permission" : "prm.prm1"}, "weight" : 53005 }, - {"permission" : {"actor" : "prm.acct2", "permission" : "prm.prm2"}, "weight" : 53405 }] - } - } + } } )====="; auto var = fc::json::from_string(test_data); - auto newaccount = var.as(); - BOOST_TEST("newacct.crtr" == newaccount.creator); - BOOST_TEST("newacct.name" == newaccount.name); - - BOOST_TEST(2147483145u == newaccount.owner.threshold); - - BOOST_TEST_REQUIRE(2 == newaccount.owner.keys.size()); - BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)newaccount.owner.keys[0].key); - BOOST_TEST(57005u == newaccount.owner.keys[0].weight); - BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)newaccount.owner.keys[1].key); - BOOST_TEST(57605u == newaccount.owner.keys[1].weight); - - BOOST_TEST_REQUIRE(2 == newaccount.owner.accounts.size()); - BOOST_TEST("prm.acct1" == newaccount.owner.accounts[0].permission.actor); - BOOST_TEST("prm.prm1" == newaccount.owner.accounts[0].permission.permission); - BOOST_TEST(53005u == newaccount.owner.accounts[0].weight); - BOOST_TEST("prm.acct2" == newaccount.owner.accounts[1].permission.actor); - BOOST_TEST("prm.prm2" == newaccount.owner.accounts[1].permission.permission); - BOOST_TEST(53405u == newaccount.owner.accounts[1].weight); - - BOOST_TEST(2146483145u == newaccount.active.threshold); - - BOOST_TEST_REQUIRE(2 == newaccount.active.keys.size()); - BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)newaccount.active.keys[0].key); - BOOST_TEST(57005u == newaccount.active.keys[0].weight); - BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)newaccount.active.keys[1].key); - BOOST_TEST(57605u == newaccount.active.keys[1].weight); - - BOOST_TEST_REQUIRE(2 == newaccount.active.accounts.size()); - BOOST_TEST("prm.acct1" == newaccount.active.accounts[0].permission.actor); - BOOST_TEST("prm.prm1" == newaccount.active.accounts[0].permission.permission); - BOOST_TEST(53005u == newaccount.active.accounts[0].weight); - BOOST_TEST("prm.acct2" == newaccount.active.accounts[1].permission.actor); - BOOST_TEST("prm.prm2" == newaccount.active.accounts[1].permission.permission); - BOOST_TEST(53405u == newaccount.active.accounts[1].weight); - - BOOST_TEST(2145483145u == newaccount.recovery.threshold); - - BOOST_TEST_REQUIRE(2 == newaccount.recovery.keys.size()); - BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)newaccount.recovery.keys[0].key); - BOOST_TEST(57005u == newaccount.recovery.keys[0].weight); - BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)newaccount.recovery.keys[1].key); - BOOST_TEST(57605u == newaccount.recovery.keys[1].weight); - - BOOST_TEST_REQUIRE(2 == newaccount.recovery.accounts.size()); - BOOST_TEST("prm.acct1" == newaccount.recovery.accounts[0].permission.actor); - BOOST_TEST("prm.prm1" == newaccount.recovery.accounts[0].permission.permission); - BOOST_TEST(53005u == newaccount.recovery.accounts[0].weight); - BOOST_TEST("prm.acct2" == newaccount.recovery.accounts[1].permission.actor); - BOOST_TEST("prm.prm2" == newaccount.recovery.accounts[1].permission.permission); - BOOST_TEST(53405u == newaccount.recovery.accounts[1].weight); + auto newacct = var.as(); + BOOST_TEST("newacct.crtr" == newacct.creator); + BOOST_TEST("newacct.name" == newacct.name); + + BOOST_TEST(2147483145u == newacct.owner.threshold); + + BOOST_TEST_REQUIRE(2 == newacct.owner.keys.size()); + BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)newacct.owner.keys[0].key); + BOOST_TEST(57005u == newacct.owner.keys[0].weight); + BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)newacct.owner.keys[1].key); + BOOST_TEST(57605u == newacct.owner.keys[1].weight); + + BOOST_TEST_REQUIRE(2 == newacct.owner.accounts.size()); + BOOST_TEST("prm.acct1" == newacct.owner.accounts[0].permission.actor); + BOOST_TEST("prm.prm1" == newacct.owner.accounts[0].permission.permission); + BOOST_TEST(53005u == newacct.owner.accounts[0].weight); + BOOST_TEST("prm.acct2" == newacct.owner.accounts[1].permission.actor); + BOOST_TEST("prm.prm2" == newacct.owner.accounts[1].permission.permission); + BOOST_TEST(53405u == newacct.owner.accounts[1].weight); + + BOOST_TEST(2146483145u == newacct.active.threshold); + + BOOST_TEST_REQUIRE(2 == newacct.active.keys.size()); + BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)newacct.active.keys[0].key); + BOOST_TEST(57005u == newacct.active.keys[0].weight); + BOOST_TEST("EOS5eVr9TVnqwnUBNwf9kwMTbrHvX5aPyyEG97dz2b2TNeqWRzbJf" == (std::string)newacct.active.keys[1].key); + BOOST_TEST(57605u == newacct.active.keys[1].weight); + + BOOST_TEST_REQUIRE(2 == newacct.active.accounts.size()); + BOOST_TEST("prm.acct1" == newacct.active.accounts[0].permission.actor); + BOOST_TEST("prm.prm1" == newacct.active.accounts[0].permission.permission); + BOOST_TEST(53005u == newacct.active.accounts[0].weight); + BOOST_TEST("prm.acct2" == newacct.active.accounts[1].permission.actor); + BOOST_TEST("prm.prm2" == newacct.active.accounts[1].permission.permission); + BOOST_TEST(53405u == newacct.active.accounts[1].weight); + auto var2 = verify_byte_round_trip_conversion( abis, "newaccount", var ); - auto newaccount2 = var2.as(); - BOOST_TEST(newaccount.creator == newaccount2.creator); - BOOST_TEST(newaccount.name == newaccount2.name); - - BOOST_TEST(newaccount.owner.threshold == newaccount2.owner.threshold); - - BOOST_TEST_REQUIRE(newaccount.owner.keys.size() == newaccount2.owner.keys.size()); - BOOST_TEST(newaccount.owner.keys[0].key == newaccount2.owner.keys[0].key); - BOOST_TEST(newaccount.owner.keys[0].weight == newaccount2.owner.keys[0].weight); - BOOST_TEST(newaccount.owner.keys[1].key == newaccount2.owner.keys[1].key); - BOOST_TEST(newaccount.owner.keys[1].weight == newaccount2.owner.keys[1].weight); - - BOOST_TEST_REQUIRE(newaccount.owner.accounts.size() == newaccount2.owner.accounts.size()); - BOOST_TEST(newaccount.owner.accounts[0].permission.actor == newaccount2.owner.accounts[0].permission.actor); - BOOST_TEST(newaccount.owner.accounts[0].permission.permission == newaccount2.owner.accounts[0].permission.permission); - BOOST_TEST(newaccount.owner.accounts[0].weight == newaccount2.owner.accounts[0].weight); - BOOST_TEST(newaccount.owner.accounts[1].permission.actor == newaccount2.owner.accounts[1].permission.actor); - BOOST_TEST(newaccount.owner.accounts[1].permission.permission == newaccount2.owner.accounts[1].permission.permission); - BOOST_TEST(newaccount.owner.accounts[1].weight == newaccount2.owner.accounts[1].weight); - - BOOST_TEST(newaccount.active.threshold == newaccount2.active.threshold); - - BOOST_TEST_REQUIRE(newaccount.active.keys.size() == newaccount2.active.keys.size()); - BOOST_TEST(newaccount.active.keys[0].key == newaccount2.active.keys[0].key); - BOOST_TEST(newaccount.active.keys[0].weight == newaccount2.active.keys[0].weight); - BOOST_TEST(newaccount.active.keys[1].key == newaccount2.active.keys[1].key); - BOOST_TEST(newaccount.active.keys[1].weight == newaccount2.active.keys[1].weight); - - BOOST_TEST_REQUIRE(newaccount.active.accounts.size() == newaccount2.active.accounts.size()); - BOOST_TEST(newaccount.active.accounts[0].permission.actor == newaccount2.active.accounts[0].permission.actor); - BOOST_TEST(newaccount.active.accounts[0].permission.permission == newaccount2.active.accounts[0].permission.permission); - BOOST_TEST(newaccount.active.accounts[0].weight == newaccount2.active.accounts[0].weight); - BOOST_TEST(newaccount.active.accounts[1].permission.actor == newaccount2.active.accounts[1].permission.actor); - BOOST_TEST(newaccount.active.accounts[1].permission.permission == newaccount2.active.accounts[1].permission.permission); - BOOST_TEST(newaccount.active.accounts[1].weight == newaccount2.active.accounts[1].weight); - - BOOST_TEST(newaccount.recovery.threshold == newaccount2.recovery.threshold); - - BOOST_TEST_REQUIRE(newaccount.recovery.keys.size() == newaccount2.recovery.keys.size()); - BOOST_TEST(newaccount.recovery.keys[0].key == newaccount2.recovery.keys[0].key); - BOOST_TEST(newaccount.recovery.keys[0].weight == newaccount2.recovery.keys[0].weight); - BOOST_TEST(newaccount.recovery.keys[1].key == newaccount2.recovery.keys[1].key); - BOOST_TEST(newaccount.recovery.keys[1].weight == newaccount2.recovery.keys[1].weight); - - BOOST_TEST_REQUIRE(newaccount.recovery.accounts.size() == newaccount2.recovery.accounts.size()); - BOOST_TEST(newaccount.recovery.accounts[0].permission.actor == newaccount2.recovery.accounts[0].permission.actor); - BOOST_TEST(newaccount.recovery.accounts[0].permission.permission == newaccount2.recovery.accounts[0].permission.permission); - BOOST_TEST(newaccount.recovery.accounts[0].weight == newaccount2.recovery.accounts[0].weight); - BOOST_TEST(newaccount.recovery.accounts[1].permission.actor == newaccount2.recovery.accounts[1].permission.actor); - BOOST_TEST(newaccount.recovery.accounts[1].permission.permission == newaccount2.recovery.accounts[1].permission.permission); - BOOST_TEST(newaccount.recovery.accounts[1].weight == newaccount2.recovery.accounts[1].weight); - - verify_type_round_trip_conversion( abis, "newaccount", var); + auto newaccount2 = var2.as(); + BOOST_TEST(newacct.creator == newaccount2.creator); + BOOST_TEST(newacct.name == newaccount2.name); + + BOOST_TEST(newacct.owner.threshold == newaccount2.owner.threshold); + + BOOST_TEST_REQUIRE(newacct.owner.keys.size() == newaccount2.owner.keys.size()); + BOOST_TEST(newacct.owner.keys[0].key == newaccount2.owner.keys[0].key); + BOOST_TEST(newacct.owner.keys[0].weight == newaccount2.owner.keys[0].weight); + BOOST_TEST(newacct.owner.keys[1].key == newaccount2.owner.keys[1].key); + BOOST_TEST(newacct.owner.keys[1].weight == newaccount2.owner.keys[1].weight); + + BOOST_TEST_REQUIRE(newacct.owner.accounts.size() == newaccount2.owner.accounts.size()); + BOOST_TEST(newacct.owner.accounts[0].permission.actor == newaccount2.owner.accounts[0].permission.actor); + BOOST_TEST(newacct.owner.accounts[0].permission.permission == newaccount2.owner.accounts[0].permission.permission); + BOOST_TEST(newacct.owner.accounts[0].weight == newaccount2.owner.accounts[0].weight); + BOOST_TEST(newacct.owner.accounts[1].permission.actor == newaccount2.owner.accounts[1].permission.actor); + BOOST_TEST(newacct.owner.accounts[1].permission.permission == newaccount2.owner.accounts[1].permission.permission); + BOOST_TEST(newacct.owner.accounts[1].weight == newaccount2.owner.accounts[1].weight); + + BOOST_TEST(newacct.active.threshold == newaccount2.active.threshold); + + BOOST_TEST_REQUIRE(newacct.active.keys.size() == newaccount2.active.keys.size()); + BOOST_TEST(newacct.active.keys[0].key == newaccount2.active.keys[0].key); + BOOST_TEST(newacct.active.keys[0].weight == newaccount2.active.keys[0].weight); + BOOST_TEST(newacct.active.keys[1].key == newaccount2.active.keys[1].key); + BOOST_TEST(newacct.active.keys[1].weight == newaccount2.active.keys[1].weight); + + BOOST_TEST_REQUIRE(newacct.active.accounts.size() == newaccount2.active.accounts.size()); + BOOST_TEST(newacct.active.accounts[0].permission.actor == newaccount2.active.accounts[0].permission.actor); + BOOST_TEST(newacct.active.accounts[0].permission.permission == newaccount2.active.accounts[0].permission.permission); + BOOST_TEST(newacct.active.accounts[0].weight == newaccount2.active.accounts[0].weight); + BOOST_TEST(newacct.active.accounts[1].permission.actor == newaccount2.active.accounts[1].permission.actor); + BOOST_TEST(newacct.active.accounts[1].permission.permission == newaccount2.active.accounts[1].permission.permission); + BOOST_TEST(newacct.active.accounts[1].weight == newaccount2.active.accounts[1].weight); + + + verify_type_round_trip_conversion( abis, "newaccount", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(setcode) +BOOST_AUTO_TEST_CASE(setcode_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); const char* test_data = R"=====( { @@ -2361,27 +2265,27 @@ BOOST_AUTO_TEST_CASE(setcode) auto var = fc::json::from_string(test_data); - auto setcode = var.as(); - BOOST_TEST("setcode.acc" == setcode.account); - BOOST_TEST(0 == setcode.vmtype); - BOOST_TEST(0 == setcode.vmversion); - BOOST_TEST("0061736d0100000001390a60037e7e7f017f60047e7e7f7f017f60017e0060057e7e7e7f7f" == fc::to_hex(setcode.code.data(), setcode.code.size())); + auto set_code = var.as(); + BOOST_TEST("setcode.acc" == set_code.account); + BOOST_TEST(0 == set_code.vmtype); + BOOST_TEST(0 == set_code.vmversion); + BOOST_TEST("0061736d0100000001390a60037e7e7f017f60047e7e7f7f017f60017e0060057e7e7e7f7f" == fc::to_hex(set_code.code.data(), set_code.code.size())); auto var2 = verify_byte_round_trip_conversion( abis, "setcode", var ); - auto setcode2 = var2.as(); - BOOST_TEST(setcode.account == setcode2.account); - BOOST_TEST(setcode.vmtype == setcode2.vmtype); - BOOST_TEST(setcode.vmversion == setcode2.vmversion); - BOOST_TEST(setcode.code == setcode2.code); + auto setcode2 = var2.as(); + BOOST_TEST(set_code.account == setcode2.account); + BOOST_TEST(set_code.vmtype == setcode2.vmtype); + BOOST_TEST(set_code.vmversion == setcode2.vmversion); + BOOST_TEST(set_code.code == setcode2.code); - verify_type_round_trip_conversion( abis, "setcode", var); + verify_type_round_trip_conversion( abis, "setcode", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(setabi) +BOOST_AUTO_TEST_CASE(setabi_test) { try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); + abi_serializer abis(eosio_contract_abi(abi_def())); const char* test_data = R"=====( { @@ -2445,205 +2349,108 @@ BOOST_AUTO_TEST_CASE(setabi) auto var = fc::json::from_string(test_data); - auto setabi = var.as(); - BOOST_TEST("setabi.acc" == setabi.account); - - BOOST_TEST_REQUIRE(1 == setabi.abi.types.size()); - - BOOST_TEST("account_name" == setabi.abi.types[0].new_type_name); - BOOST_TEST("name" == setabi.abi.types[0].type); - - BOOST_TEST_REQUIRE(3 == setabi.abi.structs.size()); - - BOOST_TEST("transfer_base" == setabi.abi.structs[0].name); - BOOST_TEST("" == setabi.abi.structs[0].base); - BOOST_TEST_REQUIRE(1 == setabi.abi.structs[0].fields.size()); - BOOST_TEST("memo" == setabi.abi.structs[0].fields[0].name); - BOOST_TEST("string" == setabi.abi.structs[0].fields[0].type); - - BOOST_TEST("transfer" == setabi.abi.structs[1].name); - BOOST_TEST("transfer_base" == setabi.abi.structs[1].base); - BOOST_TEST_REQUIRE(3 == setabi.abi.structs[1].fields.size()); - BOOST_TEST("from" == setabi.abi.structs[1].fields[0].name); - BOOST_TEST("account_name" == setabi.abi.structs[1].fields[0].type); - BOOST_TEST("to" == setabi.abi.structs[1].fields[1].name); - BOOST_TEST("account_name" == setabi.abi.structs[1].fields[1].type); - BOOST_TEST("amount" == setabi.abi.structs[1].fields[2].name); - BOOST_TEST("uint64" == setabi.abi.structs[1].fields[2].type); - - BOOST_TEST("account" == setabi.abi.structs[2].name); - BOOST_TEST("" == setabi.abi.structs[2].base); - BOOST_TEST_REQUIRE(2 == setabi.abi.structs[2].fields.size()); - BOOST_TEST("account" == setabi.abi.structs[2].fields[0].name); - BOOST_TEST("name" == setabi.abi.structs[2].fields[0].type); - BOOST_TEST("balance" == setabi.abi.structs[2].fields[1].name); - BOOST_TEST("uint64" == setabi.abi.structs[2].fields[1].type); - - BOOST_TEST_REQUIRE(1 == setabi.abi.actions.size()); - BOOST_TEST("transfer" == setabi.abi.actions[0].name); - BOOST_TEST("transfer" == setabi.abi.actions[0].type); - - BOOST_TEST_REQUIRE(1 == setabi.abi.tables.size()); - BOOST_TEST("account" == setabi.abi.tables[0].name); - BOOST_TEST("account" == setabi.abi.tables[0].type); - BOOST_TEST("i64" == setabi.abi.tables[0].index_type); - BOOST_TEST_REQUIRE(1 == setabi.abi.tables[0].key_names.size()); - BOOST_TEST("account" == setabi.abi.tables[0].key_names[0]); - BOOST_TEST_REQUIRE(1 == setabi.abi.tables[0].key_types.size()); - BOOST_TEST("name" == setabi.abi.tables[0].key_types[0]); + auto set_abi = var.as(); + BOOST_TEST("setabi.acc" == set_abi.account); + + BOOST_TEST_REQUIRE(1 == set_abi.abi.types.size()); + + BOOST_TEST("account_name" == set_abi.abi.types[0].new_type_name); + BOOST_TEST("name" == set_abi.abi.types[0].type); + + BOOST_TEST_REQUIRE(3 == set_abi.abi.structs.size()); + + BOOST_TEST("transfer_base" == set_abi.abi.structs[0].name); + BOOST_TEST("" == set_abi.abi.structs[0].base); + BOOST_TEST_REQUIRE(1 == set_abi.abi.structs[0].fields.size()); + BOOST_TEST("memo" == set_abi.abi.structs[0].fields[0].name); + BOOST_TEST("string" == set_abi.abi.structs[0].fields[0].type); + + BOOST_TEST("transfer" == set_abi.abi.structs[1].name); + BOOST_TEST("transfer_base" == set_abi.abi.structs[1].base); + BOOST_TEST_REQUIRE(3 == set_abi.abi.structs[1].fields.size()); + BOOST_TEST("from" == set_abi.abi.structs[1].fields[0].name); + BOOST_TEST("account_name" == set_abi.abi.structs[1].fields[0].type); + BOOST_TEST("to" == set_abi.abi.structs[1].fields[1].name); + BOOST_TEST("account_name" == set_abi.abi.structs[1].fields[1].type); + BOOST_TEST("amount" == set_abi.abi.structs[1].fields[2].name); + BOOST_TEST("uint64" == set_abi.abi.structs[1].fields[2].type); + + BOOST_TEST("account" == set_abi.abi.structs[2].name); + BOOST_TEST("" == set_abi.abi.structs[2].base); + BOOST_TEST_REQUIRE(2 == set_abi.abi.structs[2].fields.size()); + BOOST_TEST("account" == set_abi.abi.structs[2].fields[0].name); + BOOST_TEST("name" == set_abi.abi.structs[2].fields[0].type); + BOOST_TEST("balance" == set_abi.abi.structs[2].fields[1].name); + BOOST_TEST("uint64" == set_abi.abi.structs[2].fields[1].type); + + BOOST_TEST_REQUIRE(1 == set_abi.abi.actions.size()); + BOOST_TEST("transfer" == set_abi.abi.actions[0].name); + BOOST_TEST("transfer" == set_abi.abi.actions[0].type); + + BOOST_TEST_REQUIRE(1 == set_abi.abi.tables.size()); + BOOST_TEST("account" == set_abi.abi.tables[0].name); + BOOST_TEST("account" == set_abi.abi.tables[0].type); + BOOST_TEST("i64" == set_abi.abi.tables[0].index_type); + BOOST_TEST_REQUIRE(1 == set_abi.abi.tables[0].key_names.size()); + BOOST_TEST("account" == set_abi.abi.tables[0].key_names[0]); + BOOST_TEST_REQUIRE(1 == set_abi.abi.tables[0].key_types.size()); + BOOST_TEST("name" == set_abi.abi.tables[0].key_types[0]); auto var2 = verify_byte_round_trip_conversion( abis, "setabi", var ); - auto setabi2 = var2.as(); - - BOOST_TEST(setabi.account == setabi2.account); - - BOOST_TEST_REQUIRE(setabi.abi.types.size() == setabi2.abi.types.size()); - - BOOST_TEST(setabi.abi.types[0].new_type_name == setabi2.abi.types[0].new_type_name); - BOOST_TEST(setabi.abi.types[0].type == setabi2.abi.types[0].type); - - BOOST_TEST_REQUIRE(setabi.abi.structs.size() == setabi2.abi.structs.size()); - - BOOST_TEST(setabi.abi.structs[0].name == setabi2.abi.structs[0].name); - BOOST_TEST(setabi.abi.structs[0].base == setabi2.abi.structs[0].base); - BOOST_TEST_REQUIRE(setabi.abi.structs[0].fields.size() == setabi2.abi.structs[0].fields.size()); - BOOST_TEST(setabi.abi.structs[0].fields[0].name == setabi2.abi.structs[0].fields[0].name); - BOOST_TEST(setabi.abi.structs[0].fields[0].type == setabi2.abi.structs[0].fields[0].type); - - BOOST_TEST(setabi.abi.structs[1].name == setabi2.abi.structs[1].name); - BOOST_TEST(setabi.abi.structs[1].base == setabi2.abi.structs[1].base); - BOOST_TEST_REQUIRE(setabi.abi.structs[1].fields.size() == setabi2.abi.structs[1].fields.size()); - BOOST_TEST(setabi.abi.structs[1].fields[0].name == setabi2.abi.structs[1].fields[0].name); - BOOST_TEST(setabi.abi.structs[1].fields[0].type == setabi2.abi.structs[1].fields[0].type); - BOOST_TEST(setabi.abi.structs[1].fields[1].name == setabi2.abi.structs[1].fields[1].name); - BOOST_TEST(setabi.abi.structs[1].fields[1].type == setabi2.abi.structs[1].fields[1].type); - BOOST_TEST(setabi.abi.structs[1].fields[2].name == setabi2.abi.structs[1].fields[2].name); - BOOST_TEST(setabi.abi.structs[1].fields[2].type == setabi2.abi.structs[1].fields[2].type); - - BOOST_TEST(setabi.abi.structs[2].name == setabi2.abi.structs[2].name); - BOOST_TEST(setabi.abi.structs[2].base == setabi2.abi.structs[2].base); - BOOST_TEST_REQUIRE(setabi.abi.structs[2].fields.size() == setabi2.abi.structs[2].fields.size()); - BOOST_TEST(setabi.abi.structs[2].fields[0].name == setabi2.abi.structs[2].fields[0].name); - BOOST_TEST(setabi.abi.structs[2].fields[0].type == setabi2.abi.structs[2].fields[0].type); - BOOST_TEST(setabi.abi.structs[2].fields[1].name == setabi2.abi.structs[2].fields[1].name); - BOOST_TEST(setabi.abi.structs[2].fields[1].type == setabi2.abi.structs[2].fields[1].type); - - BOOST_TEST_REQUIRE(setabi.abi.actions.size() == setabi2.abi.actions.size()); - BOOST_TEST(setabi.abi.actions[0].name == setabi2.abi.actions[0].name); - BOOST_TEST(setabi.abi.actions[0].type == setabi2.abi.actions[0].type); - - BOOST_TEST_REQUIRE(setabi.abi.tables.size() == setabi2.abi.tables.size()); - BOOST_TEST(setabi.abi.tables[0].name == setabi2.abi.tables[0].name); - BOOST_TEST(setabi.abi.tables[0].type == setabi2.abi.tables[0].type); - BOOST_TEST(setabi.abi.tables[0].index_type == setabi2.abi.tables[0].index_type); - BOOST_TEST_REQUIRE(setabi.abi.tables[0].key_names.size() == setabi2.abi.tables[0].key_names.size()); - BOOST_TEST(setabi.abi.tables[0].key_names[0] == setabi2.abi.tables[0].key_names[0]); - BOOST_TEST_REQUIRE(setabi.abi.tables[0].key_types.size() == setabi2.abi.tables[0].key_types.size()); - BOOST_TEST(setabi.abi.tables[0].key_types[0] == setabi2.abi.tables[0].key_types[0]); - - verify_type_round_trip_conversion( abis, "setabi", var); - -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE(postrecovery) -{ try { - - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); - - const char* test_data = R"=====( - { - "account" : "postrec.acc", - "data": { - "threshold" : "2147483145", - "keys" : [ {"key" : "EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im", "weight" : 57005} ], - "accounts" : [ {"permission" : {"actor" : "postrec.acc", "permission" : "prm.prm1"}, "weight" : 57005 } ] - } - "memo": "postrec.memo" - } - )====="; - - auto var = fc::json::from_string(test_data); - - auto postrecovery = var.as(); - BOOST_TEST("postrec.acc" == postrecovery.account); - BOOST_TEST(2147483145u == postrecovery.data.threshold); - - BOOST_TEST_REQUIRE(1 == postrecovery.data.keys.size()); - BOOST_TEST("EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im" == (std::string)postrecovery.data.keys[0].key); - BOOST_TEST(57005u == postrecovery.data.keys[0].weight); - - BOOST_TEST_REQUIRE(1 == postrecovery.data.accounts.size()); - BOOST_TEST("postrec.acc" == postrecovery.data.accounts[0].permission.actor); - BOOST_TEST("prm.prm1" == postrecovery.data.accounts[0].permission.permission); - BOOST_TEST(57005u == postrecovery.data.accounts[0].weight); - BOOST_TEST("postrec.memo" == postrecovery.memo); - - auto var2 = verify_byte_round_trip_conversion( abis, "postrecovery", var ); - auto postrecovery2 = var2.as(); - BOOST_TEST(postrecovery.account == postrecovery2.account); - BOOST_TEST(postrecovery.data.threshold == postrecovery2.data.threshold); - - BOOST_TEST_REQUIRE(postrecovery.data.keys.size() == postrecovery2.data.keys.size()); - BOOST_TEST(postrecovery.data.keys[0].key == postrecovery2.data.keys[0].key); - BOOST_TEST(postrecovery.data.keys[0].weight == postrecovery2.data.keys[0].weight); - - BOOST_TEST_REQUIRE(postrecovery.data.accounts.size() == postrecovery2.data.accounts.size()); - BOOST_TEST(postrecovery.data.accounts[0].permission.actor == postrecovery2.data.accounts[0].permission.actor); - BOOST_TEST(postrecovery.data.accounts[0].permission.permission == postrecovery2.data.accounts[0].permission.permission); - BOOST_TEST(postrecovery.data.accounts[0].weight == postrecovery2.data.accounts[0].weight); - BOOST_TEST(postrecovery.memo == postrecovery2.memo); - - verify_type_round_trip_conversion( abis, "postrecovery", var); + auto setabi2 = var2.as(); + + BOOST_TEST(set_abi.account == setabi2.account); + + BOOST_TEST_REQUIRE(set_abi.abi.types.size() == setabi2.abi.types.size()); + + BOOST_TEST(set_abi.abi.types[0].new_type_name == setabi2.abi.types[0].new_type_name); + BOOST_TEST(set_abi.abi.types[0].type == setabi2.abi.types[0].type); + + BOOST_TEST_REQUIRE(set_abi.abi.structs.size() == setabi2.abi.structs.size()); + + BOOST_TEST(set_abi.abi.structs[0].name == setabi2.abi.structs[0].name); + BOOST_TEST(set_abi.abi.structs[0].base == setabi2.abi.structs[0].base); + BOOST_TEST_REQUIRE(set_abi.abi.structs[0].fields.size() == setabi2.abi.structs[0].fields.size()); + BOOST_TEST(set_abi.abi.structs[0].fields[0].name == setabi2.abi.structs[0].fields[0].name); + BOOST_TEST(set_abi.abi.structs[0].fields[0].type == setabi2.abi.structs[0].fields[0].type); + + BOOST_TEST(set_abi.abi.structs[1].name == setabi2.abi.structs[1].name); + BOOST_TEST(set_abi.abi.structs[1].base == setabi2.abi.structs[1].base); + BOOST_TEST_REQUIRE(set_abi.abi.structs[1].fields.size() == setabi2.abi.structs[1].fields.size()); + BOOST_TEST(set_abi.abi.structs[1].fields[0].name == setabi2.abi.structs[1].fields[0].name); + BOOST_TEST(set_abi.abi.structs[1].fields[0].type == setabi2.abi.structs[1].fields[0].type); + BOOST_TEST(set_abi.abi.structs[1].fields[1].name == setabi2.abi.structs[1].fields[1].name); + BOOST_TEST(set_abi.abi.structs[1].fields[1].type == setabi2.abi.structs[1].fields[1].type); + BOOST_TEST(set_abi.abi.structs[1].fields[2].name == setabi2.abi.structs[1].fields[2].name); + BOOST_TEST(set_abi.abi.structs[1].fields[2].type == setabi2.abi.structs[1].fields[2].type); + + BOOST_TEST(set_abi.abi.structs[2].name == setabi2.abi.structs[2].name); + BOOST_TEST(set_abi.abi.structs[2].base == setabi2.abi.structs[2].base); + BOOST_TEST_REQUIRE(set_abi.abi.structs[2].fields.size() == setabi2.abi.structs[2].fields.size()); + BOOST_TEST(set_abi.abi.structs[2].fields[0].name == setabi2.abi.structs[2].fields[0].name); + BOOST_TEST(set_abi.abi.structs[2].fields[0].type == setabi2.abi.structs[2].fields[0].type); + BOOST_TEST(set_abi.abi.structs[2].fields[1].name == setabi2.abi.structs[2].fields[1].name); + BOOST_TEST(set_abi.abi.structs[2].fields[1].type == setabi2.abi.structs[2].fields[1].type); + + BOOST_TEST_REQUIRE(set_abi.abi.actions.size() == setabi2.abi.actions.size()); + BOOST_TEST(set_abi.abi.actions[0].name == setabi2.abi.actions[0].name); + BOOST_TEST(set_abi.abi.actions[0].type == setabi2.abi.actions[0].type); + + BOOST_TEST_REQUIRE(set_abi.abi.tables.size() == setabi2.abi.tables.size()); + BOOST_TEST(set_abi.abi.tables[0].name == setabi2.abi.tables[0].name); + BOOST_TEST(set_abi.abi.tables[0].type == setabi2.abi.tables[0].type); + BOOST_TEST(set_abi.abi.tables[0].index_type == setabi2.abi.tables[0].index_type); + BOOST_TEST_REQUIRE(set_abi.abi.tables[0].key_names.size() == setabi2.abi.tables[0].key_names.size()); + BOOST_TEST(set_abi.abi.tables[0].key_names[0] == setabi2.abi.tables[0].key_names[0]); + BOOST_TEST_REQUIRE(set_abi.abi.tables[0].key_types.size() == setabi2.abi.tables[0].key_types.size()); + BOOST_TEST(set_abi.abi.tables[0].key_types[0] == setabi2.abi.tables[0].key_types[0]); + + verify_type_round_trip_conversion( abis, "setabi", var); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(passrecovery) -{ try { - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); - const char* test_data = R"=====( - { - "account" : "passrec.acc" - } - )====="; - - auto var = fc::json::from_string(test_data); - - auto passrecovery = var.as(); - BOOST_TEST("passrec.acc" == passrecovery.account); - - auto var2 = verify_byte_round_trip_conversion( abis, "passrecovery", var ); - auto passrecovery2 = var2.as(); - BOOST_TEST(passrecovery.account == passrecovery2.account); - - verify_type_round_trip_conversion( abis, "passrecovery", var); - -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE(vetorecovery) -{ try { - - abi_serializer abis(chain_initializer::eos_contract_abi(abi_def())); - - const char* test_data = R"=====( - { - "account" : "vetorec.acc" - } - )====="; - - auto var = fc::json::from_string(test_data); - - auto vetorecovery = var.as(); - BOOST_TEST("vetorec.acc" == vetorecovery.account); - - auto var2 = verify_byte_round_trip_conversion( abis, "vetorecovery", var ); - auto vetorecovery2 = var2.as(); - BOOST_TEST(vetorecovery.account == vetorecovery2.account); - - verify_type_round_trip_conversion( abis, "vetorecovery", var); - -} FC_LOG_AND_RETHROW() } struct action1 { action1() = default; @@ -2723,16 +2530,14 @@ BOOST_AUTO_TEST_CASE(packed_transaction) txn.ref_block_num = 1; txn.ref_block_prefix = 2; txn.expiration.from_iso_string("2021-12-20T15:30"); - txn.region = 1; name a = N(alice); txn.context_free_actions.emplace_back( vector{{N(testapi1), config::active_name}}, - contracts::newaccount{ + newaccount{ .creator = config::system_account_name, .name = a, .owner = authority( get_public_key( a, "owner" )), - .active = authority( get_public_key( a, "active" ) ), - .recovery = authority( get_public_key( a, "recovery" ) ), + .active = authority( get_public_key( a, "active" ) ) }); txn.context_free_actions.emplace_back( vector{{N(testapi2), config::active_name}}, @@ -2744,7 +2549,7 @@ BOOST_AUTO_TEST_CASE(packed_transaction) vector{{N(testapi4), config::active_name}}, action2{ 61, 23, (uint8_t)2}); txn.max_net_usage_words = 15; - txn.max_kcpu_usage = 43; + txn.max_cpu_usage_ms = 43; // pack the transaction to verify that the var unpacking logic is correct auto packed_txn = chain::packed_transaction(txn); @@ -2818,7 +2623,6 @@ BOOST_AUTO_TEST_CASE(packed_transaction) BOOST_REQUIRE_EQUAL(txn.ref_block_num, txn2.ref_block_num); BOOST_REQUIRE_EQUAL(txn.ref_block_prefix, txn2.ref_block_prefix); BOOST_REQUIRE(txn.expiration == txn2.expiration); - BOOST_REQUIRE_EQUAL(txn.region, txn2.region); BOOST_REQUIRE_EQUAL(txn.context_free_actions.size(), txn2.context_free_actions.size()); for (unsigned int i = 0; i < txn.context_free_actions.size(); ++i) verify_action_equal(txn.context_free_actions[i], txn2.context_free_actions[i]); @@ -2826,7 +2630,7 @@ BOOST_AUTO_TEST_CASE(packed_transaction) for (unsigned int i = 0; i < txn.actions.size(); ++i) verify_action_equal(txn.actions[i], txn2.actions[i]); BOOST_REQUIRE_EQUAL(txn.max_net_usage_words.value, txn2.max_net_usage_words.value); - BOOST_REQUIRE_EQUAL(txn.max_kcpu_usage.value, txn2.max_kcpu_usage.value); + BOOST_REQUIRE_EQUAL(txn.max_cpu_usage_ms, txn2.max_cpu_usage_ms); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(abi_type_repeat) @@ -2884,7 +2688,7 @@ BOOST_AUTO_TEST_CASE(abi_type_repeat) } )====="; - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(repeat_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(repeat_abi).as()); auto is_table_exception = [](fc::assert_exception const & e) -> bool { return e.to_detail_string().find("types.size") != std::string::npos; }; BOOST_CHECK_EXCEPTION( abi_serializer abis(abi), fc::assert_exception, is_table_exception ); } FC_LOG_AND_RETHROW() } @@ -2941,7 +2745,7 @@ BOOST_AUTO_TEST_CASE(abi_struct_repeat) } )====="; - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(repeat_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(repeat_abi).as()); auto is_table_exception = [](fc::assert_exception const & e) -> bool { return e.to_detail_string().find("structs.size") != std::string::npos; }; BOOST_CHECK_EXCEPTION( abi_serializer abis(abi), fc::assert_exception, is_table_exception ); } FC_LOG_AND_RETHROW() } @@ -3001,7 +2805,7 @@ BOOST_AUTO_TEST_CASE(abi_action_repeat) } )====="; - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(repeat_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(repeat_abi).as()); auto is_table_exception = [](fc::assert_exception const & e) -> bool { return e.to_detail_string().find("actions.size") != std::string::npos; }; BOOST_CHECK_EXCEPTION( abi_serializer abis(abi), fc::assert_exception, is_table_exception ); } FC_LOG_AND_RETHROW() } @@ -3064,7 +2868,7 @@ BOOST_AUTO_TEST_CASE(abi_table_repeat) } )====="; - auto abi = chain_initializer::eos_contract_abi(fc::json::from_string(repeat_abi).as()); + auto abi = eosio_contract_abi(fc::json::from_string(repeat_abi).as()); auto is_table_exception = [](fc::assert_exception const & e) -> bool { return e.to_detail_string().find("tables.size") != std::string::npos; }; BOOST_CHECK_EXCEPTION( abi_serializer abis(abi), fc::assert_exception, is_table_exception ); } FC_LOG_AND_RETHROW() } diff --git a/tests/api_tests/api_tests.cpp b/unittests/api_tests.cpp similarity index 65% rename from tests/api_tests/api_tests.cpp rename to unittests/api_tests.cpp index 5f6e6619400..8d2239fbe98 100644 --- a/tests/api_tests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -19,12 +19,13 @@ #include #include -#include #include #include -#include +#include #include +#include #include +#include #include #include @@ -49,6 +50,7 @@ FC_REFLECT( dummy_action, (a)(b)(c) ) FC_REFLECT( u128_action, (values) ) FC_REFLECT( cf_action, (payload)(cfd_idx) ) +FC_REFLECT( dtt_action, (payer)(deferred_account)(deferred_action)(permission_name)(delay_sec) ) FC_REFLECT( invalid_access_action, (code)(val)(index)(store) ) #ifdef NON_VALIDATING_TEST @@ -60,7 +62,6 @@ FC_REFLECT( invalid_access_action, (code)(val)(index)(store) ) using namespace eosio; using namespace eosio::testing; using namespace chain; -using namespace chain::contracts; using namespace fc; namespace bio = boost::iostreams; @@ -76,7 +77,7 @@ struct test_api_action { } }; -FC_REFLECT_TEMPLATE((uint64_t T), test_api_action, BOOST_PP_SEQ_NIL); +FC_REFLECT_TEMPLATE((uint64_t T), test_api_action, BOOST_PP_SEQ_NIL) template struct test_chain_action { @@ -89,7 +90,7 @@ struct test_chain_action { } }; -FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action, BOOST_PP_SEQ_NIL); +FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action, BOOST_PP_SEQ_NIL) struct check_auth { account_name account; @@ -97,12 +98,15 @@ struct check_auth { vector pubkeys; }; -FC_REFLECT(check_auth, (account)(permission)(pubkeys) ); +FC_REFLECT(check_auth, (account)(permission)(pubkeys) ) -bool expect_assert_message(const fc::exception& ex, string expected) { - BOOST_TEST_MESSAGE("LOG : " << "expected: " << expected << ", actual: " << ex.get_log().at(0).get_message()); - return (ex.get_log().at(0).get_message().find(expected) != std::string::npos); -} +struct test_permission_last_used_action { + account_name account; + permission_name permission; + fc::time_point last_used_time; +}; + +FC_REFLECT( test_permission_last_used_action, (account)(permission)(last_used_time) ) constexpr uint64_t TEST_METHOD(const char* CLASS, const char *METHOD) { return ( (uint64_t(DJBH(CLASS))<<32) | uint32_t(DJBH(METHOD)) ); @@ -128,7 +132,7 @@ string U128Str(unsigned __int128 i) } template -transaction_trace CallAction(TESTER& test, T ac, const vector& scope = {N(testapi)}) { +transaction_trace_ptr CallAction(TESTER& test, T ac, const vector& scope = {N(testapi)}) { signed_transaction trx; @@ -144,13 +148,13 @@ transaction_trace CallAction(TESTER& test, T ac, const vector& sco auto sigs = trx.sign(test.get_private_key(scope[0], "active"), chain_id_type()); trx.get_signature_keys(chain_id_type()); auto res = test.push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); test.produce_block(); return res; } template -transaction_trace CallFunction(TESTER& test, T ac, const vector& data, const vector& scope = {N(testapi)}) { +transaction_trace_ptr CallFunction(TESTER& test, T ac, const vector& data, const vector& scope = {N(testapi)}) { { signed_transaction trx; @@ -168,7 +172,7 @@ transaction_trace CallFunction(TESTER& test, T ac, const vector& data, con auto sigs = trx.sign(test.get_private_key(scope[0], "active"), chain_id_type()); trx.get_signature_keys(chain_id_type() ); auto res = test.push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); test.produce_block(); return res; } @@ -197,18 +201,17 @@ bool is_access_violation(fc::unhandled_exception const & e) { } return false; } -bool is_access_violation(const Runtime::Exception& e) { - return true; -} +bool is_access_violation(const Runtime::Exception& e) { return true; } + bool is_assert_exception(fc::assert_exception const & e) { return true; } bool is_page_memory_error(page_memory_error const &e) { return true; } -bool is_tx_missing_auth(tx_missing_auth const & e) { return true; } -bool is_tx_missing_recipient(tx_missing_recipient const & e) { return true;} -bool is_tx_missing_sigs(tx_missing_sigs const & e) { return true;} +bool is_unsatisfied_authorization(unsatisfied_authorization const & e) { return true;} bool is_wasm_execution_error(eosio::chain::wasm_execution_error const& e) {return true;} -bool is_tx_resource_exhausted(const tx_resource_exhausted& e) { return true; } -bool is_checktime_exceeded(const checktime_exceeded& e) { return true; } - +bool is_tx_net_usage_exceeded(const tx_net_usage_exceeded& e) { return true; } +bool is_block_net_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; } +bool is_tx_cpu_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; } +bool is_block_cpu_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; } +bool is_deadline_exception(const deadline_exception& e) { return true; } /* * register test suite `api_tests` @@ -244,7 +247,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { create_account( N(acc2) ); create_account( N(acc3) ); create_account( N(acc4) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_wast ); produce_blocks(1); @@ -252,7 +255,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_action", "assert_true", {}); //test assert_false - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "assert_false", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "assert_false", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "test_action::assert_false"); } @@ -297,36 +300,36 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { std::copy(data.begin(), data.end(), std::back_inserter(dest)); trx.actions.push_back(act); - test.set_transaction_headers(trx); - trx.sign(test.get_private_key(N(inita), "active"), chain_id_type()); - auto res = test.push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); + test.set_transaction_headers(trx); + trx.sign(test.get_private_key(N(inita), "active"), chain_id_type()); + auto res = test.push_transaction(trx); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); }; - BOOST_CHECK_EXCEPTION(test_require_notice(*this, raw_bytes, scope), tx_missing_sigs, - [](const tx_missing_sigs& e) { + BOOST_CHECK_EXCEPTION(test_require_notice(*this, raw_bytes, scope), unsatisfied_authorization, + [](const unsatisfied_authorization& e) { return expect_assert_message(e, "transaction declares authority"); } ); // test require_auth - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", {}), tx_missing_auth, - [](const tx_missing_auth& e) { + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", {}), missing_auth_exception, + [](const missing_auth_exception& e) { return expect_assert_message(e, "missing authority of"); } ); // test require_auth auto a3only = std::vector{{N(acc3), config::active_name}}; - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", fc::raw::pack(a3only)), tx_missing_auth, - [](const tx_missing_auth& e) { + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", fc::raw::pack(a3only)), missing_auth_exception, + [](const missing_auth_exception& e) { return expect_assert_message(e, "missing authority of"); } ); // test require_auth auto a4only = std::vector{{N(acc4), config::active_name}}; - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", fc::raw::pack(a4only)), tx_missing_auth, - [](const tx_missing_auth& e) { + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "require_auth", fc::raw::pack(a4only)), missing_auth_exception, + [](const missing_auth_exception& e) { return expect_assert_message(e, "missing authority of"); } ); @@ -350,23 +353,24 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { trx.actions.push_back(act); set_transaction_headers(trx); - trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); - trx.sign(get_private_key(N(acc3), "active"), chain_id_type()); - trx.sign(get_private_key(N(acc4), "active"), chain_id_type()); - auto res = push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); + trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); + trx.sign(get_private_key(N(acc3), "active"), chain_id_type()); + trx.sign(get_private_key(N(acc4), "active"), chain_id_type()); + auto res = push_transaction(trx); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); } - uint32_t now = control->head_block_time().sec_since_epoch(); - CALL_TEST_FUNCTION( *this, "test_action", "now", fc::raw::pack(now)); + uint64_t now = static_cast( control->head_block_time().time_since_epoch().count() ); + now += config::block_interval_us; + CALL_TEST_FUNCTION( *this, "test_action", "test_current_time", fc::raw::pack(now)); - // test now + // test current_time produce_block(); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "now", fc::raw::pack(now)), transaction_exception, - [](const fc::exception& e) { - return expect_assert_message(e, "assertion failed: tmp == now"); - } - ); + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "test_current_time", fc::raw::pack(now)), assert_exception, + [](const fc::exception& e) { + return expect_assert_message(e, "assertion failed: tmp == current_time()"); + } + ); // test test_current_receiver CALL_TEST_FUNCTION( *this, "test_action", "test_current_receiver", fc::raw::pack(N(testapi))); @@ -376,11 +380,12 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { produce_block(); // test_publication_time - uint32_t pub_time = control->head_block_time().sec_since_epoch(); + uint64_t pub_time = static_cast( control->head_block_time().time_since_epoch().count() ); + pub_time += config::block_interval_us; CALL_TEST_FUNCTION( *this, "test_action", "test_publication_time", fc::raw::pack(pub_time) ); // test test_abort - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "test_abort", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "test_abort", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "abort() called"); } @@ -398,16 +403,16 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { produce_blocks(2); create_account( N(testapi) ); create_account( N(dummy) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_wast ); produce_blocks(1); cf_action cfa; signed_transaction trx; set_transaction_headers(trx); // need at least one normal action - BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_action, + BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_auths, [](const fc::assert_exception& e) { - return expect_assert_message(e, "transaction must have at least one action"); + return expect_assert_message(e, "transaction must have at least one authorization"); } ); @@ -418,11 +423,11 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { set_transaction_headers(trx); // signing a transaction with only context_free_actions should not be allowed - auto sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); + // auto sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); - BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_action, + BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_auths, [](const fc::exception& e) { - return expect_assert_message(e, "transaction must have at least one action"); + return expect_assert_message(e, "transaction must have at least one authorization"); } ); @@ -435,10 +440,10 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { trx.actions.push_back(act1); set_transaction_headers(trx); // run normal passing case - sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); + auto sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); auto res = push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); // attempt to access context free api in non context free action @@ -448,11 +453,11 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { trx.actions.clear(); trx.actions.push_back(act2); set_transaction_headers(trx); - // run normal passing case + // run (dummy_action.b = 200) case looking for invalid use of context_free api sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); - BOOST_CHECK_EXCEPTION(push_transaction(trx), transaction_exception, + BOOST_CHECK_EXCEPTION(push_transaction(trx), assert_exception, [](const fc::exception& e) { - return expect_assert_message(e, "may only be called from context_free"); + return expect_assert_message(e, "this API may only be called from context_free apply"); } ); { @@ -465,7 +470,7 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { trx.actions.push_back(act1); // attempt to access non context free api - for (uint32_t i = 200; i <= 204; ++i) { + for (uint32_t i = 200; i <= 211; ++i) { trx.context_free_actions.clear(); trx.context_free_data.clear(); cfa.payload = i; @@ -475,9 +480,9 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { trx.signatures.clear(); set_transaction_headers(trx); sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type()); - BOOST_CHECK_EXCEPTION(push_transaction(trx), transaction_exception, + BOOST_CHECK_EXCEPTION(push_transaction(trx), assert_exception, [](const fc::exception& e) { - return expect_assert_message(e, "context_free: only context free api's can be used in this context"); + return expect_assert_message(e, "only context free api's can be used in this context" ); } ); } @@ -487,22 +492,20 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try { // test send context free action auto ttrace = CALL_TEST_FUNCTION( *this, "test_transaction", "send_cf_action", {} ); - BOOST_CHECK_EQUAL(ttrace.action_traces.size(), 2); - BOOST_CHECK_EQUAL(ttrace.action_traces[1].receiver == account_name("dummy"), true); - BOOST_CHECK_EQUAL(ttrace.action_traces[1].act.account == account_name("dummy"), true); - BOOST_CHECK_EQUAL(ttrace.action_traces[1].act.name == account_name("event1"), true); - BOOST_CHECK_EQUAL(ttrace.action_traces[1].act.authorization.size(), 0); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_transaction", "send_cf_action_fail", {} ), transaction_exception, + BOOST_CHECK_EQUAL(ttrace->action_traces.size(), 1); + BOOST_CHECK_EQUAL(ttrace->action_traces[0].inline_traces.size(), 1); + BOOST_CHECK_EQUAL(ttrace->action_traces[0].inline_traces[0].receipt.receiver, account_name("dummy")); + BOOST_CHECK_EQUAL(ttrace->action_traces[0].inline_traces[0].act.account, account_name("dummy")); + BOOST_CHECK_EQUAL(ttrace->action_traces[0].inline_traces[0].act.name, account_name("event1")); + BOOST_CHECK_EQUAL(ttrace->action_traces[0].inline_traces[0].act.authorization.size(), 0); + + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_transaction", "send_cf_action_fail", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "context free actions cannot have authorizations"); } ); - CALL_TEST_FUNCTION( *this, "test_transaction", "read_inline_action", {} ); - - CALL_TEST_FUNCTION( *this, "test_transaction", "read_inline_cf_action", {} ); - BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } @@ -527,13 +530,117 @@ BOOST_FIXTURE_TEST_CASE(cfa_tx_signature, TESTER) try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE(cfa_stateful_api, TESTER) try { + + create_account( N(testapi) ); + produce_blocks(1); + set_code( N(testapi), test_api_wast ); + + account_name a = N(testapi2); + account_name creator = N(eosio); + + signed_transaction trx; + + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = authority( get_public_key( a, "owner" ) ), + .active = authority( get_public_key( a, "active" ) ) + }); + action act({}, test_api_action{}); + trx.context_free_actions.push_back(act); + set_transaction_headers(trx); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + BOOST_CHECK_EXCEPTION(push_transaction( trx ), fc::exception, + [&](const fc::exception &e) { + return expect_assert_message(e, "only context free api's can be used in this context"); + }); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(deferred_cfa_failed, TESTER) try { + + create_account( N(testapi) ); + produce_blocks(1); + set_code( N(testapi), test_api_wast ); + + account_name a = N(testapi2); + account_name creator = N(eosio); + + signed_transaction trx; + + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = authority( get_public_key( a, "owner" ) ), + .active = authority( get_public_key( a, "active" ) ) + }); + action act({}, test_api_action{}); + trx.context_free_actions.push_back(act); + set_transaction_headers(trx, 10, 2); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + + BOOST_CHECK_EXCEPTION(push_transaction( trx ), fc::exception, + [&](const fc::exception &e) { + return expect_assert_message(e, "only context free api's can be used in this context"); + }); + + produce_blocks(10); + + // CFA failed, testapi2 not created + create_account( N(testapi2) ); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(deferred_cfa_success, TESTER) try { + + create_account( N(testapi) ); + produce_blocks(1); + set_code( N(testapi), test_api_wast ); + + account_name a = N(testapi2); + account_name creator = N(eosio); + + signed_transaction trx; + + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = authority( get_public_key( a, "owner" ) ), + .active = authority( get_public_key( a, "active" ) ) + }); + action act({}, test_api_action{}); + trx.context_free_actions.push_back(act); + set_transaction_headers(trx, 10, 2); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + auto trace = push_transaction( trx ); + BOOST_REQUIRE(trace != nullptr); + if (trace) { + BOOST_REQUIRE_EQUAL(transaction_receipt_header::status_enum::delayed, trace->receipt->status); + BOOST_REQUIRE_EQUAL(1, trace->action_traces.size()); + } + produce_blocks(10); + + // CFA success, testapi2 created + BOOST_CHECK_EXCEPTION(create_account( N(testapi2) ), fc::exception, + [&](const fc::exception &e) { + return expect_assert_message(e, "Cannot create account named testapi2, as that name is already taken"); + }); + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + /************************************************************************************* * checktime_tests test case *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(checktime_pass_tests, TESTER) { try { produce_blocks(2); create_account( N(testapi) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_wast ); produce_blocks(1); @@ -548,29 +655,50 @@ BOOST_AUTO_TEST_CASE(checktime_fail_tests) { try { // 1) compilation of the smart contract should probably not count towards the CPU time of a transaction that first uses it; // 2) checktime should eventually switch to a deterministic metric which should hopefully fix the inconsistencies // of this test succeeding/failing on different machines (for example, succeeding on our local dev machines but failing on Jenkins). - TESTER t( {fc::milliseconds(5000), fc::milliseconds(5000), fc::milliseconds(-1)} ); + TESTER t; t.produce_blocks(2); + ilog( "create account" ); t.create_account( N(testapi) ); + ilog( "set code" ); t.set_code( N(testapi), test_api_wast ); + ilog( "produce block" ); t.produce_blocks(1); - auto call_test = [](TESTER& test, auto ac) { - signed_transaction trx; + int64_t x; int64_t net; int64_t cpu; + t.control->get_resource_limits_manager().get_account_limits( N(testapi), x, net, cpu ); + wdump((net)(cpu)); + + auto call_test = [](TESTER& test, auto ac, uint32_t billed_cpu_time_us, uint8_t max_cpu_usage_ms ) { + signed_transaction trx; auto pl = vector{{N(testapi), config::active_name}}; action act(pl, ac); + ilog( "call test" ); + trx.actions.push_back(act); test.set_transaction_headers(trx); - auto sigs = trx.sign(test.get_private_key(N(testapi), "active"), chain_id_type()); + trx.max_cpu_usage_ms = max_cpu_usage_ms; + auto sigs = trx.sign(test.get_private_key(N(testapi), "active"), chain_id_type()); trx.get_signature_keys(chain_id_type() ); - auto res = test.push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); - test.produce_block(); - }; + auto res = test.push_transaction( trx, fc::time_point::now() + fc::milliseconds(200), billed_cpu_time_us ); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + test.produce_block(); + }; + - BOOST_CHECK_EXCEPTION(call_test( t, test_api_action{}), checktime_exceeded, is_checktime_exceeded); + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 5000, 0 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 0, 50 ), + tx_cpu_usage_exceeded, is_tx_cpu_usage_exceeded ); + + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 0, 0 ), + block_cpu_usage_exceeded, is_block_cpu_usage_exceeded ); // Because the onblock uses up some of the CPU BOOST_REQUIRE_EQUAL( t.validate(), true ); } FC_LOG_AND_RETHROW() } @@ -581,7 +709,7 @@ BOOST_AUTO_TEST_CASE(checktime_fail_tests) { try { BOOST_FIXTURE_TEST_CASE(compiler_builtins_tests, TESTER) { try { produce_blocks(2); create_account( N(testapi) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_wast ); produce_blocks(1); @@ -592,7 +720,7 @@ BOOST_FIXTURE_TEST_CASE(compiler_builtins_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_divti3", {}); // test test_divti3_by_0 - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_divti3_by_0", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_divti3_by_0", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "divide by zero"); } @@ -602,7 +730,7 @@ BOOST_FIXTURE_TEST_CASE(compiler_builtins_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_udivti3", {}); // test test_udivti3_by_0 - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_udivti3_by_0", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_udivti3_by_0", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "divide by zero"); } @@ -612,7 +740,7 @@ BOOST_FIXTURE_TEST_CASE(compiler_builtins_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_modti3", {}); // test test_modti3_by_0 - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_modti3_by_0", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_compiler_builtins", "test_modti3_by_0", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "divide by zero"); } @@ -643,6 +771,7 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try { produce_blocks(100); set_code( N(testapi), test_api_wast ); produce_blocks(1); + // test for zero auth { signed_transaction trx; @@ -665,47 +794,49 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try { CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_empty", {}); // test send_action_large - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_large", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_large", {}), assert_exception, [](const fc::exception& e) { - return expect_assert_message(e, "data_len < config::default_max_inline_action_size"); + return expect_assert_message(e, "data_len < context.control.get_global_properties().configuration.max_inline_action_size: inline action too big"); } ); + // test send_action_inline_fail - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_inline_fail", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_inline_fail", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "test_action::assert_false"); } ); - control->push_deferred_transactions( true ); - // test send_transaction - CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction", {}); - control->push_deferred_transactions( true ); + // test send_transaction + CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction", {}); // test send_transaction_empty - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_empty", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_empty", {}), tx_no_auths, [](const fc::exception& e) { - return expect_assert_message(e, "transaction must have at least one action"); + return expect_assert_message(e, "transaction must have at least one authorization"); } ); - control->push_deferred_transactions( true ); + + { + produce_blocks(10); + transaction_trace_ptr trace; + auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->receipt->status != transaction_receipt::executed) { trace = t; } } ); // test error handling on deferred transaction failure CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_trigger_error_handler", {}); - auto tx_traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL(tx_traces.size(), 1); - BOOST_CHECK_EQUAL(tx_traces.at(0).action_traces.size(), 2); - BOOST_CHECK_EQUAL(tx_traces.at(0).status, transaction_receipt::soft_fail); + + BOOST_CHECK(trace); + BOOST_CHECK_EQUAL(trace->receipt->status, transaction_receipt::soft_fail); + } // test test_transaction_size - CALL_TEST_FUNCTION(*this, "test_transaction", "test_transaction_size", fc::raw::pack(55) ); // TODO: Need a better way to test this. - control->push_deferred_transactions( true ); + CALL_TEST_FUNCTION(*this, "test_transaction", "test_transaction_size", fc::raw::pack(54) ); // TODO: Need a better way to test this. // test test_read_transaction // this is a bit rough, but I couldn't figure out a better way to compare the hashes auto tx_trace = CALL_TEST_FUNCTION( *this, "test_transaction", "test_read_transaction", {} ); - string sha_expect = tx_trace.id; - BOOST_CHECK_EQUAL(tx_trace.action_traces.front().console == sha_expect, true); + string sha_expect = tx_trace->id; + BOOST_CHECK_EQUAL(tx_trace->action_traces.front().console == sha_expect, true); // test test_tapos_block_num CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_num", fc::raw::pack(control->head_block_num()) ); @@ -719,63 +850,133 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try { } ); - // test send_transaction_expiring_late - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_transaction", "send_transaction_expiring_late", fc::raw::pack(N(testapi))), - eosio::chain::transaction_exception, [](const eosio::chain::transaction_exception& e) { - return expect_assert_message(e, "Transaction expiration is too far"); - } - ); - BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { produce_blocks(2); - create_account( N(testapi) ); - produce_blocks(100); + create_accounts( {N(testapi), N(testapi2), N(alice)} ); set_code( N(testapi), test_api_wast ); + set_code( N(testapi2), test_api_wast ); produce_blocks(1); //schedule - CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {} ); - //check that it doesn't get executed immediately - auto traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 0, traces.size() ); - produce_block( fc::seconds(2) ); - //check that it gets executed afterwards - traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); - //confirm printed message - BOOST_TEST(traces.back().action_traces.back().console == "deferred executed\n"); + { + transaction_trace_ptr trace; + auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {} ); + BOOST_CHECK(!trace); + produce_block( fc::seconds(2) ); + + //check that it gets executed afterwards + BOOST_CHECK(trace); + + //confirm printed message + BOOST_TEST(!trace->action_traces.empty()); + BOOST_TEST(trace->action_traces.back().console == "deferred executed\n"); + c.disconnect(); + } + + produce_blocks(10); //schedule twice (second deferred transaction should replace first one) - CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); - CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); - produce_block( fc::seconds(2) ); - //check that only one deferred transaction executed - traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); + { + transaction_trace_ptr trace; + uint32_t count = 0; + auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; ++count; } } ); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); + produce_blocks( 3 ); + + //check that only one deferred transaction executed + auto dtrxs = control->get_scheduled_transactions(); + BOOST_CHECK_EQUAL(dtrxs.size(), 1); + for (const auto& trx: dtrxs) { + control->push_scheduled_transaction(trx, fc::time_point::maximum()); + } + BOOST_CHECK_EQUAL(1, count); + BOOST_CHECK(trace); + BOOST_CHECK_EQUAL( 1, trace->action_traces.size() ); + c.disconnect(); + } + + produce_blocks(10); //schedule and cancel - CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); - CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction", {}); - produce_block( fc::seconds(2) ); - traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 0, traces.size() ); - - //cancel_deferred() before scheduling transaction should not prevent the transaction from being scheduled (check that previous bug is fixed) - CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction", {}); - CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); - produce_block( fc::seconds(2) ); - traces = control->push_deferred_transactions( true ); - BOOST_CHECK_EQUAL( 1, traces.size() ); - - //verify that deferred transaction is dependent on max_generated_transaction_count configuration property - const auto& gpo = control->get_global_properties(); - control->get_mutable_database().modify(gpo, [&]( auto& props ) { - props.configuration.max_generated_transaction_count = 0; - }); - BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}), transaction_exception); + { + transaction_trace_ptr trace; + auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; } } ); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); + CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction", {}); + produce_block( fc::seconds(2) ); + BOOST_CHECK(!trace); + c.disconnect(); + } + + produce_blocks(10); + + //cancel_deferred() fails if no transaction is scheduled + { + BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction", {}), transaction_exception); + } + + produce_blocks(10); + +{ + // Trigger a tx which in turn sends a deferred tx with payer != receiver + // Payer is alice in this case, this tx should fail since we don't have the authorization of alice + dtt_action dtt_act1; + dtt_act1.payer = N(alice); + BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act1)), missing_auth_exception); + + // Send a tx which in turn sends a deferred tx with the deferred tx's receiver != this tx receiver + // This will include the authorization of the receiver, and impose any related delay associated with the authority + // We set the authorization delay to be 10 sec here, and since the deferred tx delay is set to be 5 sec, so this tx should fail + dtt_action dtt_act2; + dtt_act2.deferred_account = N(testapi2); + dtt_act2.permission_name = N(additional); + dtt_act2.delay_sec = 5; + + auto auth = authority(get_public_key("testapi", name(dtt_act2.permission_name).to_string()), 10); + auth.accounts.push_back( permission_level_weight{{N(testapi), config::eosio_code_name}, 1} ); + + push_action(config::system_account_name, updateauth::get_name(), "testapi", fc::mutable_variant_object() + ("account", "testapi") + ("permission", name(dtt_act2.permission_name)) + ("parent", "active") + ("auth", auth) + ); + push_action(config::system_account_name, linkauth::get_name(), "testapi", fc::mutable_variant_object() + ("account", "testapi") + ("code", name(dtt_act2.deferred_account)) + ("type", name(dtt_act2.deferred_action)) + ("requirement", name(dtt_act2.permission_name))); + BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act2)), unsatisfied_authorization); + + // But if the deferred transaction has a sufficient delay, then it should work. + dtt_act2.delay_sec = 10; + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act2)); + + // Meanwhile, if the deferred tx receiver == this tx receiver, the delay will be ignored, this tx should succeed + dtt_action dtt_act3; + dtt_act3.deferred_account = N(testapi); + dtt_act3.permission_name = N(additional); + push_action(config::system_account_name, linkauth::get_name(), "testapi", fc::mutable_variant_object() + ("account", "testapi") + ("code", name(dtt_act3.deferred_account)) + ("type", name(dtt_act3.deferred_action)) + ("requirement", name(dtt_act3.permission_name))); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act3)); + + // If we make testapi account to be priviledged account: + // - the deferred transaction will work no matter who is the payer + // - the deferred transaction will not care about the delay of the authorization + push_action(config::system_account_name, N(setpriv), config::system_account_name, mutable_variant_object() + ("account", "testapi") + ("is_priv", 1)); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act1)); + CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act2)); +} BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } @@ -795,84 +996,45 @@ struct setprod_act { * chain_tests test case *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(chain_tests, TESTER) { try { - produce_blocks(2); - create_account( N(inita) ); - create_account( N(initb) ); - create_account( N(initc) ); - create_account( N(initd) ); - create_account( N(inite) ); - create_account( N(initf) ); - create_account( N(initg) ); - create_account( N(inith) ); - create_account( N(initi) ); - create_account( N(initj) ); - create_account( N(initk) ); - create_account( N(initl) ); - create_account( N(initm) ); - create_account( N(initn) ); - create_account( N(inito) ); - create_account( N(initp) ); - create_account( N(initq) ); - create_account( N(initr) ); - create_account( N(inits) ); - create_account( N(initt) ); - create_account( N(initu) ); - create_account( N(initv) ); + produce_blocks(2); - create_account( N(testapi) ); + create_account( N(testapi) ); - // set active producers - { - signed_transaction trx; + vector producers = { N(inita), + N(initb), + N(initc), + N(initd), + N(inite), + N(initf), + N(initg), + N(inith), + N(initi), + N(initj), + N(initk), + N(initl), + N(initm), + N(initn), + N(inito), + N(initp), + N(initq), + N(initr), + N(inits), + N(initt), + N(initu) + }; - auto pl = vector{{config::system_account_name, config::active_name}}; - action act(pl, test_chain_action()); - vector prod_keys = { - { N(inita), get_public_key( N(inita), "active" ) }, - { N(initb), get_public_key( N(initb), "active" ) }, - { N(initc), get_public_key( N(initc), "active" ) }, - { N(initd), get_public_key( N(initd), "active" ) }, - { N(inite), get_public_key( N(inite), "active" ) }, - { N(initf), get_public_key( N(initf), "active" ) }, - { N(initg), get_public_key( N(initg), "active" ) }, - { N(inith), get_public_key( N(inith), "active" ) }, - { N(initi), get_public_key( N(initi), "active" ) }, - { N(initj), get_public_key( N(initj), "active" ) }, - { N(initk), get_public_key( N(initk), "active" ) }, - { N(initl), get_public_key( N(initl), "active" ) }, - { N(initm), get_public_key( N(initm), "active" ) }, - { N(initn), get_public_key( N(initn), "active" ) }, - { N(inito), get_public_key( N(inito), "active" ) }, - { N(initp), get_public_key( N(initp), "active" ) }, - { N(initq), get_public_key( N(initq), "active" ) }, - { N(initr), get_public_key( N(initr), "active" ) }, - { N(inits), get_public_key( N(inits), "active" ) }, - { N(initt), get_public_key( N(initt), "active" ) }, - { N(initu), get_public_key( N(initu), "active" ) } - }; - vector data = fc::raw::pack(uint32_t(0)); - vector keys = fc::raw::pack(prod_keys); - data.insert( data.end(), keys.begin(), keys.end() ); - act.data = data; - trx.actions.push_back(act); + create_accounts( producers ); + set_producers (producers ); - set_transaction_headers(trx); - - auto sigs = trx.sign(get_private_key(config::system_account_name, "active"), chain_id_type()); - trx.get_signature_keys(chain_id_type() ); - auto res = push_transaction(trx); - BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); - } + set_code( N(testapi), test_api_wast ); + produce_blocks(100); - set_code( N(testapi), test_api_wast ); - produce_blocks(100); - auto& gpo = control->get_global_properties(); - std::vector prods(gpo.active_producers.producers.size()); - for ( unsigned int i=0; i < gpo.active_producers.producers.size(); i++ ) { - prods[i] = gpo.active_producers.producers[i].producer_name; + vector prods( control->active_producers().producers.size() ); + for ( uint32_t i = 0; i < prods.size(); i++ ) { + prods[i] = control->active_producers().producers[i].producer_name; } - CALL_TEST_FUNCTION( *this, "test_chain", "test_activeprods", fc::raw::pack(prods)); + CALL_TEST_FUNCTION( *this, "test_chain", "test_activeprods", fc::raw::pack(prods) ); BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } @@ -884,7 +1046,7 @@ BOOST_FIXTURE_TEST_CASE(db_tests, TESTER) { try { produce_blocks(2); create_account( N(testapi) ); create_account( N(testapi2) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_db_wast ); set_code( N(testapi2), test_api_db_wast ); produce_blocks(1); @@ -910,6 +1072,7 @@ BOOST_FIXTURE_TEST_CASE(db_tests, TESTER) { try { N(testapi2), WASM_TEST_ACTION("test_db", "test_invalid_access"), fc::raw::pack(ia2)), N(testapi2) ); + wdump((res)); BOOST_CHECK_EQUAL( boost::algorithm::ends_with(res, "db access violation"), true ); @@ -960,6 +1123,7 @@ BOOST_FIXTURE_TEST_CASE(db_tests, TESTER) { try { CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_db", "idx_double_nan_lookup_fail", fc::raw::pack(lookup_type), transaction_exception, "NaN is not an allowed value for a secondary key"); + CALL_TEST_FUNCTION( *this, "test_db", "misaligned_secondary_key256_tests", {}); BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } @@ -972,6 +1136,7 @@ BOOST_FIXTURE_TEST_CASE(multi_index_tests, TESTER) { try { produce_blocks(1); set_code( N(testapi), test_api_multi_index_wast ); produce_blocks(1); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_general", {}); CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_store_only", {}); CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_check_without_storing", {}); @@ -985,33 +1150,33 @@ BOOST_FIXTURE_TEST_CASE(multi_index_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_multi_index", "idx_double_general", {}); CALL_TEST_FUNCTION( *this, "test_multi_index", "idx_long_double_general", {}); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pk_iterator_exceed_end", {}, - transaction_exception, "cannot increment end iterator"); + assert_exception, "cannot increment end iterator"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_sk_iterator_exceed_end", {}, - transaction_exception, "cannot increment end iterator"); + assert_exception, "cannot increment end iterator"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pk_iterator_exceed_begin", {}, - transaction_exception, "cannot decrement iterator at beginning of table"); + assert_exception, "cannot decrement iterator at beginning of table"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_sk_iterator_exceed_begin", {}, - transaction_exception, "cannot decrement iterator at beginning of index"); + assert_exception, "cannot decrement iterator at beginning of index"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_pk_ref_to_other_table", {}, - transaction_exception, "object passed to iterator_to is not in multi_index"); + assert_exception, "object passed to iterator_to is not in multi_index"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_sk_ref_to_other_table", {}, - transaction_exception, "object passed to iterator_to is not in multi_index"); + assert_exception, "object passed to iterator_to is not in multi_index"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_pk_end_itr_to_iterator_to", {}, - transaction_exception, "object passed to iterator_to is not in multi_index"); + assert_exception, "object passed to iterator_to is not in multi_index"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_pk_end_itr_to_modify", {}, - transaction_exception, "cannot pass end iterator to modify"); + assert_exception, "cannot pass end iterator to modify"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_pk_end_itr_to_erase", {}, - transaction_exception, "cannot pass end iterator to erase"); + assert_exception, "cannot pass end iterator to erase"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_sk_end_itr_to_iterator_to", {}, - transaction_exception, "object passed to iterator_to is not in multi_index"); + assert_exception, "object passed to iterator_to is not in multi_index"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_sk_end_itr_to_modify", {}, - transaction_exception, "cannot pass end iterator to modify"); + assert_exception, "cannot pass end iterator to modify"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_pass_sk_end_itr_to_erase", {}, - transaction_exception, "cannot pass end iterator to erase"); + assert_exception, "cannot pass end iterator to erase"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_modify_primary_key", {}, - transaction_exception, "updater cannot change primary key when modifying an object"); + assert_exception, "updater cannot change primary key when modifying an object"); CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION( *this, "test_multi_index", "idx64_run_out_of_avl_pk", {}, - transaction_exception, "next primary key in table is at autoincrement limit"); + assert_exception, "next primary key in table is at autoincrement limit"); CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_sk_cache_pk_lookup", {}); CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_pk_cache_sk_lookup", {}); @@ -1024,16 +1189,16 @@ BOOST_FIXTURE_TEST_CASE(multi_index_tests, TESTER) { try { BOOST_FIXTURE_TEST_CASE(fixedpoint_tests, TESTER) { try { produce_blocks(2); create_account( N(testapi) ); - produce_blocks(1000); + produce_blocks(10); set_code( N(testapi), test_api_wast ); - produce_blocks(1000); + produce_blocks(10); CALL_TEST_FUNCTION( *this, "test_fixedpoint", "create_instances", {}); CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_addition", {}); CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_subtraction", {}); CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_multiplication", {}); CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_division", {}); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_division_by_0", {}), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_fixedpoint", "test_division_by_0", {}), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "divide by zero"); } @@ -1042,33 +1207,6 @@ BOOST_FIXTURE_TEST_CASE(fixedpoint_tests, TESTER) { try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } - -/************************************************************************************* - * real_tests test cases - *************************************************************************************/ -BOOST_FIXTURE_TEST_CASE(real_tests, TESTER) { try { - produce_blocks(1000); - create_account(N(testapi) ); - produce_blocks(1000); - set_code(N(testapi), test_api_wast); - produce_blocks(1000); - - CALL_TEST_FUNCTION( *this, "test_real", "create_instances", {} ); - CALL_TEST_FUNCTION( *this, "test_real", "test_addition", {} ); - CALL_TEST_FUNCTION( *this, "test_real", "test_multiplication", {} ); - CALL_TEST_FUNCTION( *this, "test_real", "test_division", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_real", "test_division_by_0", {}), transaction_exception, - [](const fc::exception& e) { - return expect_assert_message(e, "divide by zero"); - } - ); - - - BOOST_REQUIRE_EQUAL( validate(), true ); -} FC_LOG_AND_RETHROW() } - - - /************************************************************************************* * crypto_tests test cases *************************************************************************************/ @@ -1097,7 +1235,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "test_recover_key", payload ); CALL_TEST_FUNCTION( *this, "test_crypto", "test_recover_key_assert_true", payload ); payload[payload.size()-1] = 0; - BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_crypto", "test_recover_key_assert_false", payload ), transaction_exception, + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_crypto", "test_recover_key_assert_false", payload ), assert_exception, [](const fc::exception& e) { return expect_assert_message( e, "check == p: Error expected key different than recovered key" ); } @@ -1113,7 +1251,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "sha512_no_data", {} ); CALL_TEST_FUNCTION( *this, "test_crypto", "ripemd160_no_data", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha256_false", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha256_false", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "hash miss match"); } @@ -1121,7 +1259,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha256_true", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_false", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_false", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "hash miss match"); } @@ -1129,7 +1267,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_true", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_false", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_false", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "hash miss match"); } @@ -1137,7 +1275,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha1_true", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha512_false", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha512_false", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "hash miss match"); } @@ -1145,7 +1283,7 @@ BOOST_FIXTURE_TEST_CASE(crypto_tests, TESTER) { try { CALL_TEST_FUNCTION( *this, "test_crypto", "assert_sha512_true", {} ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_ripemd160_false", {} ), transaction_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_crypto", "assert_ripemd160_false", {} ), assert_exception, [](const fc::exception& e) { return expect_assert_message(e, "hash miss match"); } @@ -1180,9 +1318,9 @@ BOOST_FIXTURE_TEST_CASE(memory_tests, TESTER) { try { #endif CALL_TEST_FUNCTION( *this, "test_memory", "test_memset_memcpy", {} ); produce_blocks(1000); - CALL_TEST_FUNCTION( *this, "test_memory", "test_memcpy_overlap_start", {} ); + BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_memory", "test_memcpy_overlap_start", {} ), overlapping_memory_error ); produce_blocks(1000); - CALL_TEST_FUNCTION( *this, "test_memory", "test_memcpy_overlap_end", {} ); + BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_memory", "test_memcpy_overlap_end", {} ), overlapping_memory_error ); produce_blocks(1000); CALL_TEST_FUNCTION( *this, "test_memory", "test_memcmp", {} ); produce_blocks(1000); @@ -1282,32 +1420,32 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test prints auto tx1_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_prints", {} ); - auto tx1_act_cnsl = tx1_trace.action_traces.front().console; + auto tx1_act_cnsl = tx1_trace->action_traces.front().console; BOOST_CHECK_EQUAL(tx1_act_cnsl == "abcefg", true); // test prints_l auto tx2_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_prints_l", {} ); - auto tx2_act_cnsl = tx2_trace.action_traces.front().console; + auto tx2_act_cnsl = tx2_trace->action_traces.front().console; BOOST_CHECK_EQUAL(tx2_act_cnsl == "abatest", true); // test printi auto tx3_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printi", {} ); - auto tx3_act_cnsl = tx3_trace.action_traces.front().console; + auto tx3_act_cnsl = tx3_trace->action_traces.front().console; BOOST_CHECK_EQUAL( tx3_act_cnsl.substr(0,1), I64Str(0) ); BOOST_CHECK_EQUAL( tx3_act_cnsl.substr(1,6), I64Str(556644) ); BOOST_CHECK_EQUAL( tx3_act_cnsl.substr(7, std::string::npos), I64Str(-1) ); // test printui auto tx4_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printui", {} ); - auto tx4_act_cnsl = tx4_trace.action_traces.front().console; + auto tx4_act_cnsl = tx4_trace->action_traces.front().console; BOOST_CHECK_EQUAL( tx4_act_cnsl.substr(0,1), U64Str(0) ); BOOST_CHECK_EQUAL( tx4_act_cnsl.substr(1,6), U64Str(556644) ); BOOST_CHECK_EQUAL( tx4_act_cnsl.substr(7, std::string::npos), U64Str(-1) ); // "18446744073709551615" // test printn auto tx5_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printn", {} ); - auto tx5_act_cnsl = tx5_trace.action_traces.front().console; + auto tx5_act_cnsl = tx5_trace->action_traces.front().console; BOOST_CHECK_EQUAL( tx5_act_cnsl.substr(0,5), "abcde" ); BOOST_CHECK_EQUAL( tx5_act_cnsl.substr(5, 5), "ab.de" ); BOOST_CHECK_EQUAL( tx5_act_cnsl.substr(10, 6), "1q1q1q"); @@ -1319,7 +1457,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test printi128 auto tx6_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printi128", {} ); - auto tx6_act_cnsl = tx6_trace.action_traces.front().console; + auto tx6_act_cnsl = tx6_trace->action_traces.front().console; size_t start = 0; size_t end = tx6_act_cnsl.find('\n', start); BOOST_CHECK_EQUAL( tx6_act_cnsl.substr(start, end-start), U128Str(1) ); @@ -1332,7 +1470,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test printui128 auto tx7_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printui128", {} ); - auto tx7_act_cnsl = tx7_trace.action_traces.front().console; + auto tx7_act_cnsl = tx7_trace->action_traces.front().console; start = 0; end = tx7_act_cnsl.find('\n', start); BOOST_CHECK_EQUAL( tx7_act_cnsl.substr(start, end-start), U128Str(std::numeric_limits::max()) ); start = end + 1; end = tx7_act_cnsl.find('\n', start); @@ -1342,7 +1480,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test printsf auto tx8_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printsf", {} ); - auto tx8_act_cnsl = tx8_trace.action_traces.front().console; + auto tx8_act_cnsl = tx8_trace->action_traces.front().console; start = 0; end = tx8_act_cnsl.find('\n', start); BOOST_CHECK_EQUAL( tx8_act_cnsl.substr(start, end-start), "5.000000e-01" ); start = end + 1; end = tx8_act_cnsl.find('\n', start); @@ -1352,7 +1490,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test printdf auto tx9_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printdf", {} ); - auto tx9_act_cnsl = tx9_trace.action_traces.front().console; + auto tx9_act_cnsl = tx9_trace->action_traces.front().console; start = 0; end = tx9_act_cnsl.find('\n', start); BOOST_CHECK_EQUAL( tx9_act_cnsl.substr(start, end-start), "5.000000000000000e-01" ); start = end + 1; end = tx9_act_cnsl.find('\n', start); @@ -1362,7 +1500,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { // test printqf auto tx10_trace = CALL_TEST_FUNCTION( *this, "test_print", "test_printqf", {} ); - auto tx10_act_cnsl = tx10_trace.action_traces.front().console; + auto tx10_act_cnsl = tx10_trace->action_traces.front().console; start = 0; end = tx10_act_cnsl.find('\n', start); BOOST_CHECK_EQUAL( tx10_act_cnsl.substr(start, end-start), "5.000000000000000000e-01" ); start = end + 1; end = tx10_act_cnsl.find('\n', start); @@ -1373,70 +1511,6 @@ BOOST_FIXTURE_TEST_CASE(print_tests, TESTER) { try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } - -/************************************************************************************* - * math_tests test case - *************************************************************************************/ -BOOST_FIXTURE_TEST_CASE(math_tests, TESTER) { try { - produce_blocks(1000); - create_account( N(testapi) ); - produce_blocks(1000); - - produce_blocks(1000); - set_code( N(testapi), test_api_wast ); - produce_blocks(1000); - - std::random_device rd; - std::mt19937_64 gen(rd()); - std::uniform_int_distribution dis; - - // test mult_eq with 10 random pairs of 128 bit numbers - for (int i=0; i < 10; i++) { - u128_action act; - act.values[0] = dis(gen); act.values[0] <<= 64; act.values[0] |= dis(gen); - act.values[1] = dis(gen); act.values[1] <<= 64; act.values[1] |= dis(gen); - act.values[2] = act.values[0] * act.values[1]; - CALL_TEST_FUNCTION( *this, "test_math", "test_multeq", fc::raw::pack(act)); - } - // test div_eq with 10 random pairs of 128 bit numbers - for (int i=0; i < 10; i++) { - u128_action act; - act.values[0] = dis(gen); act.values[0] <<= 64; act.values[0] |= dis(gen); - act.values[1] = dis(gen); act.values[1] <<= 64; act.values[1] |= dis(gen); - act.values[2] = act.values[0] / act.values[1]; - CALL_TEST_FUNCTION( *this, "test_math", "test_diveq", fc::raw::pack(act)); - } - // test diveq for divide by zero - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_math", "test_diveq_by_0", {}), transaction_exception, - [](const fc::exception& e) { - return expect_assert_message(e, "divide by zero"); - } - ); - - CALL_TEST_FUNCTION( *this, "test_math", "test_double_api", {}); - - union { - char whole[32]; - double _[4] = {2, -2, 100000, -100000}; - } d_vals; - - std::vector ds(sizeof(d_vals)); - std::copy(d_vals.whole, d_vals.whole+sizeof(d_vals), ds.begin()); - CALL_TEST_FUNCTION( *this, "test_math", "test_i64_to_double", ds); - - std::copy(d_vals.whole, d_vals.whole+sizeof(d_vals), ds.begin()); - CALL_TEST_FUNCTION( *this, "test_math", "test_double_to_i64", ds); - - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_math", "test_double_api_div_0", {}), transaction_exception, - [](const fc::exception& e) { - return expect_assert_message(e, "divide by zero"); - } - ); - - BOOST_REQUIRE_EQUAL( validate(), true ); -} FC_LOG_AND_RETHROW() } - - /************************************************************************************* * types_tests test case *************************************************************************************/ @@ -1467,8 +1541,8 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { set_code( N(testapi), test_api_wast ); produce_blocks(1); - auto get_result_uint64 = [&]() -> uint64_t { - const auto& db = control->get_database(); + auto get_result_int64 = [&]() -> int64_t { + const auto& db = control->db(); const auto* t_id = db.find(boost::make_tuple(N(testapi), N(testapi), N(testapi))); FC_ASSERT(t_id != 0, "Table id not found"); @@ -1479,7 +1553,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { FC_ASSERT( itr != idx.end() && itr->t_id == t_id->id, "lower_bound failed"); FC_ASSERT( 0 != itr->value.size(), "unexpected result size"); - return *reinterpret_cast(itr->value.data()); + return *reinterpret_cast(itr->value.data()); }; CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", @@ -1491,7 +1565,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { } }) ); - BOOST_CHECK_EQUAL( uint64_t(1), get_result_uint64() ); + BOOST_CHECK_EQUAL( int64_t(1), get_result_int64() ); CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", fc::raw::pack( check_auth { @@ -1502,9 +1576,9 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { } }) ); - BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + BOOST_CHECK_EQUAL( int64_t(0), get_result_int64() ); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", + CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", fc::raw::pack( check_auth { .account = N(testapi), .permission = N(active), @@ -1512,11 +1586,9 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { get_public_key(N(testapi), "active"), public_key_type(string("EOS7GfRtyDWWgxV88a5TRaYY59XmHptyfjsFmHHfioGNJtPjpSmGX")) } - })), tx_irrelevant_sig, - [](const tx_irrelevant_sig& e) { - return expect_assert_message(e, "irrelevant signatures from these keys: [\"EOS7GfRtyDWWgxV88a5TRaYY59XmHptyfjsFmHHfioGNJtPjpSmGX\"]"); - } + }) ); + BOOST_CHECK_EQUAL( int64_t(0), get_result_int64() ); // Failure due to irrelevant signatures CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", fc::raw::pack( check_auth { @@ -1527,7 +1599,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { } }) ); - BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + BOOST_CHECK_EQUAL( int64_t(0), get_result_int64() ); CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", fc::raw::pack( check_auth { @@ -1536,7 +1608,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { .pubkeys = {} }) ); - BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + BOOST_CHECK_EQUAL( int64_t(0), get_result_int64() ); CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", fc::raw::pack( check_auth { @@ -1547,7 +1619,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { } }) ); - BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() ); + BOOST_CHECK_EQUAL( int64_t(0), get_result_int64() ); /* BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization", @@ -1563,7 +1635,6 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try { } ); */ - } FC_LOG_AND_RETHROW() } #if 0 @@ -1645,4 +1716,165 @@ BOOST_FIXTURE_TEST_CASE(datastream_tests, TESTER) { try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } +/************************************************************************************* + * new api feature test + *************************************************************************************/ +BOOST_FIXTURE_TEST_CASE(new_api_feature_tests, TESTER) { try { + + produce_blocks(1); + create_account(N(testapi) ); + produce_blocks(1); + set_code(N(testapi), test_api_wast); + produce_blocks(1); + + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_transaction", "new_feature", {} ), + assert_exception, + [](const fc::exception& e) { + return expect_assert_message(e, "context.privileged: testapi does not have permission to call this API"); + }); + + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_transaction", "active_new_feature", {} ), + assert_exception, + [](const fc::exception& e) { + return expect_assert_message(e, "context.privileged: testapi does not have permission to call this API"); + }); + + // change privilege + { + chainbase::database &db = control->db(); + const account_object &account = db.get(N(testapi)); + db.modify(account, [&](account_object &v) { + v.privileged = true; + }); + } + +#ifndef NON_VALIDATING_TEST + { + chainbase::database &db = validating_node->db(); + const account_object &account = db.get(N(testapi)); + db.modify(account, [&](account_object &v) { + v.privileged = true; + }); + } +#endif + + CALL_TEST_FUNCTION( *this, "test_transaction", "new_feature", {} ); + + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_transaction", "active_new_feature", {} ), + assert_exception, + [](const fc::exception& e) { + return expect_assert_message(e, "Unsupported Hardfork Detected"); + }); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() } + +/************************************************************************************* + * permission_usage_tests test cases + *************************************************************************************/ +BOOST_FIXTURE_TEST_CASE(permission_usage_tests, TESTER) { try { + produce_block(); + create_accounts( {N(testapi), N(alice), N(bob)} ); + produce_block(); + set_code(N(testapi), test_api_wast); + produce_block(); + + push_reqauth( N(alice), {{N(alice), config::active_name}}, {get_private_key(N(alice), "active")} ); + + CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(alice), config::active_name, + control->pending_block_time() + }) + ); + + // Fails because the last used time is updated after the transaction executes. + BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(testapi), config::active_name, + control->head_block_time() + fc::milliseconds(config::block_interval_ms) + }) + ), fc::assert_exception ); + + produce_blocks(5); + + set_authority( N(bob), N(perm1), authority( get_private_key(N(bob), "perm1").get_public_key() ) ); + + push_action(config::system_account_name, linkauth::get_name(), N(bob), fc::mutable_variant_object() + ("account", "bob") + ("code", "eosio") + ("type", "reqauth") + ("requirement", "perm1") + ); + + auto permission_creation_time = control->pending_block_time(); + + produce_blocks(5); + + CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(bob), N(perm1), + permission_creation_time + }) + ); + + produce_blocks(5); + + push_reqauth( N(bob), {{N(bob), N(perm1)}}, {get_private_key(N(bob), "perm1")} ); + + auto perm1_last_used_time = control->pending_block_time(); + + CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(bob), config::active_name, + permission_creation_time + }) + ); + + BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(bob), N(perm1), + permission_creation_time + }) + ), fc::assert_exception ); + + CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used", + fc::raw::pack(test_permission_last_used_action{ + N(bob), N(perm1), + perm1_last_used_time + }) + ); + + produce_block(); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() } + +/************************************************************************************* + * account_creation_time_tests test cases + *************************************************************************************/ +BOOST_FIXTURE_TEST_CASE(account_creation_time_tests, TESTER) { try { + produce_block(); + create_account( N(testapi) ); + produce_block(); + set_code(N(testapi), test_api_wast); + produce_block(); + + create_account( N(alice) ); + auto alice_creation_time = control->pending_block_time(); + + produce_blocks(10); + + CALL_TEST_FUNCTION( *this, "test_permission", "test_account_creation_time", + fc::raw::pack(test_permission_last_used_action{ + N(alice), config::active_name, + alice_creation_time + }) + ); + + produce_block(); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/chain_tests/auth_tests.cpp b/unittests/auth_tests.cpp similarity index 82% rename from tests/chain_tests/auth_tests.cpp rename to unittests/auth_tests.cpp index e7d6a2d529c..17106603ecf 100644 --- a/tests/chain_tests/auth_tests.cpp +++ b/unittests/auth_tests.cpp @@ -1,14 +1,15 @@ #include #include -#include +#include +#include +#include #include #include #include #include -#include -#include + #ifdef NON_VALIDATING_TEST #define TESTER tester #else @@ -17,7 +18,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; BOOST_AUTO_TEST_SUITE(auth_tests) @@ -26,11 +26,11 @@ BOOST_FIXTURE_TEST_CASE( missing_sigs, TESTER ) { try { create_accounts( {N(alice)} ); produce_block(); - BOOST_REQUIRE_THROW( push_reqauth( N(alice), {permission_level{N(alice), config::active_name}}, {} ), tx_missing_sigs ); + BOOST_REQUIRE_THROW( push_reqauth( N(alice), {permission_level{N(alice), config::active_name}}, {} ), unsatisfied_authorization ); auto trace = push_reqauth(N(alice), "owner"); produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); } FC_LOG_AND_RETHROW() } /// missing_sigs @@ -39,11 +39,11 @@ BOOST_FIXTURE_TEST_CASE( missing_multi_sigs, TESTER ) { try { create_account(N(alice), config::system_account_name, true); produce_block(); - BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner"), tx_missing_sigs); // without multisig + BOOST_REQUIRE_THROW(push_reqauth(N(alice), "owner"), unsatisfied_authorization); // without multisig auto trace = push_reqauth(N(alice), "owner", true); // with multisig produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); } FC_LOG_AND_RETHROW() } /// missing_multi_sigs @@ -52,7 +52,7 @@ BOOST_FIXTURE_TEST_CASE( missing_auths, TESTER ) { try { produce_block(); /// action not provided from authority - BOOST_REQUIRE_THROW( push_reqauth( N(alice), {permission_level{N(bob), config::active_name}}, { get_private_key(N(bob), "active") } ), tx_missing_auth); + BOOST_REQUIRE_THROW( push_reqauth( N(alice), {permission_level{N(bob), config::active_name}}, { get_private_key(N(bob), "active") } ), missing_auth_exception); } FC_LOG_AND_RETHROW() } /// transfer_test @@ -69,15 +69,26 @@ BOOST_FIXTURE_TEST_CASE( delegate_auth, TESTER ) { try { { .permission = {N(bob),config::active_name}, .weight = 1} }); + auto original_auth = static_cast(control->get_authorization_manager().get_permission({N(alice), config::active_name}).auth); + wdump((original_auth)); + set_authority( N(alice), config::active_name, delegated_auth ); - produce_block( fc::hours(2) ); ///< skip 2 hours + auto new_auth = static_cast(control->get_authorization_manager().get_permission({N(alice), config::active_name}).auth); + wdump((new_auth)); + BOOST_CHECK_EQUAL((new_auth == delegated_auth), true); + + produce_block( fc::milliseconds(config::block_interval_ms*2) ); + + auto auth = static_cast(control->get_authorization_manager().get_permission({N(alice), config::active_name}).auth); + wdump((auth)); + BOOST_CHECK_EQUAL((new_auth == auth), true); /// execute nonce from alice signed by bob auto trace = push_reqauth(N(alice), {permission_level{N(alice), config::active_name}}, { get_private_key(N(bob), "active") } ); produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + //todoBOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); } FC_LOG_AND_RETHROW() } @@ -144,7 +155,7 @@ try { // Bob attempts to create new spending auth for Alice BOOST_CHECK_THROW( chain.set_authority( "alice", "spending", authority(spending_pub_key), "active", { permission_level{"bob", "active"} }, { chain.get_private_key("bob", "active") } ), - transaction_exception ); + irrelevant_auth_exception ); // Create new spending auth chain.set_authority("alice", "spending", authority(spending_pub_key), "active", @@ -228,7 +239,7 @@ BOOST_AUTO_TEST_CASE(link_auths) { try { chain.set_authority("alice", "scud", scud_pub_key, "spending"); // Send req auth action with alice's spending key, it should fail - BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }), tx_irrelevant_auth); + BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }), irrelevant_auth_exception); // Link authority for eosio reqauth action with alice's spending key chain.link_authority("alice", "eosio", "spending", "reqauth"); // Now, req auth action with alice's spending key should succeed @@ -242,12 +253,12 @@ BOOST_AUTO_TEST_CASE(link_auths) { try { // Unlink alice with eosio reqauth chain.unlink_authority("alice", "eosio", "reqauth"); // Now, req auth action with alice's spending key should fail - BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }), tx_irrelevant_auth); + BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }), irrelevant_auth_exception); chain.produce_block(); // Send req auth action with scud key, it should fail - BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "scud"} }, { scud_priv_key }), tx_irrelevant_auth); + BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "scud"} }, { scud_priv_key }), irrelevant_auth_exception); // Link authority for any eosio action with alice's scud key chain.link_authority("alice", "eosio", "scud"); // Now, req auth action with alice's scud key should succeed @@ -257,6 +268,32 @@ BOOST_AUTO_TEST_CASE(link_auths) { try { } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(link_then_update_auth) { try { + TESTER chain; + + chain.create_account("alice"); + + const auto first_priv_key = chain.get_private_key("alice", "first"); + const auto first_pub_key = first_priv_key.get_public_key(); + const auto second_priv_key = chain.get_private_key("alice", "second"); + const auto second_pub_key = second_priv_key.get_public_key(); + + chain.set_authority("alice", "first", first_pub_key, "active"); + + chain.link_authority("alice", "eosio", "first", "reqauth"); + chain.push_reqauth("alice", { permission_level{N(alice), "first"} }, { first_priv_key }); + + chain.produce_blocks(13); // Wait at least 6 seconds for first push_reqauth transaction to expire. + + // Update "first" auth public key + chain.set_authority("alice", "first", second_pub_key, "active"); + // Authority updated, using previous "first" auth should fail on linked auth + BOOST_CHECK_THROW(chain.push_reqauth("alice", { permission_level{N(alice), "first"} }, { first_priv_key }), unsatisfied_authorization); + // Using updated authority, should succeed + chain.push_reqauth("alice", { permission_level{N(alice), "first"} }, { second_priv_key }); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(create_account) { try { TESTER chain; @@ -266,32 +303,33 @@ try { // Verify account created properly const auto& joe_owner_authority = chain.get(boost::make_tuple("joe", "owner")); BOOST_TEST(joe_owner_authority.auth.threshold == 1); - BOOST_TEST(joe_owner_authority.auth.accounts.size() == 0); + BOOST_TEST(joe_owner_authority.auth.accounts.size() == 1); BOOST_TEST(joe_owner_authority.auth.keys.size() == 1); BOOST_TEST(string(joe_owner_authority.auth.keys[0].key) == string(chain.get_public_key("joe", "owner"))); BOOST_TEST(joe_owner_authority.auth.keys[0].weight == 1); const auto& joe_active_authority = chain.get(boost::make_tuple("joe", "active")); BOOST_TEST(joe_active_authority.auth.threshold == 1); - BOOST_TEST(joe_active_authority.auth.accounts.size() == 0); + BOOST_TEST(joe_active_authority.auth.accounts.size() == 1); BOOST_TEST(joe_active_authority.auth.keys.size() == 1); BOOST_TEST(string(joe_active_authority.auth.keys[0].key) == string(chain.get_public_key("joe", "active"))); BOOST_TEST(joe_active_authority.auth.keys[0].weight == 1); // Create duplicate name BOOST_CHECK_EXCEPTION(chain.create_account("joe"), action_validate_exception, - assert_message_ends_with("Cannot create account named joe, as that name is already taken")); + fc_exception_message_is("Cannot create account named joe, as that name is already taken")); // Creating account with name more than 12 chars BOOST_CHECK_EXCEPTION(chain.create_account("aaaaaaaaaaaaa"), action_validate_exception, - assert_message_ends_with("account names can only be 12 chars long")); + fc_exception_message_is("account names can only be 12 chars long")); + // Creating account with eosio. prefix with privileged account chain.create_account("eosio.test1"); // Creating account with eosio. prefix with non-privileged account, should fail BOOST_CHECK_EXCEPTION(chain.create_account("eosio.test2", "joe"), action_validate_exception, - assert_message_ends_with("only privileged accounts can have names that start with 'eosio.'")); + fc_exception_message_is("only privileged accounts can have names that start with 'eosio.'")); } FC_LOG_AND_RETHROW() } @@ -310,7 +348,7 @@ BOOST_AUTO_TEST_CASE( any_auth ) { try { /// this should fail because spending is not active which is default for reqauth BOOST_REQUIRE_THROW( chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }), - tx_irrelevant_auth ); + irrelevant_auth_exception ); chain.produce_block(); @@ -324,7 +362,7 @@ BOOST_AUTO_TEST_CASE( any_auth ) { try { /// this should fail because bob cannot authorize for alice, the permission given must be one-of alices BOOST_REQUIRE_THROW( chain.push_reqauth("alice", { permission_level{N(bob), "spending"} }, { spending_priv_key }), - tx_missing_auth ); + missing_auth_exception ); chain.produce_block(); @@ -345,7 +383,7 @@ try { chain.create_account(acc1a); chain.produce_block(); - chainbase::database &db = chain.control->get_mutable_database(); + chainbase::database &db = chain.control->db(); using resource_usage_object = eosio::chain::resource_limits::resource_usage_object; using by_owner = eosio::chain::resource_limits::by_owner; @@ -356,17 +394,16 @@ try { chain.set_transaction_headers(trx); authority owner_auth = authority( chain.get_public_key( a, "owner" ) ); - + vector pls = {{acc1, "active"}}; pls.push_back({acc1, "owner"}); // same account but different permission names pls.push_back({acc1a, "owner"}); trx.actions.emplace_back( pls, - contracts::newaccount{ + newaccount{ .creator = acc1, .name = a, .owner = owner_auth, - .active = authority( chain.get_public_key( a, "active" ) ), - .recovery = authority( chain.get_public_key( a, "recovery" ) ), + .active = authority( chain.get_public_key( a, "active" ) ) }); chain.set_transaction_headers(trx); @@ -410,16 +447,15 @@ try { chain.set_transaction_headers(trx); authority invalid_auth = authority(threshold, {key_weight{chain.get_public_key( a, "owner" ), 1}}, {permission_level_weight{{creator, config::active_name}, 1}}); - + vector pls; pls.push_back({creator, "active"}); trx.actions.emplace_back( pls, - contracts::newaccount{ + newaccount{ .creator = creator, .name = a, .owner = authority( chain.get_public_key( a, "owner" ) ), - .active = invalid_auth,//authority( chain.get_public_key( a, "active" ) ), - .recovery = authority( chain.get_public_key( a, "recovery" ) ), + .active = invalid_auth//authority( chain.get_public_key( a, "active" ) ), }); chain.set_transaction_headers(trx); @@ -427,12 +463,12 @@ try { return chain.push_transaction( trx ); }; - try { + try { create_acc(acc2, acc1, 0); BOOST_FAIL("threshold can't be zero"); } catch (...) { } - try { + try { create_acc(acc4, acc1, 3); BOOST_FAIL("threshold can't be more than total weight"); } catch (...) { } @@ -446,34 +482,32 @@ BOOST_AUTO_TEST_CASE( linkauth_special ) { try { const auto& tester_account = N(tester); std::vector ids; - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); chain.produce_blocks(); chain.create_account(N(currency)); - + chain.produce_blocks(); chain.create_account(N(tester)); chain.create_account(N(tester2)); chain.produce_blocks(); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 5)); + ("auth", authority(chain.get_public_key(tester_account, "first"), 5)) + ); auto validate_disallow = [&] (const char *type) { BOOST_REQUIRE_EXCEPTION( - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", "eosio") ("type", type) ("requirement", "first")), action_validate_exception, [] (const action_validate_exception &ex)->bool { - BOOST_REQUIRE_EQUAL(std::string("message validation exception"), ex.what()); + BOOST_REQUIRE_EQUAL(std::string("action exception"), ex.what()); return true; }); }; diff --git a/tests/tests/block_timestamp_tests.cpp b/unittests/block_timestamp_tests.cpp similarity index 95% rename from tests/tests/block_timestamp_tests.cpp rename to unittests/block_timestamp_tests.cpp index 80ed628c0d2..297789c9089 100644 --- a/tests/tests/block_timestamp_tests.cpp +++ b/unittests/block_timestamp_tests.cpp @@ -4,7 +4,7 @@ */ #include -#include +#include #include #include diff --git a/tests/chain_tests/bootseq_tests.cpp b/unittests/bootseq_tests.cpp similarity index 56% rename from tests/chain_tests/bootseq_tests.cpp rename to unittests/bootseq_tests.cpp index 418f228eb60..8b0349bc2f7 100644 --- a/tests/chain_tests/bootseq_tests.cpp +++ b/unittests/bootseq_tests.cpp @@ -1,17 +1,16 @@ #include #include -#include -#include +#include #include #include // These contracts are still under dev -#if _READY -#endif #include #include #include #include +#include +#include #include @@ -26,12 +25,32 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; using mvo = fc::mutable_variant_object; +struct genesis_account { + account_name aname; + uint64_t initial_balance; +}; + +std::vector test_genesis( { + {N(b1), 100'000'000'0000ll}, + {N(whale4), 40'000'000'0000ll}, + {N(whale3), 30'000'000'0000ll}, + {N(whale2), 20'000'000'0000ll}, + {N(p1), 1'000'000'0000ll}, + {N(p2), 1'000'000'0000ll}, + {N(p3), 1'000'000'0000ll}, + {N(p4), 1'000'000'0000ll}, + {N(p5), 1'000'000'0000ll}, + {N(minow1), 100'0000ll}, + {N(minow2), 1'0000ll}, + {N(minow3), 1'0000ll}, + {N(masses),800'000'000'0000ll} +}); + class bootseq_tester : public TESTER { public: @@ -56,7 +75,7 @@ class bootseq_tester : public TESTER } bootseq_tester() { - const auto &accnt = control->get_database().get(config::system_account_name); + const auto &accnt = control->db().get(config::system_account_name); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_ser.set_abi(abi); @@ -74,7 +93,7 @@ class bootseq_tester : public TESTER } - void create_currency( name contract, name manager, asset maxsupply ) { + void create_currency( name contract, name manager, asset maxsupply, const private_key_type* signer = nullptr ) { auto act = mutable_variant_object() ("issuer", manager ) ("maximum_supply", maxsupply ) @@ -94,16 +113,18 @@ class bootseq_tester : public TESTER } - - action_result stake(const account_name& from, const account_name& to, const string& net, const string& cpu, const string& storage ) { +/* + action_result stake(const account_name& from, const account_name& to, const string& net, const string& cpu, const string& ) { return push_action( name(from), name(from), N(delegatebw), mvo() ("from", from) ("receiver", to) ("stake_net_quantity", net) ("stake_cpu_quantity", cpu) ("stake_storage_quantity", storage) + ("transfer", storage) ); } + */ #if _READY fc::variant get_total_stake( const account_name& act ) { @@ -112,9 +133,11 @@ class bootseq_tester : public TESTER } #endif + /* action_result stake( const account_name& acnt, const string& net, const string& cpu, const string& storage ) { return stake( acnt, acnt, net, cpu, storage ); } + */ asset get_balance( const account_name& act ) { @@ -129,11 +152,11 @@ class bootseq_tester : public TESTER ); } - void set_code_abi(const account_name& account, const char* wast, const char* abi) + void set_code_abi(const account_name& account, const char* wast, const char* abi, const private_key_type* signer = nullptr) { wdump((account)); - set_code(account, wast); - set_abi(account, abi); + set_code(account, wast, signer); + set_abi(account, abi, signer); produce_blocks(); } @@ -145,26 +168,45 @@ BOOST_AUTO_TEST_SUITE(bootseq_tests) BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { try { + //set_code_abi(config::system_account_name, eosio_bios_wast, eosio_bios_abi); // Create the following accounts: // eosio.msig // eosio.token create_accounts({N(eosio.msig), N(eosio.token)}); + /* + auto eosio_active = authority( 1, {}, {{{N(eosio),N(active)},1}} ); + auto eosio_active_pk = get_private_key( N(eosio), "active" ); + + base_tester::push_action( N(eosio), N(newaccount), vector{{N(eosio),config::active_name}}, + fc::variant(newaccount{ + .creator = N(eosio), + .name = N(eosio.msig), + .owner = eosio_active, + .active = eosio_active, + .recovery = eosio_active + }).get_object() ); + + base_tester::push_action( N(eosio), N(newaccount), vector{{N(eosio),config::active_name}}, + fc::variant(newaccount{ + .creator = N(eosio), + .name = N(eosio.token), + .owner = eosio_active, + .active = eosio_active, + .recovery = eosio_active + }).get_object() ); + */ // Set code for the following accounts: // eosio.system (code: eosio.bios) // eosio.msig (code: eosio.msig) // eosio.token (code: eosio.token) -// These contracts are still under dev -#if _READY - set_code_abi(N(eosio.msig), eosio_msig_wast, eosio_msig_abi); -#endif -// set_code_abi(config::system_account_name, eosio_bios_wast, eosio_bios_abi); - set_code_abi(N(eosio.token), eosio_token_wast, eosio_token_abi); + set_code_abi(N(eosio.msig), eosio_msig_wast, eosio_msig_abi);//, &eosio_active_pk); + set_code_abi(N(eosio.token), eosio_token_wast, eosio_token_abi); //, &eosio_active_pk); ilog("."); // Set privileges for eosio.msig - auto trace = base_tester::push_action(config::system_account_name, N(setpriv), + auto trace = base_tester::push_action(config::system_account_name, N(setpriv), config::system_account_name, mutable_variant_object() ("account", "eosio.msig") ("is_priv", 1) @@ -174,29 +216,99 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { // Todo : how to check the privilege is set? (use is_priv action) - auto expected = asset::from_string("1000000000.0000 EOS"); + auto max_supply = asset::from_string("10000000000.0000 EOS"); /// 1x larger than 1B initial tokens + auto initial_supply = asset::from_string("1000000000.0000 EOS"); /// 1x larger than 1B initial tokens + // Create EOS tokens in eosio.token, set its manager as eosio.system - create_currency(N(eosio.token), config::system_account_name, expected); + create_currency(N(eosio.token), config::system_account_name, max_supply ); - ilog("."); // Issue the genesis supply of 1 billion EOS tokens to eosio.system // Issue the genesis supply of 1 billion EOS tokens to eosio.system - issue(N(eosio.token), config::system_account_name, config::system_account_name, expected); + issue(N(eosio.token), config::system_account_name, config::system_account_name, initial_supply); - ilog("."); auto actual = get_balance(config::system_account_name); - BOOST_REQUIRE_EQUAL(expected, actual); - ilog("."); + BOOST_REQUIRE_EQUAL(initial_supply, actual); // Create a few genesis accounts - std::vector gen_accounts{N(inita), N(initb), N(initc)}; - ilog("."); - create_accounts(gen_accounts); - ilog("."); + //std::vector gen_accounts{N(inita), N(initb), N(initc)}; + //create_accounts(gen_accounts); + + for( const auto& a : test_genesis ) { + create_account( a.aname, N(eosio) ); + /* + base_tester::push_action(N(eosio.token), N(transfer), config::system_account_name, mutable_variant_object() + ("from", name(config::system_account_name)) + ("to", name(a.aname)) + ("quantity", asset(a.initial_balance)) + ("memo", "" ) ); + */ + } + set_code_abi(N(eosio), eosio_system_wast, eosio_system_abi); //, &eosio_active_pk); + + for( const auto& a : test_genesis ) { + auto ib = a.initial_balance; + auto ram = 1000; + auto net = (ib - ram) / 2; + auto cpu = ib - net - ram; + + auto r = base_tester::push_action(N(eosio), N(buyram), N(eosio), mutable_variant_object() + ("payer", "eosio") + ("receiver", name(a.aname)) + ("quant", asset(ram)) + ); + BOOST_REQUIRE( !r->except_ptr ); + + r = base_tester::push_action(N(eosio), N(delegatebw), N(eosio), mutable_variant_object() + ("from", "eosio" ) + ("receiver", name(a.aname)) + ("stake_net_quantity", asset(net)) + ("stake_cpu_quantity", asset(cpu)) + ("transfer", 1) + ); + + BOOST_REQUIRE( !r->except_ptr ); + } + + produce_blocks(10000); + + for( auto pro : { N(p1), N(p2), N(p3), N(p4), N(p5) } ) { + base_tester::push_action(N(eosio), N(regproducer), pro, mutable_variant_object() + ("producer", name(pro)) + ("producer_key", get_public_key( pro, "active" ) ) + ("url", "" ) + ); + } + produce_blocks(10); + + auto votepro = [&]( account_name voter, vector producers ) { + std::sort( producers.begin(), producers.end() ); + base_tester::push_action(N(eosio), N(voteproducer), voter, mvo() + ("voter", name(voter)) + ("proxy", name(0) ) + ("producers", producers) + ); + }; + + produce_blocks(100); + + votepro( N(b1), {N(p1), N(p2)} ); + votepro( N(whale2), {N(p2), N(p3)} ); + votepro( N(whale3), {N(p2), N(p4), N(p5)} ); + + wlog("pb" ); + produce_blocks(10); + wdump((control->head_block_state()->active_schedule)); + return; + produce_blocks(7000); /// produce blocks until virutal bandwidth can acomadate a small user + wlog("minow" ); + votepro( N(minow1), {N(p1), N(p2)} ); + + + // Transfer EOS to genesis accounts - for (auto gen_acc : gen_accounts) { + /*for (auto gen_acc : gen_accounts) { auto quantity = "10000.0000 EOS"; auto stake_quantity = "5000.0000 EOS"; @@ -220,16 +332,21 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { BOOST_REQUIRE_EQUAL(asset::from_string(stake_quantity).amount, total["cpu_weight"].as_uint64()); #endif } + */ ilog("."); +#warning Complete this test +/* // Set code eosio.system from eosio.bios to eosio.system set_code_abi(config::system_account_name, eosio_system_wast, eosio_system_abi); + ilog("."); // Register these genesis accts as producer account for (auto gen_acc : gen_accounts) { // BOOST_REQUIRE_EQUAL(success(), regproducer(gen_acc)); } +*/ } FC_LOG_AND_RETHROW() } diff --git a/unittests/config.hpp.in b/unittests/config.hpp.in new file mode 100644 index 00000000000..1ece71657cc --- /dev/null +++ b/unittests/config.hpp.in @@ -0,0 +1,10 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +namespace eosio { namespace unittests { namespace config { + constexpr char eosiolib_path[] = "${CMAKE_CURRENT_SOURCE_DIR}/../contracts"; + constexpr char pfr_include_path[] = "${CMAKE_CURRENT_SOURCE_DIR}/../externals/magic_get/include"; + constexpr char boost_include_path[] = "${Boost_INCLUDE_DIR}"; +}}} diff --git a/tests/wasm_tests/test_softfloat_wasts.hpp b/unittests/contracts/test_softfloat_wasts.hpp similarity index 100% rename from tests/wasm_tests/test_softfloat_wasts.hpp rename to unittests/contracts/test_softfloat_wasts.hpp diff --git a/tests/wasm_tests/test_wasts.hpp b/unittests/contracts/test_wasts.hpp similarity index 87% rename from tests/wasm_tests/test_wasts.hpp rename to unittests/contracts/test_wasts.hpp index 4f399da592d..23a34afd12d 100644 --- a/tests/wasm_tests/test_wasts.hpp +++ b/unittests/contracts/test_wasts.hpp @@ -2,69 +2,20 @@ #include // These are handcrafted or otherwise tricky to generate with our tool chain -static const char call_depth_almost_limit_wast[] = R"=====( +/* +static const char f32_add_wast[] = R"=====( (module - (type (;0;) (func (param i64))) - (import "env" "printi" (func $printi (type 0))) - (table 0 anyfunc) - (memory $0 1) - (export "memory" (memory $0)) - (export "_foo" (func $_foo)) - (export "apply" (func $apply)) - (func $_foo (param $0 i32) - (block $label$0 - (br_if $label$0 - (i32.eqz - (get_local $0) - ) - ) - (call $_foo - (i32.add - (get_local $0) - (i32.const -1) - ) - ) - ) - ) - (func $apply (param $a i64) (param $b i64) (param $c i64) - (call $_foo - (i32.const 249) - ) - ) -) -)====="; - -static const char call_depth_limit_wast[] = R"=====( -(module - (type (;0;) (func (param i64))) - (import "env" "printi" (func $printi (type 0))) + (import "env" "eosio_assert" (func $eosio_assert (param i32 i32))) (table 0 anyfunc) (memory $0 1) (export "memory" (memory $0)) - (export "_foo" (func $_foo)) (export "apply" (func $apply)) - (func $_foo (param $0 i32) - (block $label$0 - (br_if $label$0 - (i32.eqz - (get_local $0) - ) - ) - (call $_foo - (i32.add - (get_local $0) - (i32.const -1) - ) - ) + (func $apply (param $0 i64) (param $1 i64) + (call $eosio_assert (i32.eq (i32.trunc_u/f32 (f32.const 0x3f800000)) (i32.const 0x0)) (i32.const 0)) ) ) - (func $apply (param $a i64) (param $b i64) (param $c i64) - (call $_foo - (i32.const 250) - ) - ) -) )====="; +*/ static const char aligned_ref_wast[] = R"=====( (module @@ -83,22 +34,6 @@ static const char aligned_ref_wast[] = R"=====( ) )====="; -static const char aligned_ptr_wast[] = R"=====( -(module - (import "env" "diveq_i128" (func $diveq_i128 (param i32 i32))) - (table 0 anyfunc) - (memory $0 32) - (data (i32.const 16) "random stuff") - (export "apply" (func $apply)) - (func $apply (param $0 i64) (param $1 i64) (param $2 i64) - (call $diveq_i128 - (i32.const 16) - (i32.const 16) - ) - ) -) -)====="; - static const char aligned_const_ref_wast[] = R"=====( (module (import "env" "sha256" (func $sha256 (param i32 i32 i32))) @@ -123,38 +58,6 @@ static const char aligned_const_ref_wast[] = R"=====( ) )====="; -static const char misaligned_ptr_wast[] = R"=====( -(module - (import "env" "diveq_i128" (func $diveq_i128 (param i32 i32))) - (table 0 anyfunc) - (memory $0 32) - (data (i32.const 16) "random stuff") - (export "apply" (func $apply)) - (func $apply (param $0 i64) (param $1 i64) (param $2 i64) - (call $diveq_i128 - (i32.const 17) - (i32.const 16) - ) - ) -) -)====="; - -static const char misaligned_const_ptr_wast[] = R"=====( -(module - (import "env" "diveq_i128" (func $diveq_i128 (param i32 i32))) - (table 0 anyfunc) - (memory $0 32) - (data (i32.const 16) "random stuff") - (export "apply" (func $apply)) - (func $apply (param $0 i64) (param $1 i64) (param $2 i64) - (call $diveq_i128 - (i32.const 16) - (i32.const 17) - ) - ) -) -)====="; - static const char misaligned_ref_wast[] = R"=====( (module (import "env" "sha256" (func $sha256 (param i32 i32 i32))) @@ -176,7 +79,7 @@ static const char misaligned_const_ref_wast[] = R"=====( (module (import "env" "sha256" (func $sha256 (param i32 i32 i32))) (import "env" "assert_sha256" (func $assert_sha256 (param i32 i32 i32))) - (import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32))) + (import "env" "memmove" (func $memmove (param i32 i32 i32) (result i32))) (table 0 anyfunc) (memory $0 32) (data (i32.const 4) "hello") @@ -189,7 +92,7 @@ static const char misaligned_const_ref_wast[] = R"=====( (i32.const 16) ) (set_local $3 - (call $memcpy + (call $memmove (i32.const 17) (i32.const 16) (i32.const 64) @@ -206,8 +109,9 @@ static const char misaligned_const_ref_wast[] = R"=====( static const char entry_wast[] = R"=====( (module + (import "env" "require_auth" (func $require_auth (param i64))) (import "env" "eosio_assert" (func $eosio_assert (param i32 i32))) - (import "env" "now" (func $now (result i32))) + (import "env" "current_time" (func $current_time (result i64))) (table 0 anyfunc) (memory $0 1) (export "memory" (memory $0)) @@ -215,20 +119,21 @@ static const char entry_wast[] = R"=====( (export "apply" (func $apply)) (func $entry (block - (i32.store offset=4 + (i64.store offset=4 (i32.const 0) - (call $now) + (call $current_time) ) ) ) (func $apply (param $0 i64) (param $1 i64) (param $2 i64) (block + (call $require_auth (i64.const 6121376101093867520)) (call $eosio_assert - (i32.eq - (i32.load offset=4 + (i64.eq + (i64.load offset=4 (i32.const 0) ) - (call $now) + (call $current_time) ) (i32.const 0) ) @@ -238,6 +143,41 @@ static const char entry_wast[] = R"=====( ) )====="; +static const char entry_wast_2[] = R"=====( +(module + (import "env" "require_auth" (func $require_auth (param i64))) + (import "env" "eosio_assert" (func $eosio_assert (param i32 i32))) + (import "env" "current_time" (func $current_time (result i64))) + (table 0 anyfunc) + (memory $0 1) + (export "memory" (memory $0)) + (export "apply" (func $apply)) + (start $entry) + (func $apply (param $0 i64) (param $1 i64) (param $2 i64) + (block + (call $require_auth (i64.const 6121376101093867520)) + (call $eosio_assert + (i64.eq + (i64.load offset=4 + (i32.const 0) + ) + (call $current_time) + ) + (i32.const 0) + ) + ) + ) + (func $entry + (block + (i64.store offset=4 + (i32.const 0) + (call $current_time) + ) + ) + ) +) +)====="; + static const char simple_no_memory_wast[] = R"=====( (module (import "env" "require_auth" (func $require_auth (param i64))) diff --git a/tests/wasm_tests/currency_tests.cpp b/unittests/currency_tests.cpp similarity index 71% rename from tests/wasm_tests/currency_tests.cpp rename to unittests/currency_tests.cpp index 2ec23e7a7f9..25001a33915 100644 --- a/tests/wasm_tests/currency_tests.cpp +++ b/unittests/currency_tests.cpp @@ -4,7 +4,8 @@ #pragma GCC diagnostic pop #include #include -#include +#include +#include #include #include @@ -25,7 +26,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; @@ -43,6 +43,7 @@ class currency_tester : public TESTER { signed_transaction trx; trx.actions.emplace_back(std::move(act)); + set_transaction_headers(trx); trx.sign(get_private_key(signer, "active"), chain_id_type()); return push_transaction(trx); @@ -52,6 +53,26 @@ class currency_tester : public TESTER { return get_currency_balance(N(eosio.token), symbol(SY(4,CUR)), account); } + auto transfer(const account_name& from, const account_name& to, const std::string& quantity, const std::string& memo = "") { + auto trace = push_action(from, N(transfer), mutable_variant_object() + ("from", from) + ("to", to) + ("quantity", quantity) + ("memo", memo) + ); + produce_block(); + return trace; + } + + auto issue(const account_name& to, const std::string& quantity, const std::string& memo = "") { + auto trace = push_action(N(eosio.token), N(issue), mutable_variant_object() + ("to", to) + ("quantity", quantity) + ("memo", memo) + ); + produce_block(); + return trace; + } currency_tester() :TESTER(),abi_ser(json::from_string(eosio_token_abi).as()) @@ -106,7 +127,7 @@ BOOST_FIXTURE_TEST_CASE( test_transfer, currency_tester ) try { produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" ) ); } } FC_LOG_AND_RETHROW() /// test_transfer @@ -121,16 +142,16 @@ BOOST_FIXTURE_TEST_CASE( test_duplicate_transfer, currency_tester ) { ("memo", "fund Alice") ); - BOOST_CHECK_THROW(push_action(N(eosio.token), N(transfer), mutable_variant_object() - ("from", eosio_token) - ("to", "alice") - ("quantity", "100.0000 CUR") - ("memo", "fund Alice")), - tx_duplicate); + BOOST_REQUIRE_THROW(push_action(N(eosio.token), N(transfer), mutable_variant_object() + ("from", eosio_token) + ("to", "alice") + ("quantity", "100.0000 CUR") + ("memo", "fund Alice")), + tx_duplicate); produce_block(); - BOOST_CHECK_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_CHECK_EQUAL(true, chain_has_transaction(trace->id)); BOOST_CHECK_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" ) ); } @@ -148,7 +169,7 @@ BOOST_FIXTURE_TEST_CASE( test_addtransfer, currency_tester ) try { produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" )); } @@ -163,7 +184,7 @@ BOOST_FIXTURE_TEST_CASE( test_addtransfer, currency_tester ) try { produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "110.0000 CUR" )); } } FC_LOG_AND_RETHROW() /// test_transfer @@ -183,7 +204,7 @@ BOOST_FIXTURE_TEST_CASE( test_overspend, currency_tester ) try { produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" )); } @@ -195,7 +216,8 @@ BOOST_FIXTURE_TEST_CASE( test_overspend, currency_tester ) try { ("quantity", "101.0000 CUR") ("memo", "overspend! Alice"); - BOOST_CHECK_EXCEPTION(push_action(N(alice), N(transfer), data), transaction_exception, assert_message_ends_with("overdrawn balance")); + BOOST_CHECK_EXCEPTION( push_action(N(alice), N(transfer), data), + fc::assert_exception, eosio_assert_message_is("overdrawn balance") ); produce_block(); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" )); @@ -217,7 +239,7 @@ BOOST_FIXTURE_TEST_CASE( test_fullspend, currency_tester ) try { produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "100.0000 CUR" )); } @@ -232,7 +254,7 @@ BOOST_FIXTURE_TEST_CASE( test_fullspend, currency_tester ) try { auto trace = push_action(N(alice), N(transfer), data); produce_block(); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace.id)); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trace->id)); BOOST_REQUIRE_EQUAL(get_balance(N(alice)), asset::from_string( "0.0000 CUR" )); BOOST_REQUIRE_EQUAL(get_balance(N(bob)), asset::from_string( "100.0000 CUR" )); } @@ -276,13 +298,13 @@ BOOST_FIXTURE_TEST_CASE(test_symbol, TESTER) try { // from empty string { BOOST_CHECK_EXCEPTION(symbol::from_string(""), - fc::assert_exception, assert_message_ends_with("creating symbol from empty string")); + fc::assert_exception, fc_assert_exception_message_is("creating symbol from empty string")); } // precision part missing { BOOST_CHECK_EXCEPTION(symbol::from_string("RND"), - fc::assert_exception, assert_message_ends_with("missing comma in symbol")); + fc::assert_exception, fc_assert_exception_message_is("missing comma in symbol")); } // 0 decimals part @@ -294,16 +316,14 @@ BOOST_FIXTURE_TEST_CASE(test_symbol, TESTER) try { // invalid - contains lower case characters, no validation { - symbol malformed(SY(6,EoS)); - BOOST_REQUIRE_EQUAL(false, malformed.valid()); - BOOST_REQUIRE_EQUAL("EoS", malformed.name()); - BOOST_REQUIRE_EQUAL(6, malformed.decimals()); + BOOST_CHECK_EXCEPTION(symbol malformed(SY(6,EoS)), + fc::assert_exception, fc_assert_exception_message_is("invalid symbol: EoS")); } - // invalid - contains lower case characters, exception thrown + // invalid - contains lower case characters, exception thrown { BOOST_CHECK_EXCEPTION(symbol(5,"EoS"), - fc::assert_exception, assert_message_ends_with("invalid character in symbol name")); + fc::assert_exception, fc_assert_exception_message_is("invalid character in symbol name")); } // Missing decimal point, should create asset with 0 decimals @@ -318,19 +338,19 @@ BOOST_FIXTURE_TEST_CASE(test_symbol, TESTER) try { // Missing space { BOOST_CHECK_EXCEPTION(asset::from_string("10CUR"), - asset_type_exception, assert_message_ends_with("Asset's amount and symbol should be separated with space")); + asset_type_exception, fc_exception_message_is("Asset's amount and symbol should be separated with space")); } // Precision is not specified when decimal separator is introduced { BOOST_CHECK_EXCEPTION(asset::from_string("10. CUR"), - asset_type_exception, assert_message_ends_with("Missing decimal fraction after decimal point")); + asset_type_exception, fc_exception_message_is("Missing decimal fraction after decimal point")); } // Missing symbol { BOOST_CHECK_EXCEPTION(asset::from_string("10"), - asset_type_exception, assert_message_ends_with("Asset's amount and symbol should be separated with space")); + asset_type_exception, fc_exception_message_is("Asset's amount and symbol should be separated with space")); } // Multiple spaces @@ -452,31 +472,37 @@ BOOST_FIXTURE_TEST_CASE( test_deferred_failure, currency_tester ) try { produce_block(); BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); } + const auto& index = control->db().get_index(); + BOOST_REQUIRE_EQUAL(0, index.size()); - // for now wasm "time" is in seconds, so we have to truncate off any parts of a second that may have applied - fc::time_point expected_delivery(fc::seconds(control->head_block_time().sec_since_epoch()) + fc::seconds(10)); auto trace = push_action(N(eosio.token), N(transfer), mutable_variant_object() ("from", eosio_token) ("to", "proxy") ("quantity", "5.0000 CUR") ("memo", "fund Proxy") ); + fc::time_point expected_delivery = control->pending_block_time() + fc::seconds(10); - BOOST_REQUIRE_EQUAL(trace.deferred_transaction_requests.size(), 1); - auto deferred_id = trace.deferred_transaction_requests.back().get().id(); + BOOST_REQUIRE_EQUAL(1, index.size()); + auto deferred_id = index.begin()->trx_id; + BOOST_REQUIRE_EQUAL(false, chain_has_transaction(deferred_id)); - while(control->head_block_time() < expected_delivery) { + while( control->pending_block_time() < expected_delivery ) { produce_block(); BOOST_REQUIRE_EQUAL(get_balance( N(proxy)), asset::from_string("5.0000 CUR")); BOOST_REQUIRE_EQUAL(get_balance( N(bob)), asset::from_string("0.0000 CUR")); BOOST_REQUIRE_EQUAL(get_balance( N(bob)), asset::from_string("0.0000 CUR")); - BOOST_REQUIRE_EQUAL(chain_has_transaction(deferred_id), false); + BOOST_REQUIRE_EQUAL(1, index.size()); + BOOST_REQUIRE_EQUAL(false, chain_has_transaction(deferred_id)); } - fc::time_point expected_redelivery(fc::seconds(control->head_block_time().sec_since_epoch()) + fc::seconds(10)); + fc::time_point expected_redelivery = control->pending_block_time() + fc::seconds(10); + // First deferred transaction should be retired in this block. + // It will fail, and its onerror handler will reschedule the transaction for 10 seconds later. produce_block(); - BOOST_REQUIRE_EQUAL(chain_has_transaction(deferred_id), true); + BOOST_REQUIRE_EQUAL(1, index.size()); // Still one because the first deferred transaction retires but the second is created at the same time. BOOST_REQUIRE_EQUAL(get_transaction_receipt(deferred_id).status, transaction_receipt::soft_fail); + auto deferred2_id = index.begin()->trx_id; // set up alice owner { @@ -498,19 +524,25 @@ BOOST_FIXTURE_TEST_CASE( test_deferred_failure, currency_tester ) try { BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); } - while(control->head_block_time() < expected_redelivery) { + while( control->pending_block_time() < expected_redelivery ) { produce_block(); BOOST_REQUIRE_EQUAL(get_balance( N(proxy)), asset::from_string("5.0000 CUR")); + BOOST_REQUIRE_EQUAL(get_balance( N(alice)), asset::from_string("0.0000 CUR")); BOOST_REQUIRE_EQUAL(get_balance( N(bob)), asset::from_string("0.0000 CUR")); - BOOST_REQUIRE_EQUAL(get_balance( N(bob)), asset::from_string("0.0000 CUR")); + BOOST_REQUIRE_EQUAL(1, index.size()); + BOOST_REQUIRE_EQUAL(false, chain_has_transaction(deferred2_id)); } - produce_block(); - BOOST_REQUIRE_EQUAL(get_balance( N(proxy)), asset::from_string("0.0000 CUR")); - BOOST_REQUIRE_EQUAL(get_balance( N(alice)), asset::from_string("0.0000 CUR")); - BOOST_REQUIRE_EQUAL(get_balance( N(bob)), asset::from_string("5.0000 CUR")); + BOOST_REQUIRE_EQUAL(1, index.size()); + // Second deferred transaction should be retired in this block and should succeed, + // which should move tokens from the proxy contract to the bob contract, thereby trigger the bob contract to + // schedule a third deferred transaction with no delay. + // That third deferred transaction (which moves tokens from the bob contract to account alice) should be executed immediately + // after in the same block (note that this is the current deferred transaction scheduling policy in tester and it may change). produce_block(); + BOOST_REQUIRE_EQUAL(0, index.size()); + BOOST_REQUIRE_EQUAL(get_transaction_receipt(deferred2_id).status, transaction_receipt::executed); BOOST_REQUIRE_EQUAL(get_balance( N(proxy)), asset::from_string("0.0000 CUR")); BOOST_REQUIRE_EQUAL(get_balance( N(alice)), asset::from_string("5.0000 CUR")); @@ -518,4 +550,36 @@ BOOST_FIXTURE_TEST_CASE( test_deferred_failure, currency_tester ) try { } FC_LOG_AND_RETHROW() /// test_currency +BOOST_FIXTURE_TEST_CASE( test_input_quantity, currency_tester ) try { + + produce_blocks(2); + + create_accounts( {N(alice), N(bob), N(carl)} ); + + // transfer to alice using right precision + { + auto trace = transfer(eosio_token, N(alice), "100.0000 CUR"); + + BOOST_CHECK_EQUAL(true, chain_has_transaction(trace->id)); + BOOST_CHECK_EQUAL(asset::from_string( "100.0000 CUR"), get_balance(N(alice))); + BOOST_CHECK_EQUAL(1000000, get_balance(N(alice)).amount); + } + + + // transfer using different symbol name fails + { + BOOST_REQUIRE_THROW(transfer(N(alice), N(carl), "20.50 USD"), fc::assert_exception); + } + + // issue to alice using right precision + { + auto trace = issue(N(alice), "25.0256 CUR"); + + BOOST_CHECK_EQUAL(true, chain_has_transaction(trace->id)); + BOOST_CHECK_EQUAL(asset::from_string("125.0256 CUR"), get_balance(N(alice))); + } + + +} FC_LOG_AND_RETHROW() /// test_currency + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/database_tests.cpp b/unittests/database_tests.cpp similarity index 74% rename from tests/tests/database_tests.cpp rename to unittests/database_tests.cpp index 88b130db8ed..6fc82485dd4 100644 --- a/tests/tests/database_tests.cpp +++ b/unittests/database_tests.cpp @@ -4,6 +4,7 @@ */ #include +#include #include #include @@ -25,7 +26,7 @@ BOOST_AUTO_TEST_SUITE(database_tests) BOOST_AUTO_TEST_CASE(undo_test) { try { TESTER test; - auto &db = test.control->get_mutable_database(); + auto &db = test.control->db(); auto ses = db.start_undo_session(true); @@ -47,7 +48,7 @@ BOOST_AUTO_TEST_SUITE(database_tests) } FC_LOG_AND_RETHROW() } - // Test the block fetching methods on database, get_block_id_for_num, fetch_bock_by_id, and fetch_block_by_number + // Test the block fetching methods on database, fetch_bock_by_id, and fetch_block_by_number BOOST_AUTO_TEST_CASE(get_blocks) { try { TESTER test; @@ -59,17 +60,15 @@ BOOST_AUTO_TEST_SUITE(database_tests) for (uint32_t i = 0; i < num_of_blocks_to_prod; ++i) { block_ids.emplace_back(test.control->fetch_block_by_number(i + 1)->id()); BOOST_TEST(block_header::num_from_id(block_ids.back()) == i + 1); - BOOST_TEST(test.control->get_block_id_for_num(i + 1) == block_ids.back()); BOOST_TEST(test.control->fetch_block_by_number(i + 1)->id() == block_ids.back()); } // Utility function to check expected irreversible block auto calc_exp_last_irr_block_num = [&](uint32_t head_block_num) -> uint32_t { - const global_property_object &gpo = test.control->get_global_properties(); - const auto producers_size = gpo.active_producers.producers.size(); + const auto producers_size = test.control->head_block_state()->active_schedule.producers.size(); const auto max_reversible_rounds = EOS_PERCENT(producers_size, config::percent_100 - config::irreversible_threshold_percent); if( max_reversible_rounds == 0) { - return head_block_num - 1; + return head_block_num; } else { const auto current_round = head_block_num / config::producer_repetitions; const auto irreversible_round = current_round - max_reversible_rounds; @@ -78,24 +77,23 @@ BOOST_AUTO_TEST_SUITE(database_tests) }; // Check the last irreversible block number is set correctly - const auto expected_last_irreversible_block_number = calc_exp_last_irr_block_num(num_of_blocks_to_prod); - BOOST_TEST(test.control->last_irreversible_block_num() == expected_last_irreversible_block_number); + const auto expected_last_irreversible_block_number = calc_exp_last_irr_block_num(num_of_blocks_to_prod) + 1; + BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == expected_last_irreversible_block_number); // Check that block 201 cannot be found (only 20 blocks exist) - BOOST_CHECK_THROW(test.control->get_block_id_for_num(num_of_blocks_to_prod + 1), - eosio::chain::unknown_block_exception); + BOOST_TEST(test.control->fetch_block_by_number(num_of_blocks_to_prod + 1 + 1) == nullptr); const uint32_t next_num_of_blocks_to_prod = 100; // Produce 100 blocks and check their IDs should match the above test.produce_blocks(next_num_of_blocks_to_prod); const auto next_expected_last_irreversible_block_number = calc_exp_last_irr_block_num( - num_of_blocks_to_prod + next_num_of_blocks_to_prod); + num_of_blocks_to_prod + next_num_of_blocks_to_prod) + 1; // Check the last irreversible block number is updated correctly - BOOST_TEST(test.control->last_irreversible_block_num() == next_expected_last_irreversible_block_number); + BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == next_expected_last_irreversible_block_number); // Check that block 201 can now be found - BOOST_CHECK_NO_THROW(test.control->get_block_id_for_num(num_of_blocks_to_prod + 1)); + BOOST_CHECK_NO_THROW(test.control->fetch_block_by_number(num_of_blocks_to_prod + 1)); // Check the latest head block match - BOOST_TEST(test.control->get_block_id_for_num(num_of_blocks_to_prod + next_num_of_blocks_to_prod) == + BOOST_TEST(test.control->fetch_block_by_number(num_of_blocks_to_prod + next_num_of_blocks_to_prod + 1)->id() == test.control->head_block_id()); } FC_LOG_AND_RETHROW() } diff --git a/tests/chain_tests/delay_tests.cpp b/unittests/delay_tests.cpp similarity index 68% rename from tests/chain_tests/delay_tests.cpp rename to unittests/delay_tests.cpp index 2452be7e338..ca08f7aaa3c 100644 --- a/tests/chain_tests/delay_tests.cpp +++ b/unittests/delay_tests.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -14,12 +16,73 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; BOOST_AUTO_TEST_SUITE(delay_tests) +BOOST_FIXTURE_TEST_CASE( delay_create_account, validating_tester) { try { + + produce_blocks(2); + signed_transaction trx; + + account_name a = N(newco); + account_name creator = config::system_account_name; + + auto owner_auth = authority( get_public_key( a, "owner" ) ); + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = owner_auth, + .active = authority( get_public_key( a, "active" ) ) + }); + set_transaction_headers(trx); + trx.delay_sec = 3; + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + + auto trace = push_transaction( trx ); + + produce_blocks(8); + +} FC_LOG_AND_RETHROW() } + + +BOOST_FIXTURE_TEST_CASE( delay_error_create_account, validating_tester) { try { + + produce_blocks(2); + signed_transaction trx; + + account_name a = N(newco); + account_name creator = config::system_account_name; + + auto owner_auth = authority( get_public_key( a, "owner" ) ); + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = N(bad), /// a does not exist, this should error when execute + .name = a, + .owner = owner_auth, + .active = authority( get_public_key( a, "active" ) ) + }); + set_transaction_headers(trx); + trx.delay_sec = 3; + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + + ilog( fc::json::to_pretty_string(trx) ); + auto trace = push_transaction( trx ); + edump((*trace)); + + produce_blocks(6); + + auto scheduled_trxs = control->get_scheduled_transactions(); + BOOST_REQUIRE_EQUAL(scheduled_trxs.size(), 1); + auto dtrace = control->push_scheduled_transaction(scheduled_trxs.front(), fc::time_point::maximum()); + BOOST_REQUIRE_EQUAL(dtrace->except.valid(), true); + BOOST_REQUIRE_EQUAL(dtrace->except->code(), missing_auth_exception::code_value); + +} FC_LOG_AND_RETHROW() } + + asset get_currency_balance(const TESTER& chain, account_name account) { return chain.get_currency_balance(N(eosio.token), symbol(SY(4,CUR)), account); } @@ -32,9 +95,6 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -47,13 +107,13 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -80,8 +140,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -97,8 +158,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -109,14 +171,15 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); - trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -127,9 +190,10 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_test ) { try { ("memo", "hi" ), 20, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("99.0000 CUR"), liquid_balance); @@ -172,9 +236,6 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -187,30 +248,29 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - transaction_trace trace; + transaction_trace_ptr trace; // can't delete auth because it doesn't exist BOOST_REQUIRE_EXCEPTION( - trace = chain.push_action(config::system_account_name, contracts::deleteauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, deleteauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first")), permission_query_exception, [] (const permission_query_exception &e)->bool { - std::string check_str = "3010001 permission_query_exception: Permission Query Exception\nFailed to retrieve permission"; - BOOST_REQUIRE_EQUAL(check_str, e.to_detail_string().substr(0, check_str.length())); + expect_assert_message(e, "permission_query_exception: Permission Query Exception\nFailed to retrieve permission"); return true; }); // update auth - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0)); + ("auth", authority(chain.get_public_key(tester_account, "first"))) + ); // link auth - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", "eosio.token") ("type", "transfer") @@ -240,8 +300,7 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(); @@ -257,7 +316,7 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); liquid_balance = get_currency_balance(chain, N(eosio.token)); BOOST_REQUIRE_EQUAL(asset::from_string("999900.0000 CUR"), liquid_balance); @@ -268,29 +327,28 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { // can't delete auth because it's linked BOOST_REQUIRE_EXCEPTION( - trace = chain.push_action(config::system_account_name, contracts::deleteauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, deleteauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first")), action_validate_exception, [] (const action_validate_exception &e)->bool { - std::string check_str = "3040000 action_validate_exception: message validation exception\nCannot delete a linked authority"; - BOOST_REQUIRE_EQUAL(check_str, e.to_detail_string().substr(0, check_str.length())); + expect_assert_message(e, "action_validate_exception: message validation exception\nCannot delete a linked authority"); return true; }); // unlink auth - trace = chain.push_action(config::system_account_name, contracts::unlinkauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, unlinkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", "eosio.token") ("type", "transfer")); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); // delete auth - trace = chain.push_action(config::system_account_name, contracts::deleteauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, deleteauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first")); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(1);; @@ -300,8 +358,7 @@ BOOST_AUTO_TEST_CASE(delete_auth_test) { try { ("quantity", "3.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(); @@ -319,9 +376,6 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -334,13 +388,13 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -367,8 +421,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -384,8 +439,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -396,14 +452,15 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); - trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "active") ("parent", "owner") - ("data", authority(chain.get_public_key(tester_account, "active"))) - ("delay", 15)); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + ("auth", authority(chain.get_public_key(tester_account, "active"), 15)) + ); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -414,9 +471,10 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_parent_permission_test ) { try { ("memo", "hi" ), 20, 15 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("99.0000 CUR"), liquid_balance); @@ -459,9 +517,6 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -474,19 +529,19 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"))) + ); + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "second") ("parent", "first") - ("data", authority(chain.get_public_key(tester_account, "second"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "second"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -513,8 +568,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -530,8 +586,9 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -542,14 +599,15 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); - trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 20)); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + ("auth", authority(chain.get_public_key(tester_account, "first"), 20)) + ); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -560,9 +618,10 @@ BOOST_AUTO_TEST_CASE( link_delay_direct_walk_parent_permissions_test ) { try { ("memo", "hi" ), 30, 20 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("99.0000 CUR"), liquid_balance); @@ -605,9 +664,6 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -620,13 +676,13 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -653,8 +709,9 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -672,9 +729,10 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -686,16 +744,16 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); // this transaction will be delayed 20 blocks - trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0), + ("auth", authority(chain.get_public_key(tester_account, "first"))), 30, 10); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -719,9 +777,10 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { ("memo", "hi" ), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -740,11 +799,20 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { // first transfer will finally be performed chain.produce_blocks(); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("99.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); + // delayed update auth removing the delay will finally execute + chain.produce_blocks(); + + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); + // this transfer is performed right away since delay is removed trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() ("from", "tester") @@ -752,8 +820,7 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(); @@ -769,16 +836,15 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - chain.produce_blocks(); - - liquid_balance = get_currency_balance(chain, N(tester)); - BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); - liquid_balance = get_currency_balance(chain, N(tester2)); - BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); // second transfer finally is performed chain.produce_blocks(); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(0, gen_size); + liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("84.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); @@ -792,9 +858,6 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -807,19 +870,19 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "second") ("parent", "first") - ("data", authority(chain.get_public_key(tester_account, "second"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "second"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -846,8 +909,9 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -865,9 +929,10 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -879,17 +944,18 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); // this transaction will be delayed 20 blocks - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0), + ("auth", authority(chain.get_public_key(tester_account, "first"))), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -913,9 +979,11 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { ("memo", "hi" ), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -939,6 +1007,8 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); + chain.produce_blocks(); + // this transfer is performed right away since delay is removed trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() ("from", "tester") @@ -946,8 +1016,10 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); chain.produce_blocks(); @@ -956,7 +1028,7 @@ BOOST_AUTO_TEST_CASE( link_delay_permission_change_with_delay_heirarchy_test ) { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - chain.produce_blocks(15); + chain.produce_blocks(14); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); @@ -986,9 +1058,6 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -1001,23 +1070,23 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") ("requirement", "first")); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "second") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "second"))) - ("delay", 0)); + ("auth", authority(chain.get_public_key(tester_account, "second"))) + ); chain.produce_blocks(); chain.push_action(N(eosio.token), N(create), N(eosio.token), mutable_variant_object() @@ -1040,8 +1109,9 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -1058,10 +1128,10 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { ("memo", "hi" ), 30, 10 ); - - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1073,31 +1143,32 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); BOOST_REQUIRE_EXCEPTION( - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action( config::system_account_name, linkauth::get_name(), + vector{permission_level{tester_account, N(first)}}, + fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") ("requirement", "second"), 30, 3), - transaction_exception, - [] (const transaction_exception &e)->bool { - std::string check_str = "3030000 transaction_exception: transaction validation exception\nauthorization imposes a delay (10 sec) greater than the delay specified in transaction header (3 sec)"; - BOOST_REQUIRE_EQUAL(check_str, e.to_detail_string().substr(0, check_str.length())); - return true; - } + unsatisfied_authorization, + fc_exception_message_starts_with("transaction declares authority") ); // this transaction will be delayed 20 blocks - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action( config::system_account_name, linkauth::get_name(), + vector{{tester_account, N(first)}}, + fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") ("requirement", "second"), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1121,9 +1192,10 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { ("memo", "hi" ), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1147,6 +1219,9 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); + // delay on minimum permission of transfer is finally removed + chain.produce_blocks(); + // this transfer is performed right away since delay is removed trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() ("from", "tester") @@ -1154,24 +1229,17 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_test ) { try { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); - chain.produce_blocks(); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - chain.produce_blocks(15); - - liquid_balance = get_currency_balance(chain, N(tester)); - BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); - liquid_balance = get_currency_balance(chain, N(tester2)); - BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - - chain.produce_blocks(); + chain.produce_blocks(16); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); @@ -1195,9 +1263,6 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -1210,13 +1275,13 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -1243,8 +1308,7 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(); @@ -1261,10 +1325,10 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { ("memo", "hi" ), 30, 10 ); - - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1276,29 +1340,29 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); BOOST_REQUIRE_EXCEPTION( - chain.push_action(config::system_account_name, contracts::unlinkauth::get_name(), tester_account, fc::mutable_variant_object() - ("account", "tester") - ("code", eosio_token) - ("type", "transfer"), - 30, 7), - transaction_exception, - [] (const transaction_exception &e)->bool { - std::string check_str = "3030000 transaction_exception: transaction validation exception\nauthorization imposes a delay (10 sec) greater than the delay specified in transaction header (7 sec)"; - BOOST_REQUIRE_EQUAL(check_str, e.to_detail_string().substr(0, check_str.length())); - return true; - } + chain.push_action( config::system_account_name, unlinkauth::get_name(), + vector{{tester_account, N(first)}}, + fc::mutable_variant_object() + ("account", "tester") + ("code", eosio_token) + ("type", "transfer"), + 30, 7 + ), + unsatisfied_authorization, + fc_exception_message_starts_with("transaction declares authority") ); // this transaction will be delayed 20 blocks - chain.push_action(config::system_account_name, contracts::unlinkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, unlinkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer"), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1322,9 +1386,10 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { ("memo", "hi" ), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1348,6 +1413,9 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); + // the delayed unlinkauth finally occurs + chain.produce_blocks(); + // this transfer is performed right away since delay is removed trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() ("from", "tester") @@ -1355,8 +1423,7 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); chain.produce_blocks(); @@ -1372,13 +1439,6 @@ BOOST_AUTO_TEST_CASE( link_delay_unlink_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - chain.produce_blocks(); - - liquid_balance = get_currency_balance(chain, N(tester)); - BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); - liquid_balance = get_currency_balance(chain, N(tester2)); - BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - // second transfer finally is performed chain.produce_blocks(); @@ -1395,9 +1455,6 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -1410,29 +1467,29 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "second") ("parent", "first") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") ("requirement", "second")); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "third") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "third"))) - ("delay", 0)); + ("auth", authority(chain.get_public_key(tester_account, "third"))) + ); chain.produce_blocks(); chain.push_action(N(eosio.token), N(create), N(eosio.token), mutable_variant_object() @@ -1455,8 +1512,9 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -1473,10 +1531,10 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { ("memo", "hi" ), 30, 10 ); - - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1488,16 +1546,17 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); // this transaction will be delayed 20 blocks - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") ("requirement", "third"), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1521,9 +1580,10 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { ("memo", "hi" ), 30, 10 ); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1547,6 +1607,9 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); + // delay on minimum permission of transfer is finally removed + chain.produce_blocks(); + // this transfer is performed right away since delay is removed trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() ("from", "tester") @@ -1554,24 +1617,16 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); - - chain.produce_blocks(); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - chain.produce_blocks(15); - - liquid_balance = get_currency_balance(chain, N(tester)); - BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); - liquid_balance = get_currency_balance(chain, N(tester2)); - BOOST_REQUIRE_EQUAL(asset::from_string("11.0000 CUR"), liquid_balance); - - chain.produce_blocks(); + chain.produce_blocks(16); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("89.0000 CUR"), liquid_balance); @@ -1586,7 +1641,7 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("16.0000 CUR"), liquid_balance); -} FC_LOG_AND_RETHROW() }/// schedule_test +} FC_LOG_AND_RETHROW() } /// link_delay_link_change_heirarchy_test // test delay_sec field imposing unneeded delay BOOST_AUTO_TEST_CASE( mindelay_test ) { try { @@ -1594,9 +1649,6 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(eosio.token)); chain.produce_blocks(10); @@ -1629,8 +1681,9 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -1646,8 +1699,9 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); @@ -1659,10 +1713,10 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("1.0000 CUR"), liquid_balance); // send transfer with delay_sec set to 10 - const auto& acnt = chain.control->get_database().get(N(eosio.token)); + const auto& acnt = chain.control->db().get(N(eosio.token)); const auto abi = acnt.get_abi(); - chain::contracts::abi_serializer abis(abi); - const auto a = chain.control->get_database().get(N(eosio.token)).get_abi(); + chain::abi_serializer abis(abi); + const auto a = chain.control->db().get(N(eosio.token)).get_abi(); string action_type_name = abis.get_action_type(name("transfer")); @@ -1683,9 +1737,10 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { chain.set_transaction_headers(trx, 30, 10); trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type()); trace = chain.push_transaction(trx); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("99.0000 CUR"), liquid_balance); @@ -1725,11 +1780,8 @@ BOOST_AUTO_TEST_CASE( mindelay_test ) { try { // test canceldelay action cancelling a delayed transaction BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { TESTER chain; - const auto& tester_account = N(tester); std::vector ids; - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); chain.produce_blocks(); chain.create_account(N(eosio.token)); @@ -1743,13 +1795,13 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { chain.create_account(N(tester2)); chain.produce_blocks(10); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 10)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 10)) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -1776,8 +1828,9 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); auto liquid_balance = get_currency_balance(chain, N(eosio.token)); @@ -1793,12 +1846,16 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { ("memo", "hi" ), 30, 10 ); - ids.push_back(trace.id); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + //wdump((fc::json::to_pretty_string(trace))); + ids.push_back(trace->id); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); - const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get().sender_id; + const auto& idx = chain.control->db().get_index(); + auto itr = idx.find( trace->id ); + BOOST_CHECK_EQUAL( (itr != idx.end()), true ); chain.produce_blocks(); @@ -1810,35 +1867,34 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); BOOST_REQUIRE_EXCEPTION( - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action( config::system_account_name, + updateauth::get_name(), + vector{{tester_account, N(first)}}, + fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0), + ("auth", authority(chain.get_public_key(tester_account, "first"))), 30, 7 ), - transaction_exception, - [] (const transaction_exception &e)->bool { - std::string check_str = "3030000 transaction_exception: transaction validation exception\nauthorization imposes a delay (10 sec) greater than the delay specified in transaction header (7 sec)"; - BOOST_REQUIRE_EQUAL(check_str, e.to_detail_string().substr(0, check_str.length())); - return true; - } + unsatisfied_authorization, + fc_exception_message_starts_with("transaction declares authority") ); // this transaction will be delayed 20 blocks - trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 0), + ("auth", authority(chain.get_public_key(tester_account, "first"))), 30, 10 ); - ids.push_back(trace.id); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + //wdump((fc::json::to_pretty_string(trace))); + ids.push_back(trace->id); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1862,10 +1918,12 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { ("memo", "hi" ), 30, 10 ); - ids.push_back(trace.id); - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + //wdump((fc::json::to_pretty_string(trace))); + ids.push_back(trace->id); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(3, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); chain.produce_blocks(); @@ -1877,17 +1935,19 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { // send canceldelay for first delayed transaction signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), config::active_name}}, - chain::contracts::canceldelay{{N(tester), config::active_name}, ids[0]}); + chain::canceldelay{{N(tester), config::active_name}, ids[0]}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type()); trace = chain.push_transaction(trx); + //wdump((fc::json::to_pretty_string(trace))); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - - const auto sender_id_canceled = trace.deferred_transaction_requests[0].get().sender_id; - BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled))); + const auto& cidx = chain.control->db().get_index(); + auto citr = cidx.find( ids[0] ); + BOOST_CHECK_EQUAL( (citr == cidx.end()), true ); chain.produce_blocks(); @@ -1896,9 +1956,20 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { liquid_balance = get_currency_balance(chain, N(tester2)); BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance); - // update auth will finally be performed + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + chain.produce_blocks(); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(2, gen_size); + + chain.produce_blocks(); + // update auth will finally be performed + + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); + liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); @@ -1911,8 +1982,11 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { ("quantity", "10.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + //wdump((fc::json::to_pretty_string(trace))); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); chain.produce_blocks(); @@ -1923,12 +1997,8 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { chain.produce_blocks(15); - liquid_balance = get_currency_balance(chain, N(tester)); - BOOST_REQUIRE_EQUAL(asset::from_string("90.0000 CUR"), liquid_balance); - liquid_balance = get_currency_balance(chain, N(tester2)); - BOOST_REQUIRE_EQUAL(asset::from_string("10.0000 CUR"), liquid_balance); - - chain.produce_blocks(); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("90.0000 CUR"), liquid_balance); @@ -1938,6 +2008,9 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { // second transfer finally is performed chain.produce_blocks(); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(0, gen_size); + liquid_balance = get_currency_balance(chain, N(tester)); BOOST_REQUIRE_EQUAL(asset::from_string("85.0000 CUR"), liquid_balance); liquid_balance = get_currency_balance(chain, N(tester2)); @@ -1949,9 +2022,6 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { TESTER chain; const auto& tester_account = N(tester); - std::vector ids; - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); chain.produce_blocks(); chain.create_account(N(eosio.token)); @@ -1965,19 +2035,19 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { chain.create_account(N(tester2)); chain.produce_blocks(); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 5)); - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "first"), 5)) + ); + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "second") ("parent", "first") - ("data", authority(chain.get_public_key(tester_account, "second"))) - ("delay", 0)); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + ("auth", authority(chain.get_public_key(tester_account, "second"))) + ); + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -2004,8 +2074,9 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { ("quantity", "100.0000 CUR") ("memo", "hi" ) ); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); chain.produce_blocks(); auto liquid_balance = get_currency_balance(chain, N(eosio.token)); @@ -2024,12 +2095,15 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { ("memo", "hi" ), 30, 5 ); - auto trx_id = trace.id; - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + auto trx_id = trace->id; + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); - const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get().sender_id; + const auto& idx = chain.control->db().get_index(); + auto itr = idx.find( trx_id ); + BOOST_CHECK_EQUAL( (itr != idx.end()), true ); chain.produce_blocks(); @@ -2042,35 +2116,40 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { { signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), config::active_name}}, - chain::contracts::canceldelay{{N(tester), config::active_name}, trx_id}); + chain::canceldelay{{N(tester), config::active_name}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type()); - BOOST_REQUIRE_THROW( chain.push_transaction(trx), transaction_exception ); + BOOST_REQUIRE_EXCEPTION( chain.push_transaction(trx), action_validate_exception, + fc_exception_message_is("canceling_auth in canceldelay action was not found as authorization in the original delayed transaction") ); } // attempt canceldelay with "second" permission for delayed transfer of 1.0000 CUR { signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), N(second)}}, - chain::contracts::canceldelay{{N(tester), N(first)}, trx_id}); + chain::canceldelay{{N(tester), N(first)}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "second"), chain_id_type()); - BOOST_REQUIRE_THROW( chain.push_transaction(trx), tx_irrelevant_auth ); + BOOST_REQUIRE_THROW( chain.push_transaction(trx), irrelevant_auth_exception ); + BOOST_REQUIRE_EXCEPTION( chain.push_transaction(trx), irrelevant_auth_exception, + fc_exception_message_starts_with("canceldelay action declares irrelevant authority") ); } // canceldelay with "active" permission for delayed transfer of 1.0000 CUR signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), config::active_name}}, - chain::contracts::canceldelay{{N(tester), N(first)}, trx_id}); + chain::canceldelay{{N(tester), N(first)}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type()); trace = chain.push_transaction(trx); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); - const auto sender_id_canceled = trace.deferred_transaction_requests[0].get().sender_id; - BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled))); + const auto& cidx = chain.control->db().get_index(); + auto citr = cidx.find( trx_id ); + BOOST_REQUIRE_EQUAL( (citr == cidx.end()), true ); chain.produce_blocks(10); @@ -2082,7 +2161,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { ilog("reset minimum permission of transfer to second permission"); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", eosio_token) ("type", "transfer") @@ -2090,7 +2169,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { 30, 5 ); - chain.produce_blocks(10); + chain.produce_blocks(11); ilog("attempting second delayed transfer"); @@ -2103,12 +2182,15 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { ("memo", "hi" ), 30, 5 ); - auto trx_id = trace.id; - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + auto trx_id = trace->id; + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + auto gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(1, gen_size); + BOOST_CHECK_EQUAL(0, trace->action_traces.size()); - const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get().sender_id; + const auto& idx = chain.control->db().get_index(); + auto itr = idx.find( trx_id ); + BOOST_CHECK_EQUAL( (itr != idx.end()), true ); chain.produce_blocks(); @@ -2120,16 +2202,18 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { // canceldelay with "first" permission for delayed transfer of 5.0000 CUR signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), N(first)}}, - chain::contracts::canceldelay{{N(tester), N(second)}, trx_id}); + chain::canceldelay{{N(tester), N(second)}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "first"), chain_id_type()); trace = chain.push_transaction(trx); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); - const auto sender_id_canceled = trace.deferred_transaction_requests[0].get().sender_id; - BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled))); + const auto& cidx = chain.control->db().get_index(); + auto citr = cidx.find( trx_id ); + BOOST_REQUIRE_EQUAL( (citr == cidx.end()), true ); chain.produce_blocks(10); @@ -2150,12 +2234,15 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { ("memo", "hi" ), 30, 5 ); - auto trx_id = trace.id; - BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); - BOOST_REQUIRE_EQUAL(0, trace.action_traces.size()); + auto trx_id = trace->id; + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); - const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get().sender_id; + const auto& idx = chain.control->db().get_index(); + auto itr = idx.find( trx_id ); + BOOST_CHECK_EQUAL( (itr != idx.end()), true ); chain.produce_blocks(); @@ -2168,25 +2255,27 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { { signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), N(active)}}, - chain::contracts::canceldelay{{N(tester), config::owner_name}, trx_id}); + chain::canceldelay{{N(tester), config::owner_name}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type()); - BOOST_REQUIRE_THROW( chain.push_transaction(trx), tx_irrelevant_auth ); + BOOST_REQUIRE_THROW( chain.push_transaction(trx), irrelevant_auth_exception ); } // canceldelay with "owner" permission for delayed transfer of 10.0000 CUR signed_transaction trx; trx.actions.emplace_back(vector{{N(tester), config::owner_name}}, - chain::contracts::canceldelay{{N(tester), config::owner_name}, trx_id}); + chain::canceldelay{{N(tester), config::owner_name}, trx_id}); chain.set_transaction_headers(trx); trx.sign(chain.get_private_key(N(tester), "owner"), chain_id_type()); trace = chain.push_transaction(trx); - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(0, gen_size); - const auto sender_id_canceled = trace.deferred_transaction_requests[0].get().sender_id; - BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled))); + const auto& cidx = chain.control->db().get_index(); + auto citr = cidx.find( trx_id ); + BOOST_REQUIRE_EQUAL( (citr == cidx.end()), true ); chain.produce_blocks(10); @@ -2199,50 +2288,39 @@ BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try { BOOST_AUTO_TEST_CASE( max_transaction_delay_create ) { try { - //assuming max transaction delay is 45 days (default in confgi.hpp) + //assuming max transaction delay is 45 days (default in config.hpp) TESTER chain; const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.produce_blocks(); chain.create_account(N(tester)); chain.produce_blocks(10); BOOST_REQUIRE_EXCEPTION( - chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 50*86400)), //50 days - chain::action_validate_exception, - [&](const chain::transaction_exception& ex) { - string expected = "message validation exception (3040000)\nCannot set delay longer than max_transacton_delay, which is 3888000 seconds"; - return expected == string(ex.to_string()).substr(0, expected.size()); - } + ("auth", authority(chain.get_public_key(tester_account, "first"), 50*86400)) ), // 50 days delay + action_validate_exception, + fc_exception_message_starts_with("Cannot set delay longer than max_transacton_delay") ); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( max_transaction_delay_execute ) { try { - //assuming max transaction delay is 45 days (default in confgi.hpp) + //assuming max transaction delay is 45 days (default in config.hpp) TESTER chain; const auto& tester_account = N(tester); - chain.set_code(config::system_account_name, eosio_system_wast); - chain.set_abi(config::system_account_name, eosio_system_abi); - chain.create_account(N(eosio.token)); chain.set_code(N(eosio.token), eosio_token_wast); chain.set_abi(N(eosio.token), eosio_token_abi); chain.produce_blocks(); chain.create_account(N(tester)); - chain.produce_blocks(10); chain.produce_blocks(); chain.push_action(N(eosio.token), N(create), N(eosio.token), mutable_variant_object() @@ -2259,43 +2337,54 @@ BOOST_AUTO_TEST_CASE( max_transaction_delay_execute ) { try { ); //create a permission level with delay 30 days and associate it with token transfer - auto trace = chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object() + auto trace = chain.push_action(config::system_account_name, updateauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("permission", "first") ("parent", "active") - ("data", authority(chain.get_public_key(tester_account, "first"))) - ("delay", 30*86400)); //30 days - BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status); - BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size()); + ("auth", authority(chain.get_public_key(tester_account, "first"), 30*86400)) // 30 days delay + ); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); - chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object() + trace = chain.push_action(config::system_account_name, linkauth::get_name(), tester_account, fc::mutable_variant_object() ("account", "tester") ("code", "eosio.token") ("type", "transfer") ("requirement", "first")); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + + chain.produce_blocks(); //change max_transaction_delay to 60 sec - chain.control->get_mutable_database().modify( chain.control->get_global_properties(), - [&]( auto& gprops ) { - gprops.configuration.max_transaction_delay = 60; - }); + chain.control->db().modify( chain.control->get_global_properties(), + [&]( auto& gprops ) { + gprops.configuration.max_transaction_delay = 60; + }); +#ifndef NON_VALIDATING_TEST + chain.validating_node->db().modify( chain.validating_node->get_global_properties(), + [&]( auto& gprops ) { + gprops.configuration.max_transaction_delay = 60; + }); +#endif + chain.produce_blocks(); //should be able to create transaction with delay 60 sec, despite permission delay being 30 days, because max_transaction_delay is 60 sec trace = chain.push_action(N(eosio.token), name("transfer"), N(tester), fc::mutable_variant_object() - ("from", "tester") - ("to", "eosio.token") - ("quantity", "9.0000 CUR") - ("memo", "" ), - 120, 60 - ); - BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size()); + ("from", "tester") + ("to", "eosio.token") + ("quantity", "9.0000 CUR") + ("memo", "" ), 120, 60); + BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace->receipt->status); + + chain.produce_blocks(); + + auto gen_size = chain.control->db().get_index().size(); + BOOST_REQUIRE_EQUAL(1, gen_size); + BOOST_REQUIRE_EQUAL(0, trace->action_traces.size()); //check that the delayed transaction executed after after 60 sec - chain.produce_block( fc::seconds(60) ); - auto traces = chain.control->push_deferred_transactions(true); - BOOST_REQUIRE_EQUAL( 1, traces.size() ); - BOOST_REQUIRE( 1 <= traces.at(0).action_traces.size() ); - BOOST_REQUIRE_EQUAL( transaction_receipt::executed, traces.at(0).status ); + chain.produce_blocks(120); + gen_size = chain.control->db().get_index().size(); + BOOST_CHECK_EQUAL(0, gen_size); //check that the transfer really happened auto liquid_balance = get_currency_balance(chain, N(tester)); diff --git a/tests/wasm_tests/dice_tests.cpp b/unittests/dice_tests.cpp similarity index 94% rename from tests/wasm_tests/dice_tests.cpp rename to unittests/dice_tests.cpp index 12cb732a3ad..d77a15edebf 100644 --- a/tests/wasm_tests/dice_tests.cpp +++ b/unittests/dice_tests.cpp @@ -1,6 +1,7 @@ #include #include -#include +#include +#include #include #include @@ -20,7 +21,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; using namespace std; @@ -98,7 +98,7 @@ struct dice_tester : TESTER { template const auto& get_index() { - return control->get_database().get_index(); + return control->db().get_index(); } void offer_bet(account_name account, asset amount, const checksum_type& commitment) { @@ -108,7 +108,8 @@ struct dice_tester : TESTER { trx.actions.push_back(act); set_transaction_headers(trx); trx.sign(get_private_key( account, "active" ), chain_id_type()); - control->push_transaction(packed_transaction(trx,packed_transaction::none)); + auto ptrx = packed_transaction(trx,packed_transaction::none); + push_transaction(ptrx); } void cancel_offer(account_name account, const checksum_type& commitment) { @@ -118,7 +119,8 @@ struct dice_tester : TESTER { trx.actions.push_back(act); set_transaction_headers(trx); trx.sign(get_private_key( account, "active" ), chain_id_type()); - control->push_transaction(packed_transaction(trx,packed_transaction::none)); + auto ptrx = packed_transaction(trx,packed_transaction::none); + push_transaction(ptrx); } void deposit(account_name account, asset amount) { @@ -128,7 +130,8 @@ struct dice_tester : TESTER { trx.actions.push_back(act); set_transaction_headers(trx); trx.sign(get_private_key( account, "active" ), chain_id_type()); - control->push_transaction(packed_transaction(trx,packed_transaction::none)); + auto ptrx = packed_transaction(trx,packed_transaction::none); + push_transaction(ptrx); } void withdraw(account_name account, asset amount) { @@ -138,7 +141,8 @@ struct dice_tester : TESTER { trx.actions.push_back(act); set_transaction_headers(trx); trx.sign(get_private_key( account, "active" ), chain_id_type()); - control->push_transaction(packed_transaction(trx,packed_transaction::none)); + auto ptrx = packed_transaction(trx,packed_transaction::none); + push_transaction(ptrx); } void reveal(account_name account, const checksum_type& commitment, const checksum_type& source ) { @@ -148,14 +152,15 @@ struct dice_tester : TESTER { trx.actions.push_back(act); set_transaction_headers(trx); trx.sign(get_private_key( account, "active" ), chain_id_type()); - control->push_transaction(packed_transaction(trx,packed_transaction::none)); + auto ptrx = packed_transaction(trx,packed_transaction::none); + push_transaction(ptrx); } bool dice_account(account_name account, account_t& acnt) { auto* maybe_tid = find_table(N(dice), N(dice), N(account)); if(maybe_tid == nullptr) return false; - auto* o = control->get_database().find(boost::make_tuple(maybe_tid->id, account)); + auto* o = control->db().find(boost::make_tuple(maybe_tid->id, account)); if(o == nullptr) { return false; } diff --git a/unittests/eosio.system_tests.cpp b/unittests/eosio.system_tests.cpp new file mode 100644 index 00000000000..9d8635e2237 --- /dev/null +++ b/unittests/eosio.system_tests.cpp @@ -0,0 +1,2186 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +#ifdef NON_VALIDATING_TEST +#define TESTER tester +#else +#define TESTER validating_tester +#endif + +using namespace eosio::testing; +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +using mvo = fc::mutable_variant_object; + +class eosio_system_tester : public TESTER { +public: + + eosio_system_tester() { + produce_blocks( 2 ); + + create_accounts( { N(eosio.token) } ); + + produce_blocks( 100 ); + + set_code( N(eosio.token), eosio_token_wast ); + set_abi( N(eosio.token), eosio_token_abi ); + + create_currency( N(eosio.token), config::system_account_name, asset::from_string("10000000000.0000 EOS") ); + issue(config::system_account_name, "1000000000.0000 EOS"); + BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); + + set_code( config::system_account_name, eosio_system_wast ); + set_abi( config::system_account_name, eosio_system_abi ); + + produce_blocks(); + + create_account_with_resources( N(alice), N(eosio), asset::from_string("1.0000 EOS"), false ); + create_account_with_resources( N(bob), N(eosio), asset::from_string("0.4500 EOS"), false ); + create_account_with_resources( N(carol), N(eosio), asset::from_string("1.0000 EOS"), false ); + + BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); + + // eosio pays it self for these... + //BOOST_REQUIRE_EQUAL( asset::from_string("999999998.5000 EOS"), get_balance( "eosio" ) ); + + produce_blocks(); + + { + const auto& accnt = control->db().get( config::system_account_name ); + abi_def abi; + BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); + abi_ser.set_abi(abi); + } + { + const auto& accnt = control->db().get( N(eosio.token) ); + abi_def abi; + BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); + token_abi_ser.set_abi(abi); + } + } + + void create_accounts_with_resources( vector accounts, account_name creator = N(eosio) ) { + for( auto a : accounts ) { + create_account_with_resources( a, creator ); + } + } + + transaction_trace_ptr create_account_with_resources( account_name a, account_name creator ) { + signed_transaction trx; + set_transaction_headers(trx); + + authority owner_auth; + owner_auth = authority( get_public_key( a, "owner" ) ); + + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = owner_auth, + .active = authority( get_public_key( a, "active" ) ) + }); + + trx.actions.emplace_back( get_action( N(eosio), N(buyrambytes), vector{{creator,config::active_name}}, + mvo() + ("payer", creator) + ("receiver", a) + ("bytes", 8000) ) + ); + trx.actions.emplace_back( get_action( N(eosio), N(delegatebw), vector{{creator,config::active_name}}, + mvo() + ("from", creator) + ("receiver", a) + ("stake_net_quantity", "10.0000 EOS" ) + ("stake_cpu_quantity", "10.0000 EOS" ) + ("transfer", 0 ) + ) + ); + + set_transaction_headers(trx); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + return push_transaction( trx ); + } + + transaction_trace_ptr create_account_with_resources( account_name a, account_name creator, asset ramfunds, bool multisig, + asset net = asset::from_string("10.0000 EOS"), asset cpu = asset::from_string("10.0000 EOS") ) { + signed_transaction trx; + set_transaction_headers(trx); + + authority owner_auth; + if (multisig) { + // multisig between account's owner key and creators active permission + owner_auth = authority(2, {key_weight{get_public_key( a, "owner" ), 1}}, {permission_level_weight{{creator, config::active_name}, 1}}); + } else { + owner_auth = authority( get_public_key( a, "owner" ) ); + } + + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = owner_auth, + .active = authority( get_public_key( a, "active" ) ) + }); + + trx.actions.emplace_back( get_action( N(eosio), N(buyram), vector{{creator,config::active_name}}, + mvo() + ("payer", creator) + ("receiver", a) + ("quant", ramfunds) ) + ); + + trx.actions.emplace_back( get_action( N(eosio), N(delegatebw), vector{{creator,config::active_name}}, + mvo() + ("from", creator) + ("receiver", a) + ("stake_net_quantity", net ) + ("stake_cpu_quantity", cpu ) + ("transfer", 0 ) + ) + ); + + set_transaction_headers(trx); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + return push_transaction( trx ); + } + + transaction_trace_ptr setup_producer_accounts( const std::vector& accounts ) { + account_name creator(N(eosio)); + signed_transaction trx; + set_transaction_headers(trx); + asset cpu = asset::from_string("80.0000 EOS"); + asset net = asset::from_string("80.0000 EOS"); + asset ram = asset::from_string("1.0000 EOS"); + + for (const auto& a: accounts) { + authority owner_auth( get_public_key( a, "owner" ) ); + trx.actions.emplace_back( vector{{creator,config::active_name}}, + newaccount{ + .creator = creator, + .name = a, + .owner = owner_auth, + .active = authority( get_public_key( a, "active" ) ) + }); + + trx.actions.emplace_back( get_action( N(eosio), N(buyram), vector{ {creator, config::active_name} }, + mvo() + ("payer", creator) + ("receiver", a) + ("quant", ram) ) + ); + + trx.actions.emplace_back( get_action( N(eosio), N(delegatebw), vector{ {creator, config::active_name} }, + mvo() + ("from", creator) + ("receiver", a) + ("stake_net_quantity", net) + ("stake_cpu_quantity", cpu ) + ("transfer", 0 ) + ) + ); + } + + set_transaction_headers(trx); + trx.sign( get_private_key( creator, "active" ), chain_id_type() ); + return push_transaction( trx ); + } + + action_result buyram( const account_name& payer, account_name receiver, string eosin ) { + return push_action( payer, N(buyram), mvo()( "payer",payer)("receiver",receiver)("quant",eosin) ); + } + action_result buyrambytes( const account_name& payer, account_name receiver, uint32_t numbytes ) { + return push_action( payer, N(buyrambytes), mvo()( "payer",payer)("receiver",receiver)("bytes",numbytes) ); + } + + action_result sellram( const account_name& account, uint64_t numbytes ) { + return push_action( account, N(sellram), mvo()( "account", account)("bytes",numbytes) ); + } + + action_result push_action( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) { + string action_type_name = abi_ser.get_action_type(name); + + action act; + act.account = config::system_account_name; + act.name = name; + act.data = abi_ser.variant_to_binary( action_type_name, data ); + + return base_tester::push_action( std::move(act), auth ? uint64_t(signer) : signer == N(bob) ? N(alice) : N(bob) ); + } + + action_result stake( const account_name& from, const account_name& to, const string& net, const string& cpu ) { + return push_action( name(from), N(delegatebw), mvo() + ("from", from) + ("receiver", to) + ("stake_net_quantity", net) + ("stake_cpu_quantity", cpu) + ("transfer", 0 ) + ); + } + + action_result stake( const account_name& acnt, const string& net, const string& cpu ) { + return stake( acnt, acnt, net, cpu ); + } + + action_result unstake( const account_name& from, const account_name& to, const string& net, const string& cpu ) { + return push_action( name(from), N(undelegatebw), mvo() + ("from", from) + ("receiver", to) + ("unstake_net_quantity", net) + ("unstake_cpu_quantity", cpu) + ); + } + + action_result unstake( const account_name& acnt, const string& net, const string& cpu ) { + return unstake( acnt, acnt, net, cpu ); + } + + static fc::variant_object producer_parameters_example( int n ) { + return mutable_variant_object() + ("max_block_net_usage", 10000000 + n ) + ("target_block_net_usage_pct", 10 + n ) + ("max_transaction_net_usage", 1000000 + n ) + ("base_per_transaction_net_usage", 100 + n) + ("net_usage_leeway", 500 + n ) + ("context_free_discount_net_usage_num", 1 + n ) + ("context_free_discount_net_usage_den", 100 + n ) + ("max_block_cpu_usage", 10000000 + n ) + ("target_block_cpu_usage_pct", 10 + n ) + ("max_transaction_cpu_usage", 1000000 + n ) + ("base_per_transaction_cpu_usage", 100 + n) + ("base_per_action_cpu_usage", 100 + n) + ("base_setcode_cpu_usage", 100 + n) + ("per_signature_cpu_usage", 100 + n) + ("cpu_usage_leeway", 2048 + n ) + ("context_free_discount_cpu_usage_num", 1 + n ) + ("context_free_discount_cpu_usage_den", 100 + n ) + ("max_transaction_lifetime", 3600 + n) + ("deferred_trx_expiration_window", 600 + n) + ("max_transaction_delay", 10*86400+n) + ("max_inline_action_size", 4096 + n) + ("max_inline_action_depth", 4 + n) + ("max_authority_depth", 6 + n) + ("max_generated_transaction_count", 10 + n) + ("max_ram_size", (n % 10 + 1) * 1024 * 1024) + ("percent_of_max_inflation_rate", 50 + n) + ("ram_reserve_ratio", 100 + n); + } + + action_result regproducer( const account_name& acnt, int params_fixture = 1 ) { + action_result r = push_action( acnt, N(regproducer), mvo() + ("producer", acnt ) + ("producer_key", get_public_key( acnt, "active" ) ) + ("url", "" ) + ); + BOOST_REQUIRE_EQUAL( success(), r); + return r; + } + + uint32_t last_block_time() const { + return time_point_sec( control->head_block_time() ).sec_since_epoch(); + } + + asset get_balance( const account_name& act ) { + //return get_currency_balance( config::system_account_name, symbol(SY(4,EOS)), act ); + //temporary code. current get_currency_balancy uses table name N(accounts) from currency.h + //generic_currency table name is N(account). + const auto& db = control->db(); + const auto* tbl = db.find(boost::make_tuple(N(eosio.token), act, N(accounts))); + share_type result = 0; + + // the balance is implied to be 0 if either the table or row does not exist + if (tbl) { + const auto *obj = db.find(boost::make_tuple(tbl->id, symbol(SY(4,EOS)).to_symbol_code())); + if (obj) { + // balance is the first field in the serialization + fc::datastream ds(obj->value.data(), obj->value.size()); + fc::raw::unpack(ds, result); + } + } + return asset( result, symbol(SY(4,EOS)) ); + } + + fc::variant get_total_stake( const account_name& act ) { + vector data = get_row_by_account( config::system_account_name, act, N(userres), act ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "user_resources", data ); + } + + fc::variant get_voter_info( const account_name& act ) { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(voters), act ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "voter_info", data ); + } + + fc::variant get_producer_info( const account_name& act ) { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(producers), act ); + return abi_ser.binary_to_variant( "producer_info", data ); + } + + void create_currency( name contract, name manager, asset maxsupply ) { + auto act = mutable_variant_object() + ("issuer", manager ) + ("maximum_supply", maxsupply ) + ("can_freeze", 0) + ("can_recall", 0) + ("can_whitelist", 0); + + base_tester::push_action(contract, N(create), contract, act ); + } + + void issue( name to, const string& amount, name manager = config::system_account_name ) { + base_tester::push_action( N(eosio.token), N(issue), manager, mutable_variant_object() + ("to", to ) + ("quantity", asset::from_string(amount) ) + ("memo", "") + ); + } + void transfer( name from, name to, const string& amount, name manager = config::system_account_name ) { + base_tester::push_action( N(eosio.token), N(transfer), manager, mutable_variant_object() + ("from", from) + ("to", to ) + ("quantity", asset::from_string(amount) ) + ("memo", "") + ); + } + + double stake2votes( asset stake ) { + auto now = control->pending_block_time().time_since_epoch().count() / 1000000; + return stake.amount * pow(2, int64_t(now/ (86400 * 7))/ double(52) ); // 52 week periods (i.e. ~years) + } + + double stake2votes( const string& s ) { + return stake2votes( asset::from_string(s) ); + } + + fc::variant get_stats( const string& symbolname ) { + auto symb = eosio::chain::symbol::from_string(symbolname); + auto symbol_code = symb.to_symbol_code().value; + vector data = get_row_by_account( N(eosio.token), symbol_code, N(stat), symbol_code ); + return data.empty() ? fc::variant() : token_abi_ser.binary_to_variant( "currency_stats", data ); + } + + asset get_token_supply() { + return get_stats("4,EOS")["supply"].as(); + } + + fc::variant get_global_state() { + vector data = get_row_by_account( N(eosio), N(eosio), N(global), N(global) ); + if (data.empty()) std::cout << "\nData is empty\n" << std::endl; + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state", data ); + + } + + abi_serializer abi_ser; + abi_serializer token_abi_ser; +}; + +fc::mutable_variant_object voter( account_name acct ) { + return mutable_variant_object() + ("owner", acct) + ("proxy", name(0).to_string()) + ("producers", variants() ) + ("staked", int64_t(0)) + //("last_vote_weight", double(0)) + ("proxied_vote_weight", double(0)) + ("is_proxy", 0) + ("deferred_trx_id", 0) + ("last_unstake_time", fc::time_point_sec() ) + ("unstaking", asset() ) + ; +} + +fc::mutable_variant_object voter( account_name acct, const string& vote_stake ) { + return voter( acct )( "staked", asset::from_string( vote_stake ).amount ); +} + +fc::mutable_variant_object voter( account_name acct, int64_t vote_stake ) { + return voter( acct )( "staked", vote_stake ); +} + +fc::mutable_variant_object proxy( account_name acct ) { + return voter( acct )( "is_proxy", 1 ); +} + +inline uint64_t M( const string& eos_str ) { + return asset::from_string( eos_str ).amount; +} + +BOOST_AUTO_TEST_SUITE(eosio_system_tests) + +BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { + + BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 EOS"), get_balance( "alice" ) ); + + transfer( "eosio", "alice", "1000.0000 EOS", "eosio" ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio", "alice", "200.0000 EOS", "100.0000 EOS" ) ); + + auto total = get_total_stake( "alice" ); + auto init_bytes = total["ram_bytes"].as_uint64(); + + BOOST_REQUIRE_EQUAL( success(), buyram( "alice", "alice", "200.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("800.0000 EOS"), get_balance( "alice" ) ); + + total = get_total_stake( "alice" ); + auto bytes = total["ram_bytes"].as_uint64(); + auto bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + BOOST_REQUIRE_EQUAL( true, 0 < bought_bytes ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice", bought_bytes ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("999.9999 EOS"), get_balance( "alice" ) ); + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( true, total["ram_bytes"].as_uint64() == init_bytes ); + + transfer( "eosio", "alice", "100000000.0000 EOS", "eosio" ); + BOOST_REQUIRE_EQUAL( asset::from_string("100000999.9999 EOS"), get_balance( "alice" ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice", "alice", "10000000.0000 EOS" ) ); + + total = get_total_stake( "alice" ); + bytes = total["ram_bytes"].as_uint64(); + bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice", bought_bytes ) ); + total = get_total_stake( "alice" ); + + bytes = total["ram_bytes"].as_uint64(); + bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + + BOOST_REQUIRE_EQUAL( true, total["ram_bytes"].as_uint64() == init_bytes ); + BOOST_REQUIRE_EQUAL( asset::from_string("100000999.9993 EOS"), get_balance( "alice" ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_unstake, eosio_system_tester ) try { + //issue( "eosio", "1000.0000 EOS", config::system_account_name ); + + BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 EOS"), get_balance( "alice" ) ); + transfer( "eosio", "alice", "1000.0000 EOS", "eosio" ); + BOOST_REQUIRE_EQUAL( asset::from_string("999999000.0000 EOS"), get_balance( "eosio" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio", "alice", "200.0000 EOS", "100.0000 EOS" ) ); + + auto total = get_total_stake("alice"); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "alice", "200.0000 EOS", "100.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "alice", "200.0000 EOS", "100.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + //after 3 days funds should be released + produce_block( fc::hours(1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "200.0000 EOS", "100.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + total = get_total_stake("bob"); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); + + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS").amount, total["net_weight"].as().amount ); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS").amount, total["cpu_weight"].as().amount ); + + REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS"), get_voter_info( "alice" ) ); + + auto bytes = total["ram_bytes"].as_uint64(); + BOOST_REQUIRE_EQUAL( true, 0 < bytes ); + + //unstake from bob + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "bob", "200.0000 EOS", "100.0000 EOS" ) ); + total = get_total_stake("bob"); + BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("10.0000 EOS"), total["cpu_weight"].as()); + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + //after 3 days funds should be released + produce_block( fc::hours(1) ); + produce_blocks(1); + + REQUIRE_MATCHING_OBJECT( voter( "alice", "0.0000 EOS" ), get_voter_info( "alice" ) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( fail_without_auth, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake( "eosio", "alice", "2000.0000 EOS", "1000.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "10.0000 EOS", "10.0000 EOS" ) ); + + BOOST_REQUIRE_EQUAL( error("missing authority of alice"), + push_action( N(alice), N(delegatebw), mvo() + ("from", "alice") + ("receiver", "bob") + ("stake_net_quantity", "10.0000 EOS") + ("stake_cpu_quantity", "10.0000 EOS") + ("transfer", 0 ) + ,false + ) + ); + + BOOST_REQUIRE_EQUAL( error("missing authority of alice"), + push_action(N(alice), N(undelegatebw), mvo() + ("from", "alice") + ("receiver", "bob") + ("unstake_net_quantity", "200.0000 EOS") + ("unstake_cpu_quantity", "100.0000 EOS") + ("transfer", 0 ) + ,false + ) + ); + //REQUIRE_MATCHING_OBJECT( , get_voter_info( "alice" ) ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( stake_negative, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), + stake( "alice", "-0.0001 EOS", "0.0000 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), + stake( "alice", "0.0000 EOS", "-0.0001 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), + stake( "alice", "00.0000 EOS", "00.0000 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must stake a positive amount"), + stake( "alice", "0.0000 EOS", "00.0000 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( true, get_voter_info( "alice" ).is_null() ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unstake_negative, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "200.0001 EOS", "100.0001 EOS" ) ); + + auto total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0001 EOS"), total["net_weight"].as()); + auto vinfo = get_voter_info("alice" ); + wdump((vinfo)); + REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0002 EOS" ), get_voter_info( "alice" ) ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), + unstake( "alice", "bob", "-1.0000 EOS", "0.0000 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), + unstake( "alice", "bob", "0.0000 EOS", "-1.0000 EOS" ) + ); + + //unstake all zeros + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: must unstake a positive amount"), + unstake( "alice", "bob", "0.0000 EOS", "0.0000 EOS" ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unstake_more_than_at_stake, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "200.0000 EOS", "100.0000 EOS" ) ); + + auto total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); + + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + + //trying to unstake more net bandwith than at stake + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked net bandwidth"), + unstake( "alice", "200.0001 EOS", "0.0000 EOS" ) + ); + + //trying to unstake more cpu bandwith than at stake + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked cpu bandwidth"), + unstake( "alice", "0.0000 EOS", "100.0001 EOS" ) + ); + + //check that nothing has changed + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( delegate_to_another_user, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake ( "alice", "bob", "200.0000 EOS", "100.0000 EOS" ) ); + + auto total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("210.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + //all voting power goes to alice + REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); + //but not to bob + BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob" ).is_null() ); + + //bob should not be able to unstake what was staked by alice + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: unable to find key"), + unstake( "bob", "0.0000 EOS", "10.0000 EOS" ) + ); + + issue( "carol", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "bob", "20.0000 EOS", "10.0000 EOS" ) ); + total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("230.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("970.0000 EOS"), get_balance( "carol" ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol", "30.0000 EOS" ), get_voter_info( "carol" ) ); + + //alice should not be able to unstake money staked by carol + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked net bandwidth"), + unstake( "alice", "bob", "2001.0000 EOS", "1.0000 EOS" ) + ); + + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: insufficient staked cpu bandwidth"), + unstake( "alice", "bob", "1.0000 EOS", "101.0000 EOS" ) + ); + + total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("230.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS"), total["cpu_weight"].as()); + //balance should not change after unsuccessfull attempts to unstake + BOOST_REQUIRE_EQUAL( asset::from_string("700.0000 EOS"), get_balance( "alice" ) ); + //voting power too + REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol", "30.0000 EOS" ), get_voter_info( "carol" ) ); + BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob" ).is_null() ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( stake_unstake_separate, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( asset::from_string("1000.0000 EOS"), get_balance( "alice" ) ); + + //everything at once + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "10.0000 EOS", "20.0000 EOS" ) ); + auto total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["cpu_weight"].as()); + + //cpu + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "100.0000 EOS", "0.0000 EOS" ) ); + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["cpu_weight"].as()); + + //net + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "0.0000 EOS", "200.0000 EOS" ) ); + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("120.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("230.0000 EOS"), total["cpu_weight"].as()); + + //unstake cpu + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "100.0000 EOS", "0.0000 EOS" ) ); + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("230.0000 EOS"), total["cpu_weight"].as()); + + //unstake net + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "0.0000 EOS", "200.0000 EOS" ) ); + total = get_total_stake( "alice" ); + BOOST_REQUIRE_EQUAL( asset::from_string("20.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("30.0000 EOS"), total["cpu_weight"].as()); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( adding_stake_partial_unstake, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "200.0000 EOS", "100.0000 EOS" ) ); + + REQUIRE_MATCHING_OBJECT( voter( "alice", "300.0000 EOS" ), get_voter_info( "alice" ) ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "bob", "100.0000 EOS", "50.0000 EOS" ) ); + + auto total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("310.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("160.0000 EOS"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice", "450.0000 EOS" ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("550.0000 EOS"), get_balance( "alice" ) ); + + //unstake a share + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "bob", "150.0000 EOS", "75.0000 EOS" ) ); + + total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("160.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("85.0000 EOS"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice", "225.0000 EOS" ), get_voter_info( "alice" ) ); + + //unstake more + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "bob", "50.0000 EOS", "25.0000 EOS" ) ); + total = get_total_stake( "bob" ); + BOOST_REQUIRE_EQUAL( asset::from_string("110.0000 EOS"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( asset::from_string("60.0000 EOS"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice", "150.0000 EOS" ), get_voter_info( "alice" ) ); + + //combined amount should be available only in 3 days + produce_block( fc::days(2) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("550.0000 EOS"), get_balance( "alice" ) ); + produce_block( fc::days(1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( asset::from_string("850.0000 EOS"), get_balance( "alice" ) ); + +} FC_LOG_AND_RETHROW() + + +// Tests for voting +BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + + fc::variant params = producer_parameters_example(1); + auto key = fc::crypto::public_key( std::string("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV") ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", key ) + ("url", "http://block.one") + ) + ); + + auto info = get_producer_info( "alice" ); + BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + + //call regproducer again to change parameters + fc::variant params2 = producer_parameters_example(2); + + info = get_producer_info( "alice" ); + BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + + //unregister producer + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(unregprod), mvo() + ("producer", "alice") + ) + ); + info = get_producer_info( "alice" ); + //key should be empty + wdump((info)); + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(info["producer_key"].as_string()) ); + //everything else should stay the same + BOOST_REQUIRE_EQUAL( "alice", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + + //unregister bob who is not a producer + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer not found" ), + push_action( N(bob), N(unregprod), mvo() + ("producer", "bob") + ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_for_producer, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url", "http://block.one") + ) + ); + auto prod = get_producer_info( "alice" ); + BOOST_REQUIRE_EQUAL( "alice", prod["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, prod["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", prod["url"].as_string() ); + + issue( "bob", "2000.0000 EOS", config::system_account_name ); + issue( "carol", "3000.0000 EOS", config::system_account_name ); + + //bob makes stake + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "11.0000 EOS", "0.1111 EOS" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("1988.8889 EOS"), get_balance( "bob" ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "11.1111 EOS" ), get_voter_info( "bob" ) ); + + //bob votes for alice + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + + //check that producer parameters stay the same after voting + prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("11.1111 EOS") == prod["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "alice", prod["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( "http://block.one", prod["url"].as_string() ); + + //carol makes stake + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "22.0000 EOS", "0.2222 EOS" ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol", "22.2222 EOS" ), get_voter_info( "carol" ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("2977.7778 EOS"), get_balance( "carol" ) ); + //carol votes for alice + BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + //new stake votes be added to alice's total_votes + prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("33.3333 EOS") == prod["total_votes"].as_double() ); + + //bob increases his stake + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "55.0000 EOS", "0.5555 EOS" ) ); + //should increase alice's total_votes + prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("88.8888 EOS") == prod["total_votes"].as_double() ); + + //carol unstakes part of the stake + BOOST_REQUIRE_EQUAL( success(), unstake( "carol", "2.0000 EOS", "0.0002 EOS"/*"2.0000 EOS", "0.0002 EOS"*/ ) ); + //should decrease alice's total_votes + prod = get_producer_info( "alice" ); + wdump((prod)); + BOOST_TEST_REQUIRE( stake2votes("86.8886 EOS") == prod["total_votes"].as_double() ); + + //bob revokes his vote + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector() ) + ) + ); + //should decrease alice's total_votes + prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("20.2220 EOS") == prod["total_votes"].as_double() ); + //but eos should still be at stake + BOOST_REQUIRE_EQUAL( asset::from_string("1933.3334 EOS"), get_balance( "bob" ) ); + + //carol unstakes rest of eos + BOOST_REQUIRE_EQUAL( success(), unstake( "carol", "20.0000 EOS", "0.2220 EOS" ) ); + //should decrease alice's total_votes to zero + prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( 0.0 == prod["total_votes"].as_double() ); + + //carol should receive funds in 3 days + produce_block( fc::days(3) ); + produce_block(); + BOOST_REQUIRE_EQUAL( asset::from_string("3000.0000 EOS"), get_balance( "carol" ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unregistered_producer_voting, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + issue( "bob", "2000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS" ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); + + //bob should not be able to vote for alice who is not a producer + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer is not registered" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + + //alice registers as a producer + issue( "alice", "1000.0000 EOS", config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url", "") + ) + ); + //and then unregisters + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unregprod), mvo() + ("producer", "alice") + ) + ); + //key should be empty + auto prod = get_producer_info( "alice" ); + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(prod["producer_key"].as_string()) ); + + //bob should not be able to vote for alice who is an unregistered producer + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer is not currently registered" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( more_than_30_producer_voting, eosio_system_tester ) try { + issue( "bob", "2000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS" ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); + + //bob should not be able to vote for alice who is not a producer + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: attempt to vote for too many producers" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector(31, N(alice)) ) + ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_same_producer_30_times, eosio_system_tester ) try { + issue( "bob", "2000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "50.0000 EOS", "50.0000 EOS" ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "100.0000 EOS" ), get_voter_info( "bob" ) ); + + //alice becomes a producer + issue( "alice", "1000.0000 EOS", config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key(N(alice), "active") ) + ("url", "") + ) + ); + + //bob should not be able to vote for alice who is not a producer + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: producer votes must be unique and sorted" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector(30, N(alice)) ) + ) + ); + + auto prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( 0 == prod["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( producer_keep_votes, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + issue( "alice", "1000.0000 EOS", config::system_account_name ); + fc::variant params = producer_parameters_example(1); + vector key = fc::raw::pack( get_public_key( N(alice), "active" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url", "") + ) + ); + + //bob makes stake + issue( "bob", "2000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "13.0000 EOS", "0.5791 EOS" ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "13.5791 EOS" ), get_voter_info( "bob" ) ); + + //bob votes for alice + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + + auto prod = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("13.5791 EOS") == prod["total_votes"].as_double() ); + + //unregister producer + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(unregprod), mvo() + ("producer", "alice") + ) + ); + prod = get_producer_info( "alice" ); + //key should be empty + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(prod["producer_key"].as_string()) ); + //check parameters just in case + //REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes("13.5791 EOS"), prod["total_votes"].as_double() ); + + //regtister the same producer again + params = producer_parameters_example(2); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url", "") + ) + ); + prod = get_producer_info( "alice" ); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes("13.5791 EOS"), prod["total_votes"].as_double() ); + + //change parameters + params = producer_parameters_example(3); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url","") + ) + ); + prod = get_producer_info( "alice" ); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes("13.5791 EOS"), prod["total_votes"].as_double() ); + //check parameters just in case + //REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_for_two_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + //alice becomes a producer + fc::variant params = producer_parameters_example(1); + auto key = get_public_key( N(alice), "active" ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproducer), mvo() + ("producer", "alice") + ("producer_key", get_public_key( N(alice), "active") ) + ("url","") + ) + ); + //bob becomes a producer + params = producer_parameters_example(2); + key = get_public_key( N(bob), "active" ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproducer), mvo() + ("producer", "bob") + ("producer_key", get_public_key( N(alice), "active") ) + ("url","") + ) + ); + + //carol votes for alice and bob + issue( "carol", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "15.0005 EOS", "5.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice), N(bob) } ) + ) + ); + + auto alice_info = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("20.0005 EOS") == alice_info["total_votes"].as_double() ); + auto bob_info = get_producer_info( "bob" ); + BOOST_TEST_REQUIRE( stake2votes("20.0005 EOS") == bob_info["total_votes"].as_double() ); + + //carol votes for alice (but revokes vote for bob) + BOOST_REQUIRE_EQUAL( success(), push_action(N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice) } ) + ) + ); + + alice_info = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("20.0005 EOS") == alice_info["total_votes"].as_double() ); + bob_info = get_producer_info( "bob" ); + BOOST_TEST_REQUIRE( 0 == bob_info["total_votes"].as_double() ); + + //alice votes for herself and bob + issue( "alice", "2.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "1.0000 EOS", "1.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(alice), N(bob) } ) + ) + ); + + alice_info = get_producer_info( "alice" ); + BOOST_TEST_REQUIRE( stake2votes("22.0005 EOS") == alice_info["total_votes"].as_double() ); + + bob_info = get_producer_info( "bob" ); + BOOST_TEST_REQUIRE( stake2votes("2.0000 EOS") == bob_info["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_register_unregister_keeps_stake, eosio_system_tester ) try { + //register proxy by first action for this user ever + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true ) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); + + //unregister proxy + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice" ), get_voter_info( "alice" ) ); + + //stake and then register as a proxy + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "200.0002 EOS", "100.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproxy), mvo() + ("proxy", "bob") + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "bob" )( "staked", 3000003 ), get_voter_info( "bob" ) ); + //unrgister and check that stake is still in place + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproxy), mvo() + ("proxy", "bob") + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "bob", "300.0003 EOS" ), get_voter_info( "bob" ) ); + + //register as a proxy and then stake + BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(regproxy), mvo() + ("proxy", "carol") + ("isproxy", true) + ) + ); + issue( "carol", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "246.0002 EOS", "531.0001 EOS" ) ); + //check that both proxy flag and stake a correct + REQUIRE_MATCHING_OBJECT( proxy( "carol" )( "staked", 7770003 ), get_voter_info( "carol" ) ); + + //unregister + BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(regproxy), mvo() + ("proxy", "carol") + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "carol", "777.0003 EOS" ), get_voter_info( "carol" ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_stake_unstake_keeps_proxy_flag, eosio_system_tester ) try { + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true) + ) + ); + issue( "alice", "1000.0000 EOS", config::system_account_name ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); + + //stake + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "100.0000 EOS", "50.0000 EOS" ) ); + //check that account is still a proxy + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", 1500000 ), get_voter_info( "alice" ) ); + + //stake more + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0000 EOS", "20.0000 EOS" ) ); + //check that account is still a proxy + REQUIRE_MATCHING_OBJECT( proxy( "alice" )("staked", 2000000 ), get_voter_info( "alice" ) ); + + //unstake more + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "65.0000 EOS", "35.0000 EOS" ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )("staked", 1000000 ), get_voter_info( "alice" ) ); + + //unstake the rest + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "65.0000 EOS", "35.0000 EOS" ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", 0 ), get_voter_info( "alice" ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_actions_affect_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + create_accounts_with_resources( { N(producer1), N(producer2), N(producer3) } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); + + //register as a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true) + ) + ); + + //accumulate proxied votes + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_vote_weight", stake2votes("150.0003 EOS") ), get_voter_info( "alice" ) ); + + //vote for producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer2) } ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_producer_info( "producer3" )["total_votes"].as_double() ); + + //vote for another producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer3) } ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer3" )["total_votes"].as_double() ); + + //unregister proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", false) + ) + ); + //REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_vote_weight", stake2votes("150.0003 EOS") ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + //register proxy again + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_producer_info( "producer3" )["total_votes"].as_double() ); + + //stake increase by proxy itself affects producers + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( stake2votes("200.0005 EOS"), get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( stake2votes("200.0005 EOS"), get_producer_info( "producer3" )["total_votes"].as_double() ); + + //stake decrease by proxy itself affects producers + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "10.0001 EOS", "10.0001 EOS" ) ); + BOOST_TEST_REQUIRE( stake2votes("180.0003 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("180.0003 EOS") == get_producer_info( "producer3" )["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + const asset large_asset = asset::from_string("80.0000 EOS"); + create_account_with_resources( N(inita), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + create_account_with_resources( N(initb), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + + create_account_with_resources( N(vota), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + create_account_with_resources( N(votb), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + + BOOST_REQUIRE_EQUAL(success(), regproducer(N(inita))); + auto prod = get_producer_info( N(inita) ); + BOOST_REQUIRE_EQUAL("inita", prod["owner"].as_string()); + BOOST_REQUIRE_EQUAL(0, prod["total_votes"].as_double()); + + issue( "vota", "400000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("vota", "100000000.0000 EOS", "100000000.0000 EOS")); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(vota), N(voteproducer), mvo() + ("voter", "vota") + ("proxy", name(0).to_string()) + ("producers", vector{ N(inita) }) + ) + ); + + // inita is the only active producer + // produce enough blocks so new schedule kicks in and inita produces some blocks + { + produce_blocks(50); + + const auto initial_global_state = get_global_state(); + const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64(); + const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as(); + const asset initial_savings = initial_global_state["savings"].as(); + + prod = get_producer_info("inita"); + const uint32_t produced_blocks = prod["produced_blocks"].as(); + BOOST_REQUIRE(1 < produced_blocks); + BOOST_REQUIRE_EQUAL(0, prod["last_claim_time"].as()); + const asset initial_supply = get_token_supply(); + const asset initial_balance = get_balance(N(inita)); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(inita), N(claimrewards), mvo()("owner", "inita"))); + + const auto global_state = get_global_state(); + const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64(); + const asset pervote_bucket = global_state["pervote_bucket"].as(); + const asset savings = global_state["savings"].as(); + prod = get_producer_info("inita"); + BOOST_REQUIRE_EQUAL(1, prod["produced_blocks"].as()); + const asset supply = get_token_supply(); + const asset balance = get_balance(N(inita)); + + BOOST_REQUIRE_EQUAL(claim_time, prod["last_claim_time"].as()); + const int32_t secs_between_fills = static_cast((claim_time - initial_claim_time) / 1000000); + + BOOST_REQUIRE_EQUAL(0, initial_pervote_bucket.amount); + + BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * ((4.879-1.0)/100.0)) / (52*7*24*3600) ), + savings.amount - initial_savings.amount); + + int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * (0.25/100.0) / (52*7*24*3600*2) ); + int64_t from_pervote_bucket = int64_t( initial_supply.amount * secs_between_fills * (0.75/100.0) / (52*7*24*3600) ); + + if (from_pervote_bucket >= 100 * 10000) { + BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(0, pervote_bucket.amount); + } else { + BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(from_pervote_bucket, pervote_bucket.amount); + } + + const int64_t max_supply_growth = int64_t( (initial_supply.amount * secs_between_fills * (4.879/100.0)) / (52*7*24*3600) ); + BOOST_REQUIRE(max_supply_growth >= supply.amount - initial_supply.amount); + } + + { + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(N(inita), N(claimrewards), mvo()("owner", "inita"))); + } + + // inita waits for 23 hours and 55 minutes, can't claim rewards yet + { + produce_block(fc::seconds(23 * 3600 + 55 * 60)); + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(N(inita), N(claimrewards), mvo()("owner", "inita"))); + } + + // wait 5 more minutes, inita can now claim rewards again + { + produce_block(fc::seconds(5 * 60)); + const auto initial_global_state = get_global_state(); + const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64(); + const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as(); + const asset initial_savings = initial_global_state["savings"].as(); + + prod = get_producer_info("inita"); + const uint32_t produced_blocks = prod["produced_blocks"].as(); + BOOST_REQUIRE(1 < produced_blocks); + BOOST_REQUIRE(0 < prod["last_claim_time"].as()); + const asset initial_supply = get_token_supply(); + const asset initial_balance = get_balance(N(inita)); + + BOOST_REQUIRE_EQUAL(success(), + push_action(N(inita), N(claimrewards), mvo()("owner", "inita"))); + + const auto global_state = get_global_state(); + const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64(); + const asset pervote_bucket = global_state["pervote_bucket"].as(); + const asset savings = global_state["savings"].as(); + + prod = get_producer_info("inita"); + BOOST_REQUIRE_EQUAL(1, prod["produced_blocks"].as()); + const asset supply = get_token_supply(); + const asset balance = get_balance(N(inita)); + + BOOST_REQUIRE_EQUAL(claim_time, prod["last_claim_time"].as()); + const int32_t secs_between_fills = static_cast((claim_time - initial_claim_time) / 1000000); + + BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * ((4.879-1.0)/100.0)) / (52*7*24*3600) ), + savings.amount - initial_savings.amount); + + int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * (0.25/100.0) / (52*7*24*3600*2) ); + int64_t from_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * (0.75/100.0) / (52*7*24*3600) ); + + if (from_pervote_bucket >= 100 * 10000) { + BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(0, pervote_bucket.amount); + } else { + BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(from_pervote_bucket, pervote_bucket.amount); + } + + const int64_t max_supply_growth = int64_t( (initial_supply.amount * secs_between_fills * (4.879/100.0)) / (52*7*24*3600) ); + BOOST_REQUIRE(max_supply_growth >= supply.amount - initial_supply.amount); + } + + // initb tries to claim rewards but he's not on the list + { + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: account name is not in producer list"), + push_action(N(initb), N(claimrewards), mvo()("owner", "initb"))); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(multiple_producer_pay, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + + const int64_t secs_per_year = 52 * 7 * 24 * 3600; + const int64_t blocks_per_year = 52 * 7 * 24 * 3600 * 2; + + const double cont_rate = 4.879/100.; + const double standby_rate = 0.750/100.; + const double block_rate = 0.250/100.; + + const asset net = asset::from_string("80.0000 EOS"); + const asset cpu = asset::from_string("80.0000 EOS"); + create_account_with_resources( N(vota), N(eosio), asset::from_string("1.0000 EOS"), false, net, cpu ); + create_account_with_resources( N(votb), N(eosio), asset::from_string("1.0000 EOS"), false, net, cpu ); + create_account_with_resources( N(votc), N(eosio), asset::from_string("1.0000 EOS"), false, net, cpu ); + + // create accounts {inita, initb, ..., initz} and register as producers + std::vector producer_names; + { + producer_names.reserve('z' - 'a' + 1); + const std::string root("init"); + for ( char c = 'a'; c <= 'z'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + setup_producer_accounts(producer_names); + for (const auto& p: producer_names) { + + BOOST_REQUIRE_EQUAL( success(), regproducer(p) ); + produce_blocks(10); + ilog( "------ get pro----------" ); + wdump((p)); + BOOST_TEST(0 == get_producer_info(p)["total_votes"].as()); + } + + { + ilog( "------ get initz ----------" ); + auto inita_info = get_producer_info( N(inita) ); + wdump((inita_info)); + BOOST_REQUIRE_EQUAL(0, inita_info["total_votes"].as()); + + + ilog( "------ get initz ----------" ); + auto initz_info = get_producer_info( N(initz) ); + wdump((initz_info)); + BOOST_REQUIRE_EQUAL(0, initz_info["total_votes"].as()); + } + } + + { + transfer( config::system_account_name, "vota", "100000000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL(success(), stake("vota", "30000000.0000 EOS", "30000000.0000 EOS") ); + transfer( config::system_account_name, "votb", "100000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("votb", "30000000.0000 EOS", "30000000.0000 EOS") ); + transfer( config::system_account_name, "votc", "100000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("votc", "30000000.0000 EOS", "30000000.0000 EOS") ); + } + + // vota votes for inita ... initj + // votb votes for inita ... initu + // votc votes for inita ... initz + { + BOOST_REQUIRE_EQUAL(success(), push_action(N(vota), N(voteproducer), mvo() + ("voter", "vota") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.begin()+10)) + ) + ); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(votb), N(voteproducer), mvo() + ("voter", "votb") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.begin()+21)) + ) + ); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(votc), N(voteproducer), mvo() + ("voter", "votc") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.end())) + ) + ); + + } + + { + auto proda = get_producer_info( N(inita) ); + auto prodj = get_producer_info( N(initj) ); + auto prodk = get_producer_info( N(initk) ); + auto produ = get_producer_info( N(initu) ); + auto prodv = get_producer_info( N(initv) ); + auto prodz = get_producer_info( N(initz) ); + + BOOST_REQUIRE (0 == proda["produced_blocks"].as() && 0 == prodz["produced_blocks"].as()); + BOOST_REQUIRE (0 == proda["last_claim_time"].as() && 0 == prodz["last_claim_time"].as()); + + // check vote ratios + BOOST_REQUIRE ( 0 < proda["total_votes"].as() && 0 < prodz["total_votes"].as() ); + BOOST_TEST( proda["total_votes"].as() == prodj["total_votes"].as() ); + BOOST_TEST( prodk["total_votes"].as() == produ["total_votes"].as() ); + BOOST_TEST( prodv["total_votes"].as() == prodz["total_votes"].as() ); + BOOST_TEST( 2 * proda["total_votes"].as() == 3 * produ["total_votes"].as() ); + BOOST_TEST( proda["total_votes"].as() == 3 * prodz["total_votes"].as() ); + } + + // give a chance for everyone to produce blocks + { + produce_blocks(21 * 12 + 20); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["produced_blocks"].as()) { + all_21_produced = false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["produced_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE(all_21_produced && rest_didnt_produce); + } + + std::vector vote_shares(producer_names.size()); + { + double total_votes = 0; + for (uint32_t i = 0; i < producer_names.size(); ++i) { + vote_shares[i] = get_producer_info(producer_names[i])["total_votes"].as(); + total_votes += vote_shares[i]; + } + std::for_each(vote_shares.begin(), vote_shares.end(), [total_votes](double& x) { x /= total_votes; }); + + BOOST_TEST(double(1) == std::accumulate(vote_shares.begin(), vote_shares.end(), double(0))); + BOOST_TEST(double(3./57.) == vote_shares.front()); + BOOST_TEST(double(1./57.) == vote_shares.back()); + } + + { + const uint32_t prod_index = 2; + const auto prod_name = producer_names[prod_index]; + const auto produced_blocks = get_producer_info(prod_name)["produced_blocks"].as(); + + const auto initial_global_state = get_global_state(); + const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64(); + const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as(); + const asset initial_savings = initial_global_state["savings"].as(); + const asset initial_supply = get_token_supply(); + const asset initial_balance = get_balance(prod_name); + + BOOST_REQUIRE_EQUAL(success(), push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + + const auto global_state = get_global_state(); + const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64(); + const asset pervote_bucket = global_state["pervote_bucket"].as(); + const asset savings = global_state["savings"].as(); + const asset supply = get_token_supply(); + const asset balance = get_balance(prod_name); + + const int32_t secs_between_fills = static_cast((claim_time - initial_claim_time) / 1000000); + + BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * (cont_rate - standby_rate - block_rate)) / secs_per_year ), + savings.amount - initial_savings.amount); + + int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * block_rate / blocks_per_year ); + int64_t expected_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * standby_rate / secs_per_year ); + int64_t from_pervote_bucket = int64_t( vote_shares[prod_index] * expected_pervote_bucket ); + + if (from_pervote_bucket >= 100 * 10000) { + BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(expected_pervote_bucket - from_pervote_bucket, pervote_bucket.amount); + } else { + BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(expected_pervote_bucket, pervote_bucket.amount); + } + + produce_blocks(5); + + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + } + + { + const uint32_t prod_index = 23; + const auto prod_name = producer_names[prod_index]; + const uint64_t initial_claim_time = get_global_state()["last_pervote_bucket_fill"].as_uint64(); + const asset initial_supply = get_token_supply(); + BOOST_REQUIRE_EQUAL(success(), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + BOOST_REQUIRE_EQUAL(0, get_balance(prod_name).amount); + BOOST_REQUIRE_EQUAL(initial_claim_time, get_global_state()["last_pervote_bucket_fill"].as_uint64()); + BOOST_REQUIRE_EQUAL(initial_supply, get_token_supply()); + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + } + + // wait to 23 hours which is not enough for producers to get deactivated + // payment calculations don't change. By now, pervote_bucket has grown enough + // that a producer's share is more than 100 tokens + produce_block(fc::seconds(23 * 3600)); + + { + const uint32_t prod_index = 15; + const auto prod_name = producer_names[prod_index]; + const auto produced_blocks = get_producer_info(prod_name)["produced_blocks"].as(); + + const auto initial_global_state = get_global_state(); + const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64(); + const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as(); + const asset initial_savings = initial_global_state["savings"].as(); + const asset initial_supply = get_token_supply(); + const asset initial_balance = get_balance(prod_name); + + BOOST_REQUIRE_EQUAL(success(), push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + + const auto global_state = get_global_state(); + const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64(); + const asset pervote_bucket = global_state["pervote_bucket"].as(); + const asset savings = global_state["savings"].as(); + const asset supply = get_token_supply(); + const asset balance = get_balance(prod_name); + + const int32_t secs_between_fills = static_cast((claim_time - initial_claim_time) / 1000000); + + BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * (cont_rate - standby_rate - block_rate)) / secs_per_year ), + savings.amount - initial_savings.amount); + + int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * block_rate / blocks_per_year ); + int64_t expected_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * standby_rate / secs_per_year ); + int64_t from_pervote_bucket = int64_t( vote_shares[prod_index] * expected_pervote_bucket ); + + BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount); + BOOST_REQUIRE_EQUAL(expected_pervote_bucket - from_pervote_bucket, pervote_bucket.amount); + + produce_blocks(5); + + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + } + + { + const uint32_t prod_index = 24; + const auto prod_name = producer_names[prod_index]; + BOOST_REQUIRE_EQUAL(success(), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + BOOST_REQUIRE(100 * 10000 <= get_balance(prod_name).amount); + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"), + push_action(prod_name, N(claimrewards), mvo()("owner", prod_name))); + } + + // wait two more hours, now most producers haven't produced in a day and will + // be deactivated + produce_block(fc::seconds(2 * 3600)); + + produce_blocks(8 * 21 * 12); + + { + bool all_newly_elected_produced = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 == get_producer_info(producer_names[i])["produced_blocks"].as()) { + all_newly_elected_produced = false; + } + } + BOOST_REQUIRE(all_newly_elected_produced); + } + + { + uint32_t survived_active_producers = 0; + uint32_t one_inactive_index = 0; + for (uint32_t i = 0; i < 21; ++i) { + if (fc::crypto::public_key() != fc::crypto::public_key(get_producer_info(producer_names[i])["producer_key"].as_string())) { + ++survived_active_producers; + } else { + one_inactive_index = i; + } + } + + BOOST_REQUIRE(3 >= survived_active_producers); + + auto inactive_prod_info = get_producer_info(producer_names[one_inactive_index]); + BOOST_REQUIRE_EQUAL(0, inactive_prod_info["time_became_active"].as()); + BOOST_REQUIRE_EQUAL(error("condition: assertion failed: producer does not have an active key"), + push_action(producer_names[one_inactive_index], N(claimrewards), mvo()("owner", producer_names[one_inactive_index]))); + } + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(producer_onblock_check, eosio_system_tester) try { + + const auto tol = boost::test_tools::tolerance(0.0000000001); + + const asset large_asset = asset::from_string("100000000.0000 EOS"); + create_account_with_resources( N(vota), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + create_account_with_resources( N(votb), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + create_account_with_resources( N(votc), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset ); + + // create accounts {inita, initb, ..., initz} and register as producers + std::vector producer_names={N(inita),N(initb),N(initc),N(initd),N(inite),N(initf),N(initg),N(inith), + N(initi),N(initj),N(initk),N(initl),N(initm),N(initn),N(inito),N(initp),N(initq),N(initr),N(inits),N(initt), + N(initu),N(initv),N(initw),N(initx),N(inity),N(initz)}; + setup_producer_accounts(producer_names); + + for (auto a:producer_names) + regproducer(a); + + BOOST_REQUIRE_EQUAL(0, get_producer_info( N(inita) )["total_votes"].as()); + BOOST_REQUIRE_EQUAL(0, get_producer_info( N(initz) )["total_votes"].as()); + + issue( "vota", "100000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("vota", "30000000.0000 EOS", "30000000.0000 EOS")); + BOOST_REQUIRE_EQUAL(success(), push_action(N(vota), N(voteproducer), mvo() + ("voter", "vota") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.begin()+10)) + )); + + // give a chance for everyone to produce blocks + { + produce_blocks(21 * 12); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["produced_blocks"].as()) { + all_21_produced= false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["produced_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE_EQUAL(false, all_21_produced); + BOOST_REQUIRE_EQUAL(true, rest_didnt_produce); + } + + // issue across 15% boundary + issue( "votb", "100000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("votb", "30000000.0000 EOS", "30000000.0000 EOS")); + issue( "votc", "100000000.0000 EOS", config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("votc", "30000000.0000 EOS", "30000000.0000 EOS")); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(votb), N(voteproducer), mvo() + ("voter", "votb") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.begin()+21)) + ) + ); + + BOOST_REQUIRE_EQUAL(success(), push_action(N(votc), N(voteproducer), mvo() + ("voter", "votc") + ("proxy", name(0).to_string()) + ("producers", vector(producer_names.begin(), producer_names.end())) + ) + ); + + // give a chance for everyone to produce blocks + { + produce_blocks(21 * 12); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["produced_blocks"].as()) { + all_21_produced= false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["produced_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE_EQUAL(true, all_21_produced); + BOOST_REQUIRE_EQUAL(true, rest_didnt_produce); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( voters_actions_affect_proxy_and_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+6) ) try { + create_accounts_with_resources( { N(donald), N(producer1), N(producer2), N(producer3) } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); + + //alice becomes a producer + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); + + //alice makes stake and votes + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer2) } ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("50.0002 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("50.0002 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + BOOST_REQUIRE_EQUAL( success(), push_action( N(donald), N(regproxy), mvo() + ("proxy", "donald") + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "donald" ), get_voter_info( "donald" ) ); + + //bob chooses alice as a proxy + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("150.0003 EOS") == get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("200.0005 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("200.0005 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + //carol chooses alice as a proxy + issue( "carol", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "30.0001 EOS", "20.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("200.0005 EOS") == get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("250.0007 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("250.0007 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + + //proxied voter carol increases stake + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "50.0000 EOS", "70.0000 EOS" ) ); + BOOST_TEST_REQUIRE( stake2votes("320.0005 EOS") == get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("370.0007 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("370.0007 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + //proxied voter bob decreases stake + BOOST_REQUIRE_EQUAL( success(), unstake( "bob", "50.0001 EOS", "50.0001 EOS" ) ); + BOOST_TEST_REQUIRE( stake2votes("220.0003 EOS") == get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("270.0005 EOS") == get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("270.0005 EOS") == get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + //proxied voter carol chooses another proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", "donald" ) + ("producers", vector() ) + ) + ); + BOOST_TEST_REQUIRE( stake2votes("50.0001 EOS"), get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("170.0002 EOS"), get_voter_info( "donald" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("100.0003 EOS"), get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("100.0003 EOS"), get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_double() ); + + //bob switches to direct voting and votes for one of the same producers, but not for another one + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "") + ("producers", vector{ N(producer2) } ) + ) + ); + BOOST_TEST_REQUIRE( 0.0 == get_voter_info( "alice" )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("50.0002 EOS"), get_producer_info( "producer1" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes("100.0003 EOS"), get_producer_info( "producer2" )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( 0.0 == get_producer_info( "producer3" )["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_both_proxy_and_producers, eosio_system_tester ) try { + //alice becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); + + //carol becomes a producer + BOOST_REQUIRE_EQUAL( success(), regproducer( "carol", 1) ); + + //bob chooses alice as a proxy + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( error("condition: assertion failed: cannot vote for producers and proxy at same time"), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector{ N(carol) } ) + ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( select_invalid_proxy, eosio_system_tester ) try { + //accumulate proxied votes + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + + //selecting account not registered as a proxy + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid proxy specified" ), + push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + + //selecting not existing account as a proxy + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid proxy specified" ), + push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "notexist" ) + ("producers", vector() ) + ) + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( double_register_unregister_proxy_keeps_votes, eosio_system_tester ) try { + //alice becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", 1) + ) + ); + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "5.0000 EOS", "5.0000 EOS" ) ); + edump((get_voter_info("alice"))); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "staked", 100000 ), get_voter_info( "alice" ) ); + + //bob stakes and selects alice as a proxy + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_vote_weight", stake2votes( "150.0003 EOS" ))( "staked", 100000 ), get_voter_info( "alice" ) ); + + //double regestering should fail without affecting total votes and stake + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: action has no effect" ), + push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", 1) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_vote_weight", stake2votes("150.0003 EOS") )( "staked", 100000 ), get_voter_info( "alice" ) ); + + //uregister + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", 0) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_vote_weight", stake2votes("150.0003 EOS") )( "staked", 100000 ), get_voter_info( "alice" ) ); + + //double unregistering should not affect proxied_votes and stake + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: action has no effect" ), + push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", 0) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_vote_weight", stake2votes("150.0003 EOS"))( "staked", 100000 ), get_voter_info( "alice" ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_cannot_use_another_proxy, eosio_system_tester ) try { + //alice becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ("isproxy", 1) + ) + ); + + //bob becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(regproxy), mvo() + ("proxy", "bob") + ("isproxy", 1) + ) + ); + //proxy should not be able to use a proxy + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account registered as a proxy is not allowed to use a proxy" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + + //voter that uses a proxy should not be allowed to become a proxy + issue( "carol", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "100.0002 EOS", "50.0001 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() + ("voter", "carol") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: account that uses a proxy is not allowed to become a proxy" ), + push_action( N(carol), N(regproxy), mvo() + ("proxy", "carol") + ("isproxy", 1) + ) + ); + + //proxy should not be able to use itself as a proxy + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: cannot proxy to self" ), + push_action( N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "bob" ) + ("producers", vector() ) + ) + ); + +} FC_LOG_AND_RETHROW() + +fc::mutable_variant_object config_to_variant( const eosio::chain::chain_config& config ) { + return mutable_variant_object() + ( "max_block_net_usage", config.max_block_net_usage ) + ( "target_block_net_usage_pct", config.target_block_net_usage_pct ) + ( "max_transaction_net_usage", config.max_transaction_net_usage ) + ( "base_per_transaction_net_usage", config.base_per_transaction_net_usage ) + ( "context_free_discount_net_usage_num", config.context_free_discount_net_usage_num ) + ( "context_free_discount_net_usage_den", config.context_free_discount_net_usage_den ) + ( "max_block_cpu_usage", config.max_block_cpu_usage ) + ( "target_block_cpu_usage_pct", config.target_block_cpu_usage_pct ) + ( "max_transaction_cpu_usage", config.max_transaction_cpu_usage ) + ( "base_per_transaction_cpu_usage", config.base_per_transaction_cpu_usage ) + ( "base_per_action_cpu_usage", config.base_per_action_cpu_usage ) + ( "base_setcode_cpu_usage", config.base_setcode_cpu_usage ) + ( "per_signature_cpu_usage", config.per_signature_cpu_usage ) + ( "context_free_discount_cpu_usage_num", config.context_free_discount_cpu_usage_num ) + ( "context_free_discount_cpu_usage_den", config.context_free_discount_cpu_usage_den ) + ( "max_transaction_lifetime", config.max_transaction_lifetime ) + ( "deferred_trx_expiration_window", config.deferred_trx_expiration_window ) + ( "max_transaction_delay", config.max_transaction_delay ) + ( "max_inline_action_size", config.max_inline_action_size ) + ( "max_inline_action_depth", config.max_inline_action_depth ) + ( "max_authority_depth", config.max_authority_depth ) + ( "max_generated_transaction_count", config.max_generated_transaction_count ); +} + +BOOST_FIXTURE_TEST_CASE( elect_producers /*_and_parameters*/, eosio_system_tester ) try { + create_accounts_with_resources( { N(producer1), N(producer2), N(producer3) } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); + + //stake more than 15% of total EOS supply to activate chain + transfer( "eosio", "alice", "600000000.0000 EOS", "eosio" ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "alice", "300000000.0000 EOS", "300000000.0000 EOS" ) ); + // 1000000000.0000 + //vote for producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1) } ) + ) + ); + produce_blocks(250); + auto producer_keys = control->head_block_state()->active_schedule.producers; + BOOST_REQUIRE_EQUAL( 1, producer_keys.size() ); + BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[0].producer_name ); + + //auto config = config_to_variant( control->get_global_properties().configuration ); + //auto prod1_config = testing::filter_fields( config, producer_parameters_example( 1 ) ); + //REQUIRE_EQUAL_OBJECTS(prod1_config, config); + + // elect 2 producers + issue( "bob", "80000.0000 EOS", config::system_account_name ); + ilog("stake"); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "40000.0000 EOS", "40000.0000 EOS" ) ); + ilog("start vote"); + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer2) } ) + ) + ); + ilog("."); + produce_blocks(250); + producer_keys = control->head_block_state()->active_schedule.producers; + BOOST_REQUIRE_EQUAL( 2, producer_keys.size() ); + BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("producer2"), producer_keys[1].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //auto prod2_config = testing::filter_fields( config, producer_parameters_example( 2 ) ); + //REQUIRE_EQUAL_OBJECTS(prod2_config, config); + + // elect 3 producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer2), N(producer3) } ) + ) + ); + produce_blocks(250); + producer_keys = control->head_block_state()->active_schedule.producers; + BOOST_REQUIRE_EQUAL( 3, producer_keys.size() ); + BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("producer2"), producer_keys[1].producer_name ); + BOOST_REQUIRE_EQUAL( name("producer3"), producer_keys[2].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //REQUIRE_EQUAL_OBJECTS(prod2_config, config); + + //back to 2 producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer3) } ) + ) + ); + produce_blocks(250); + producer_keys = control->head_block_state()->active_schedule.producers; + BOOST_REQUIRE_EQUAL( 2, producer_keys.size() ); + BOOST_REQUIRE_EQUAL( name("producer1"), producer_keys[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("producer3"), producer_keys[1].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //auto prod3_config = testing::filter_fields( config, producer_parameters_example( 3 ) ); + //REQUIRE_EQUAL_OBJECTS(prod3_config, config); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/eosio.token_tests.cpp b/unittests/eosio.token_tests.cpp similarity index 78% rename from tests/wasm_tests/eosio.token_tests.cpp rename to unittests/eosio.token_tests.cpp index 6034579de1a..e44f28aebbb 100644 --- a/tests/wasm_tests/eosio.token_tests.cpp +++ b/unittests/eosio.token_tests.cpp @@ -1,7 +1,6 @@ #include #include -#include -#include +#include #include #include @@ -13,8 +12,6 @@ using namespace eosio::testing; using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; -using namespace eosio::chain_apis; using namespace eosio::testing; using namespace fc; using namespace std; @@ -35,7 +32,7 @@ class eosio_token_tester : public tester { produce_blocks(); - const auto& accnt = control->get_database().get( N(eosio.token) ); + const auto& accnt = control->db().get( N(eosio.token) ); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_ser.set_abi(abi); @@ -51,7 +48,7 @@ class eosio_token_tester : public tester { return base_tester::push_action( std::move(act), uint64_t(signer)); } - + fc::variant get_stats( const string& symbolname ) { auto symb = eosio::chain::symbol::from_string(symbolname); @@ -71,7 +68,7 @@ class eosio_token_tester : public tester { action_result create( account_name issuer, asset maximum_supply, uint8_t issuer_can_freeze, - uint8_t issuer_can_recall, + uint8_t issuer_can_recall, uint8_t issuer_can_whitelist ) { return push_action( N(eosio.token), N(create), mvo() @@ -91,7 +88,7 @@ class eosio_token_tester : public tester { ); } - action_result transfer( account_name from, + action_result transfer( account_name from, account_name to, asset quantity, string memo ) { @@ -128,12 +125,34 @@ BOOST_FIXTURE_TEST_CASE( create_tests, eosio_token_tester ) try { BOOST_FIXTURE_TEST_CASE( create_negative_max_supply, eosio_token_tester ) try { - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: max-supply must be positive" ), + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: max-supply must be positive" ), create( N(alice), asset::from_string("-1000.000 TKN"), false, false, false) ); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( symbol_already_exists, eosio_token_tester ) try { + + auto token = create( N(alice), asset::from_string("100 TKN"), true, false, false); + auto stats = get_stats("0,TKN"); + REQUIRE_MATCHING_OBJECT( stats, mvo() + ("supply", "0 TKN") + ("max_supply", "100 TKN") + ("issuer", "alice") + ("can_freeze",1) + ("can_recall",0) + ("can_whitelist",0) + ("is_frozen",false) + ("enforce_whitelist",false) + ); + produce_blocks(1); + + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: token with symbol already exists" ), + create( N(alice), asset::from_string("100 TKN"), true, false, false) + ); + +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE( create_max_supply, eosio_token_tester ) try { auto token = create( N(alice), asset::from_string("4611686018427387903 TKN"), true, false, false); @@ -150,9 +169,13 @@ BOOST_FIXTURE_TEST_CASE( create_max_supply, eosio_token_tester ) try { ); produce_blocks(1); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid supply" ), - create( N(alice), asset::from_string("4611686018427387904 TKN"), true, false, false) - ); + asset max(10, symbol(SY(0, NKT))); + max.amount = 4611686018427387904; + + BOOST_CHECK_EXCEPTION( create( N(alice), max, true, false, false) , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + } FC_LOG_AND_RETHROW() @@ -172,15 +195,13 @@ BOOST_FIXTURE_TEST_CASE( create_max_decimals, eosio_token_tester ) try { ); produce_blocks(1); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid supply" ), - create( N(alice), asset::from_string("4.611686018427387904 TKN"), true, false, false) - ); - + asset max(10, symbol(SY(0, NKT))); //1.0000000000000000000 => 0x8ac7230489e80000L - //TODO: Better error message - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: max-supply must be positive" ), - create( N(alice), asset::from_string("1.0000000000000000000 TKN"), true, false, false) - ); + max.amount = 0x8ac7230489e80000L; + + BOOST_CHECK_EXCEPTION( create( N(alice), max, true, false, false) , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); } FC_LOG_AND_RETHROW() @@ -210,25 +231,18 @@ BOOST_FIXTURE_TEST_CASE( issue_tests, eosio_token_tester ) try { ("whitelist", 1) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: quantity exceeds available supply" ), + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: quantity exceeds available supply" ), issue( N(alice), N(alice), asset::from_string("500.001 TKN"), "hola" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid quantity" ), - issue( N(alice), N(alice), asset::from_string("4611686018427387.904 TKN"), "hola" ) - ); - - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: must issue positive quantity" ), + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: must issue positive quantity" ), issue( N(alice), N(alice), asset::from_string("-1.000 TKN"), "hola" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: comparison of assets with different symbols is not allowed" ), - issue( N(alice), N(alice), asset::from_string("1.00 TKN"), "hola" ) + BOOST_REQUIRE_EQUAL( success(), + issue( N(alice), N(alice), asset::from_string("1.000 TKN"), "hola" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: comparison of assets with different symbols is not allowed" ), - issue( N(alice), N(alice), asset::from_string("1.0000 TKN"), "hola" ) - ); } FC_LOG_AND_RETHROW() @@ -259,7 +273,7 @@ BOOST_FIXTURE_TEST_CASE( transfer_tests, eosio_token_tester ) try { ); transfer( N(alice), N(bob), asset::from_string("300 CERO"), "hola" ); - + alice_balance = get_account(N(alice), "0,CERO"); REQUIRE_MATCHING_OBJECT( alice_balance, mvo() ("balance", "700 CERO") @@ -274,21 +288,14 @@ BOOST_FIXTURE_TEST_CASE( transfer_tests, eosio_token_tester ) try { ("whitelist", 1) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: overdrawn balance" ), + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: overdrawn balance" ), transfer( N(alice), N(bob), asset::from_string("701 CERO"), "hola" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: invalid quantity" ), - transfer( N(alice), N(alice), asset::from_string("4611686018427387904 CERO"), "hola" ) + BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: must transfer positive quantity" ), + transfer( N(alice), N(bob), asset::from_string("-1000 CERO"), "hola" ) ); - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: must transfer positive quantity" ), - transfer( N(alice), N(alice), asset::from_string("-1000 CERO"), "hola" ) - ); - - BOOST_REQUIRE_EQUAL( error( "condition: assertion failed: attempt to subtract asset with different symbol" ), - transfer( N(alice), N(alice), asset::from_string("1.0 CERO"), "hola" ) - ); } FC_LOG_AND_RETHROW() diff --git a/tests/wasm_tests/exchange_tests.cpp b/unittests/exchange_tests.cpp similarity index 89% rename from tests/wasm_tests/exchange_tests.cpp rename to unittests/exchange_tests.cpp index 376b74a0164..7aeb67d18d7 100644 --- a/tests/wasm_tests/exchange_tests.cpp +++ b/unittests/exchange_tests.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -26,7 +26,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; @@ -85,12 +84,10 @@ class exchange_tester : public TESTER { exchange_state get_market_state( account_name exchange, symbol sym ) { uint64_t s = sym.value() >> 8; - const auto& db = control->get_database(); - const auto* tbl = db.find( - boost::make_tuple(exchange, s, N(markets))); + const auto& db = control->db(); + const auto* tbl = db.find(boost::make_tuple(exchange, s, N(markets))); if (tbl) { - const auto *obj = db.find( - boost::make_tuple(tbl->id, s)); + const auto *obj = db.find(boost::make_tuple(tbl->id, s)); if( obj ) { fc::datastream ds(obj->value.data(), obj->value.size()); exchange_state result; @@ -103,13 +100,11 @@ class exchange_tester : public TESTER { extended_asset get_exchange_balance( account_name exchange, account_name currency, symbol sym, account_name owner ) { - const auto& db = control->get_database(); - const auto* tbl = db.find( - boost::make_tuple(exchange, owner, N(exaccounts))); + const auto& db = control->db(); + const auto* tbl = db.find(boost::make_tuple(exchange, owner, N(exaccounts))); if (tbl) { - const auto *obj = db.find( - boost::make_tuple(tbl->id, owner)); + const auto *obj = db.find(boost::make_tuple(tbl->id, owner)); if( obj ) { fc::datastream ds(obj->value.data(), obj->value.size()); account_name own; @@ -128,16 +123,14 @@ class exchange_tester : public TESTER { double get_lent_shares( account_name exchange, symbol market, account_name owner, bool base ) { - const auto& db = control->get_database(); + const auto& db = control->db(); auto scope = ((market.value() >> 8) << 4) + (base ? 1 : 2); - const auto* tbl = db.find( - boost::make_tuple(exchange, scope, N(loans))); + const auto* tbl = db.find(boost::make_tuple(exchange, scope, N(loans))); if (tbl) { - const auto *obj = db.find( - boost::make_tuple(tbl->id, owner)); + const auto *obj = db.find(boost::make_tuple(tbl->id, owner)); if( obj ) { fc::datastream ds(obj->value.data(), obj->value.size()); account_name own; @@ -299,7 +292,7 @@ BOOST_AUTO_TEST_CASE( exchange_create ) try { extended_asset( A(10.00 BTC), N(eosio.token) ), extended_asset( A(0.01 USD), N(eosio.token) ) ); - for( const auto& at : result.action_traces ) + for( const auto& at : result->action_traces ) ilog( "${s}", ("s",at.console) ); trader_ex_usd = t.get_exchange_balance( N(exchange), N(eosio.token), symbol(2,"USD"), N(trader) ); @@ -314,7 +307,7 @@ BOOST_AUTO_TEST_CASE( exchange_create ) try { trader_ex_usd = t.get_exchange_balance( N(exchange), N(eosio.token), symbol(2,"USD"), N(trader) ); trader_ex_btc = t.get_exchange_balance( N(exchange), N(eosio.token), symbol(2,"BTC"), N(trader) ); - for( const auto& at : result.action_traces ) + for( const auto& at : result->action_traces ) ilog( "${s}", ("s",at.console) ); wdump((trader_ex_btc.quantity)); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp new file mode 100644 index 00000000000..4e7da0b45f1 --- /dev/null +++ b/unittests/forked_tests.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using namespace eosio::chain; +using namespace eosio::testing; + +private_key_type get_private_key( name keyname, string role ) { + return private_key_type::regenerate(fc::sha256::hash(string(keyname)+role)); +} + +public_key_type get_public_key( name keyname, string role ){ + return get_private_key( keyname, role ).get_public_key(); +} + +BOOST_AUTO_TEST_SUITE(forked_tests) + +BOOST_AUTO_TEST_CASE( irrblock ) try { + tester c; + c.produce_blocks(10); + auto r = c.create_accounts( {N(dan),N(sam),N(pam),N(scott)} ); + auto res = c.set_producers( {N(dan),N(sam),N(pam),N(scott)} ); + vector sch = { {N(dan),get_public_key(N(dan), "active")}, + {N(sam),get_public_key(N(sam), "active")}, + {N(scott),get_public_key(N(scott), "active")}, + {N(pam),get_public_key(N(pam), "active")} + }; + wlog("set producer schedule to [dan,sam,pam]"); + c.produce_blocks(50); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( forking ) try { + tester c; + c.produce_block(); + c.produce_block(); + auto r = c.create_accounts( {N(dan),N(sam),N(pam)} ); + wdump((fc::json::to_pretty_string(r))); + c.produce_block(); + auto res = c.set_producers( {N(dan),N(sam),N(pam)} ); + vector sch = { {N(dan),get_public_key(N(dan), "active")}, + {N(sam),get_public_key(N(sam), "active")}, + {N(pam),get_public_key(N(pam), "active")}}; + wdump((fc::json::to_pretty_string(res))); + wlog("set producer schedule to [dan,sam,pam]"); + c.produce_blocks(30); + + auto r2 = c.create_accounts( {N(eosio.token)} ); + wdump((fc::json::to_pretty_string(r2))); + c.set_code( N(eosio.token), eosio_token_wast ); + c.set_abi( N(eosio.token), eosio_token_abi ); + c.produce_blocks(10); + + + auto cr = c.push_action( N(eosio.token), N(create), N(eosio.token), mutable_variant_object() + ("issuer", "eosio" ) + ("maximum_supply", "10000000.0000 EOS") + ("can_freeze", 0) + ("can_recall", 0) + ("can_whitelist", 0) + ); + + wdump((fc::json::to_pretty_string(cr))); + + cr = c.push_action( N(eosio.token), N(issue), N(eosio), mutable_variant_object() + ("to", "dan" ) + ("quantity", "100.0000 EOS") + ("memo", "") + ); + + wdump((fc::json::to_pretty_string(cr))); + + + tester c2; + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + wlog( "c1 blocks:" ); + c.produce_blocks(3); + signed_block_ptr b; + b = c.produce_block(); + account_name expected_producer = N(dan); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + + b = c.produce_block(); + expected_producer = N(sam); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + c.produce_blocks(10); + c.create_accounts( {N(cam)} ); + c.set_producers( {N(dan),N(sam),N(pam),N(cam)} ); + wlog("set producer schedule to [dan,sam,pam,cam]"); + c.produce_block(); + // The next block should be produced by pam. + + // Sync second chain with first chain. + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + // Now sam and pam go on their own fork while dan is producing blocks by himself. + + wlog( "sam and pam go off on their own fork on c2 while dan produces blocks by himself in c1" ); + auto fork_block_num = c.control->head_block_num(); + + wlog( "c2 blocks:" ); + c2.produce_blocks(12); // pam produces 12 blocks + b = c2.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // sam skips over dan's blocks + expected_producer = N(sam); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + c2.produce_blocks(11 + 12); + + + wlog( "c1 blocks:" ); + b = c.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // dan skips over pam's blocks + expected_producer = N(dan); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + c.produce_blocks(11); + + // dan on chain 1 now gets all of the blocks from chain 2 which should cause fork switch + wlog( "push c2 blocks to c1" ); + for( uint32_t start = fork_block_num + 1, end = c2.control->head_block_num(); start <= end; ++start ) { + wdump((start)); + auto fb = c2.control->fetch_block_by_number( start ); + c.push_block( fb ); + } + wlog( "end push c2 blocks to c1" ); + + wlog( "c1 blocks:" ); + c.produce_blocks(24); + + b = c.produce_block(); // Switching active schedule to version 2 happens in this block. + expected_producer = N(pam); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + + b = c.produce_block(); + expected_producer = N(cam); +// BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); + c.produce_blocks(10); + + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.push_block( fb ); + } + wlog( "end push c1 blocks to c2" ); + + // Now with four block producers active and two identical chains (for now), + // we can test out the case that would trigger the bug in the old fork db code: + fork_block_num = c.control->head_block_num(); + wlog( "cam and dan go off on their own fork on c1 while sam and pam go off on their own fork on c2" ); + wlog( "c1 blocks:" ); + c.produce_blocks(12); // dan produces 12 blocks + c.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // cam skips over sam and pam's blocks + c.produce_blocks(23); // cam finishes the remaining 11 blocks then dan produces his 12 blocks + wlog( "c2 blocks:" ); + c2.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // pam skips over dan and sam's blocks + c2.produce_blocks(11); // pam finishes the remaining 11 blocks + c2.produce_block( fc::milliseconds(config::block_interval_ms * 25) ); // sam skips over cam and dan's blocks + c2.produce_blocks(11); // sam finishes the remaining 11 blocks + + wlog( "now cam and dan rejoin sam and pam on c2" ); + c2.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // cam skips over pam's blocks (this block triggers a block on this branch to become irreversible) + c2.produce_blocks(11); // cam produces the remaining 11 blocks + b = c2.produce_block(); // dan produces a block + + // a node on chain 1 now gets all but the last block from chain 2 which should cause a fork switch + wlog( "push c2 blocks (except for the last block by dan) to c1" ); + for( uint32_t start = fork_block_num + 1, end = c2.control->head_block_num() - 1; start <= end; ++start ) { + auto fb = c2.control->fetch_block_by_number( start ); + c.push_block( fb ); + } + wlog( "end push c2 blocks to c1" ); + wlog( "now push dan's block to c1 but first corrupt it so it is a bad block" ); + auto bad_block = *b; + bad_block.transaction_mroot = bad_block.previous; + c.control->abort_block(); + BOOST_REQUIRE_EXCEPTION(c.control->push_block( std::make_shared(bad_block) ), fc::exception, + [] (const fc::exception &ex)->bool { + return ex.to_detail_string().find("block not signed by expected key") != std::string::npos; + }); +} FC_LOG_AND_RETHROW() + + +/** + * This test verifies that the fork-choice rule favors the branch with + * the highest last irreversible block over one that is longer. + */ +BOOST_AUTO_TEST_CASE( prune_remove_branch ) try { + tester c; + c.produce_blocks(10); + auto r = c.create_accounts( {N(dan),N(sam),N(pam),N(scott)} ); + auto res = c.set_producers( {N(dan),N(sam),N(pam),N(scott)} ); + wlog("set producer schedule to [dan,sam,pam,scott]"); + c.produce_blocks(50); + + tester c2; + wlog( "push c1 blocks to c2" ); + while( c2.control->head_block_num() < c.control->head_block_num() ) { + auto fb = c.control->fetch_block_by_number( c2.control->head_block_num()+1 ); + c2.push_block( fb ); + } + + // fork happen after block 61 + BOOST_REQUIRE_EQUAL(61, c.control->head_block_num()); + BOOST_REQUIRE_EQUAL(61, c2.control->head_block_num()); + + int fork_num = c.control->head_block_num(); + + auto nextproducer = [](tester &c, int skip_interval) ->account_name { + auto head_time = c.control->head_block_time(); + auto next_time = head_time + fc::milliseconds(config::block_interval_ms * skip_interval); + return c.control->head_block_state()->get_scheduled_producer(next_time).producer_name; + }; + + // fork c: 2 producers: dan, sam + // fork c2: 1 producer: scott + int skip1 = 1, skip2 = 1; + for (int i = 0; i < 50; ++i) { + account_name next1 = nextproducer(c, skip1); + if (next1 == N(dan) || next1 == N(sam)) { + c.produce_block(fc::milliseconds(config::block_interval_ms * skip1)); skip1 = 1; + } + else ++skip1; + account_name next2 = nextproducer(c2, skip2); + if (next2 == N(scott)) { + c2.produce_block(fc::milliseconds(config::block_interval_ms * skip2)); skip2 = 1; + } + else ++skip2; + } + + BOOST_REQUIRE_EQUAL(87, c.control->head_block_num()); + BOOST_REQUIRE_EQUAL(73, c2.control->head_block_num()); + + // push fork from c2 => c + int p = fork_num; + while ( p < c2.control->head_block_num()) { + auto fb = c2.control->fetch_block_by_number(++p); + c.push_block(fb); + } + + BOOST_REQUIRE_EQUAL(73, c.control->head_block_num()); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE(confirmation) try { + + tester c; + c.produce_blocks(10); + auto r = c.create_accounts( {N(dan),N(sam),N(pam),N(scott)} ); + auto res = c.set_producers( {N(dan),N(sam),N(pam),N(scott)} ); + + private_key_type priv_sam = c.get_private_key( N(sam), "active" ); + private_key_type priv_dan = c.get_private_key( N(dan), "active" ); + private_key_type priv_pam = c.get_private_key( N(pam), "active" ); + private_key_type priv_scott = c.get_private_key( N(scott), "active" ); + private_key_type priv_invalid = c.get_private_key( N(invalid), "active" ); + + wlog("set producer schedule to [dan,sam,pam,scott]"); + c.produce_blocks(50); + c.control->abort_block(); // discard pending block + + BOOST_REQUIRE_EQUAL(61, c.control->head_block_num()); + + // 55 is by dan + block_state_ptr blk = c.control->fork_db().get_block_in_current_chain_by_num(55); + block_state_ptr blk61 = c.control->fork_db().get_block_in_current_chain_by_num(61); + block_state_ptr blk50 = c.control->fork_db().get_block_in_current_chain_by_num(50); + + BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(0, blk->confirmations.size()); + + // invalid signature + BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_invalid.sign(blk->sig_digest())}), + fc::exception, + [] (const fc::exception &ex)->bool { + return ex.to_detail_string().find("confirmation not signed by expected key") != std::string::npos; + }); + + // invalid schedule + BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(invalid), priv_invalid.sign(blk->sig_digest())}), + fc::exception, + [] (const fc::exception &ex)->bool { + return ex.to_detail_string().find("producer not in current schedule") != std::string::npos; + }); + + // signed by sam + c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_sam.sign(blk->sig_digest())}); + + BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(1, blk->confirmations.size()); + + // double confirm not allowed + BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_sam.sign(blk->sig_digest())}), + fc::exception, + [] (const fc::exception &ex)->bool { + return ex.to_detail_string().find("block already confirmed by this producer") != std::string::npos; + }); + + // signed by dan + c.control->push_confirmation(header_confirmation{blk->id, N(dan), priv_dan.sign(blk->sig_digest())}); + + BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(2, blk->confirmations.size()); + + // signed by pam + c.control->push_confirmation(header_confirmation{blk->id, N(pam), priv_pam.sign(blk->sig_digest())}); + + // we have more than 2/3 of confirmations, bft irreversible number should be set + BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(55, blk61->bft_irreversible_blocknum); // bft irreversible number will propagate to higher block + BOOST_REQUIRE_EQUAL(0, blk50->bft_irreversible_blocknum); // bft irreversible number will not propagate to lower block + BOOST_REQUIRE_EQUAL(3, blk->confirmations.size()); + + // signed by scott + c.control->push_confirmation(header_confirmation{blk->id, N(scott), priv_scott.sign(blk->sig_digest())}); + + BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(4, blk->confirmations.size()); + + // let's confirm block 50 as well + c.control->push_confirmation(header_confirmation{blk50->id, N(sam), priv_sam.sign(blk50->sig_digest())}); + c.control->push_confirmation(header_confirmation{blk50->id, N(dan), priv_dan.sign(blk50->sig_digest())}); + c.control->push_confirmation(header_confirmation{blk50->id, N(pam), priv_pam.sign(blk50->sig_digest())}); + BOOST_REQUIRE_EQUAL(50, blk50->bft_irreversible_blocknum); // bft irreversible number will not propagate to lower block + + block_state_ptr blk54 = c.control->fork_db().get_block_in_current_chain_by_num(54); + BOOST_REQUIRE_EQUAL(50, blk54->bft_irreversible_blocknum); + BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum); // bft irreversible number will not be updated to lower value + BOOST_REQUIRE_EQUAL(55, blk61->bft_irreversible_blocknum); + + c.produce_blocks(20); + + block_state_ptr blk81 = c.control->fork_db().get_block_in_current_chain_by_num(81); + BOOST_REQUIRE_EQUAL(55, blk81->bft_irreversible_blocknum); // bft irreversible number will propagate into new blocks + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/identity_tests.cpp b/unittests/identity_tests.cpp similarity index 96% rename from tests/wasm_tests/identity_tests.cpp rename to unittests/identity_tests.cpp index 36b58c4ca0e..638cc9548c2 100644 --- a/tests/wasm_tests/identity_tests.cpp +++ b/unittests/identity_tests.cpp @@ -1,9 +1,10 @@ #include #include -#include -#include -#include +#include +#include #include +#include +#include #include #include @@ -17,8 +18,6 @@ #include -#include "test_wasts.hpp" - #ifdef NON_VALIDATING_TEST #define TESTER tester #else @@ -27,8 +26,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; -using namespace eosio::chain_apis; using namespace eosio::testing; using namespace fc; @@ -47,23 +44,23 @@ class identity_tester : public TESTER { set_abi(N(identitytest), identity_test_abi); produce_blocks(1); - const auto& accnt = control->get_database().get( N(identity) ); + const auto& accnt = control->db().get( N(identity) ); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_ser.set_abi(abi); - const auto& acnt_test = control->get_database().get( N(identitytest) ); + const auto& acnt_test = control->db().get( N(identitytest) ); abi_def abi_test; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(acnt_test.abi, abi_test), true); abi_ser_test.set_abi(abi_test); - const global_property_object &gpo = control->get_global_properties(); - FC_ASSERT(0 < gpo.active_producers.producers.size(), "No producers"); - producer_name = (string)gpo.active_producers.producers.front().producer_name; + const auto& ap = control->active_producers(); + FC_ASSERT(0 < ap.producers.size(), "No producers"); + producer_name = (string)ap.producers.front().producer_name; } uint64_t get_result_uint64() { - const auto& db = control->get_database(); + const auto& db = control->db(); const auto* t_id = db.find(boost::make_tuple(N(identitytest), 0, N(result))); FC_ASSERT(t_id != 0, "Table id not found"); @@ -113,7 +110,7 @@ class identity_tester : public TESTER { } fc::variant get_identity(uint64_t idnt) { - const auto& db = control->get_database(); + const auto& db = control->db(); const auto* t_id = db.find(boost::make_tuple(N(identity), N(identity), N(ident))); FC_ASSERT(t_id != 0, "object not found"); @@ -124,7 +121,7 @@ class identity_tester : public TESTER { BOOST_REQUIRE_EQUAL(idnt, itr->primary_key); vector data; - read_only::copy_inline_row(*itr, data); + copy_row(*itr, data); return abi_ser.binary_to_variant("identrow", data); } @@ -142,7 +139,7 @@ class identity_tester : public TESTER { } fc::variant get_certrow(uint64_t identity, const string& property, uint64_t trusted, const string& certifier) { - const auto& db = control->get_database(); + const auto& db = control->db(); const auto* t_id = db.find(boost::make_tuple(N(identity), identity, N( certs ))); if ( !t_id ) { return fc::variant(nullptr); @@ -161,7 +158,7 @@ class identity_tester : public TESTER { "Record found in secondary index, but not found in primary index." ); vector data; - read_only::copy_inline_row(*itr, data); + copy_row(*itr, data); return abi_ser.binary_to_variant("certrow", data); } else { return fc::variant(nullptr); @@ -169,7 +166,7 @@ class identity_tester : public TESTER { } fc::variant get_accountrow(const string& account) { - const auto& db = control->get_database(); + const auto& db = control->db(); uint64_t acnt = string_to_name(account.c_str()); const auto* t_id = db.find(boost::make_tuple(N(identity), acnt, N(account))); if (!t_id) { @@ -179,7 +176,7 @@ class identity_tester : public TESTER { auto itr = idx.lower_bound(boost::make_tuple(t_id->id, N(account))); if( itr != idx.end() && itr->t_id == t_id->id && N(account) == itr->primary_key) { vector data; - read_only::copy_inline_row(*itr, data); + copy_row(*itr, data); return abi_ser.binary_to_variant("accountrow", data); } else { return fc::variant(nullptr); @@ -202,7 +199,7 @@ class identity_tester : public TESTER { } bool get_trust(const string& trustor, const string& trusting) { - const auto& db = control->get_database(); + const auto& db = control->db(); const auto* t_id = db.find(boost::make_tuple(N(identity), string_to_name(trustor.c_str()), N(trust))); if (!t_id) { return false; diff --git a/unittests/main.cpp b/unittests/main.cpp new file mode 100644 index 00000000000..ad2b5846210 --- /dev/null +++ b/unittests/main.cpp @@ -0,0 +1,45 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include +#include +#include +#include + +//extern uint32_t EOS_TESTING_GENESIS_TIMESTAMP; + +void translate_fc_exception(const fc::exception &e) { + std::cerr << "\033[33m" << e.to_detail_string() << "\033[0m" << std::endl; + BOOST_TEST_FAIL("Caught Unexpected Exception"); +} + +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + // Turn off blockchain logging if no --verbose parameter is not added + // To have verbose enabled, call "tests/chain_test -- --verbose" + bool is_verbose = false; + std::string verbose_arg = "--verbose"; + for (int i = 0; i < argc; i++) { + if (verbose_arg == argv[i]) { + is_verbose = true; + break; + } + } + if(!is_verbose) fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::off); + + // Register fc::exception translator + boost::unit_test::unit_test_monitor.register_exception_translator(&translate_fc_exception); + + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; + /* + const char* genesis_timestamp_str = getenv("EOS_TESTING_GENESIS_TIMESTAMP"); + if( genesis_timestamp_str != nullptr ) + { + EOS_TESTING_GENESIS_TIMESTAMP = std::stoul( genesis_timestamp_str ); + } + std::cout << "EOS_TESTING_GENESIS_TIMESTAMP is " << EOS_TESTING_GENESIS_TIMESTAMP << std::endl; + */ + return nullptr; +} diff --git a/tests/tests/message_buffer_tests.cpp b/unittests/message_buffer_tests.cpp similarity index 97% rename from tests/tests/message_buffer_tests.cpp rename to unittests/message_buffer_tests.cpp index 736136d000f..0fe4c708336 100644 --- a/tests/tests/message_buffer_tests.cpp +++ b/unittests/message_buffer_tests.cpp @@ -3,7 +3,7 @@ * @copyright defined in eos/LICENSE.txt */ -#include +#include #include #include @@ -36,7 +36,7 @@ constexpr auto def_buffer_size = 1024*1024*def_buffer_size_mb; BOOST_AUTO_TEST_CASE(message_buffer_construction) { try { - eosio::message_buffer mb; + fc::message_buffer mb; BOOST_CHECK_EQUAL(mb.total_bytes(), def_buffer_size); BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size); BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); @@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE(message_buffer_construction) BOOST_AUTO_TEST_CASE(message_buffer_growth) { try { - eosio::message_buffer mb; + fc::message_buffer mb; mb.add_buffer_to_chain(); BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size); @@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(message_buffer_peek_read) try { { const uint32_t small = 32; - eosio::message_buffer mb; + fc::message_buffer mb; BOOST_CHECK_EQUAL(mb.total_bytes(), small); BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); @@ -216,7 +216,7 @@ BOOST_AUTO_TEST_CASE(message_buffer_write_ptr_to_end) try { { const uint32_t small = 32; - eosio::message_buffer mb; + fc::message_buffer mb; BOOST_CHECK_EQUAL(mb.total_bytes(), small); BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); diff --git a/tests/tests/misc_tests.cpp b/unittests/misc_tests.cpp similarity index 52% rename from tests/tests/misc_tests.cpp rename to unittests/misc_tests.cpp index 501c4d3e508..a45e51b4beb 100644 --- a/tests/tests/misc_tests.cpp +++ b/unittests/misc_tests.cpp @@ -23,6 +23,8 @@ #endif using namespace eosio::chain; +using namespace eosio::testing; + namespace eosio { using namespace chain; @@ -50,6 +52,98 @@ BOOST_AUTO_TEST_CASE(json_from_string_test) BOOST_CHECK_EQUAL(exc_found, true); } +// Test overflow handling in asset::from_string +BOOST_AUTO_TEST_CASE(asset_from_string_overflow) +{ + asset a; + + // precision = 19, magnitude < 2^61 + BOOST_CHECK_EXCEPTION( asset::from_string("0.1000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-0.1000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("1.0000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-1.0000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); + + // precision = 18, magnitude < 2^58 + a = asset::from_string("0.100000000000000000 CUR"); + BOOST_CHECK_EQUAL(a.amount, 100000000000000000L); + a = asset::from_string("-0.100000000000000000 CUR"); + BOOST_CHECK_EQUAL(a.amount, -100000000000000000L); + + // precision = 18, magnitude = 2^62 + BOOST_CHECK_EXCEPTION( asset::from_string("4.611686018427387904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-4.611686018427387904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("4611686018427387.904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-4611686018427387.904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + + // precision = 18, magnitude = 2^62-1 + a = asset::from_string("4.611686018427387903 CUR"); + BOOST_CHECK_EQUAL(a.amount, 4611686018427387903L); + a = asset::from_string("-4.611686018427387903 CUR"); + BOOST_CHECK_EQUAL(a.amount, -4611686018427387903L); + + // precision = 0, magnitude = 2^62 + BOOST_CHECK_EXCEPTION( asset::from_string("4611686018427387904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-4611686018427387904 CUR") , asset_type_exception, [](const asset_type_exception& e) { + return expect_assert_message(e, "magnitude of asset amount must be less than 2^62"); + }); + + // precision = 0, magnitude = 2^62-1 + a = asset::from_string("4611686018427387903 CUR"); + BOOST_CHECK_EQUAL(a.amount, 4611686018427387903L); + a = asset::from_string("-4611686018427387903 CUR"); + BOOST_CHECK_EQUAL(a.amount, -4611686018427387903L); + + // precision = 18, magnitude = 2^65 + BOOST_CHECK_EXCEPTION( asset::from_string("36.893488147419103232 CUR") , overflow_exception, [](const overflow_exception& e) { + return true; + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-36.893488147419103232 CUR") , underflow_exception, [](const underflow_exception& e) { + return true; + }); + + // precision = 14, magnitude > 2^76 + BOOST_CHECK_EXCEPTION( asset::from_string("1000000000.00000000000000 CUR") , overflow_exception, [](const overflow_exception& e) { + return true; + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-1000000000.00000000000000 CUR") , underflow_exception, [](const underflow_exception& e) { + return true; + }); + + // precision = 0, magnitude > 2^76 + BOOST_CHECK_EXCEPTION( asset::from_string("100000000000000000000000 CUR") , parse_error_exception, [](const parse_error_exception& e) { + return expect_assert_message(e, "Couldn't parse int64_t"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-100000000000000000000000 CUR") , parse_error_exception, [](const parse_error_exception& e) { + return expect_assert_message(e, "Couldn't parse int64_t"); + }); + + // precision = 20, magnitude > 2^142 + BOOST_CHECK_EXCEPTION( asset::from_string("100000000000000000000000.00000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); + BOOST_CHECK_EXCEPTION( asset::from_string("-100000000000000000000000.00000000000000000000 CUR") , assert_exception, [](const assert_exception& e) { + return expect_assert_message(e, "precision should be <= 18"); + }); +} + /// Test that our deterministic random shuffle algorithm gives the same results in all environments BOOST_AUTO_TEST_CASE(deterministic_randomness) { try { @@ -95,6 +189,8 @@ struct permission_visitor { permissions.push_back(permission); } + void operator()(const permission_level& permission, bool repeat ) {} + void push_undo() { if( _log ) ilog("push_undo called"); @@ -128,24 +224,23 @@ BOOST_AUTO_TEST_CASE(authority_checker) auto GetNullAuthority = [](auto){abort(); return authority();}; - permission_visitor pv; auto A = authority(2, {key_weight{a, 1}, key_weight{b, 1}}); { - auto checker = make_auth_checker(GetNullAuthority, pv, 2, {a, b}); + auto checker = make_auth_checker(GetNullAuthority, 2, {a, b}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 2); BOOST_TEST(checker.unused_keys().size() == 0); } { - auto checker = make_auth_checker(GetNullAuthority, pv, 2, {a, c}); + auto checker = make_auth_checker(GetNullAuthority, 2, {a, c}); BOOST_TEST(!checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 0); BOOST_TEST(checker.unused_keys().size() == 2); } { - auto checker = make_auth_checker(GetNullAuthority, pv, 2, {a, b, c}); + auto checker = make_auth_checker(GetNullAuthority, 2, {a, b, c}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 2); @@ -155,27 +250,27 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(checker.unused_keys().count(c) == 1); } { - auto checker = make_auth_checker(GetNullAuthority, pv, 2, {b, c}); + auto checker = make_auth_checker(GetNullAuthority, 2, {b, c}); BOOST_TEST(!checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 0); } A = authority(3, {key_weight{a, 1}, key_weight{b, 1}, key_weight{c, 1}}); - BOOST_TEST(make_auth_checker(GetNullAuthority, pv, 2, {c, b, a}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetNullAuthority, pv, 2, {a, b}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetNullAuthority, pv, 2, {a, c}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetNullAuthority, pv, 2, {b, c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetNullAuthority, 2, {c, b, a}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetNullAuthority, 2, {a, b}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetNullAuthority, 2, {a, c}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetNullAuthority, 2, {b, c}).satisfied(A)); A = authority(1, {key_weight{a, 1}, key_weight{b, 1}}); - BOOST_TEST(make_auth_checker(GetNullAuthority, pv, 2, {a}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetNullAuthority, pv, 2, {b}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetNullAuthority, pv, 2, {c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetNullAuthority, 2, {a}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetNullAuthority, 2, {b}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetNullAuthority, 2, {c}).satisfied(A)); A = authority(1, {key_weight{a, 2}, key_weight{b, 1}}); - BOOST_TEST(make_auth_checker(GetNullAuthority, pv, 2, {a}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetNullAuthority, pv, 2, {b}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetNullAuthority, pv, 2, {c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetNullAuthority, 2, {a}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetNullAuthority, 2, {b}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetNullAuthority, 2, {c}).satisfied(A)); auto GetCAuthority = [c](auto){ return authority(1, {key_weight{c, 1}}); @@ -183,26 +278,26 @@ BOOST_AUTO_TEST_CASE(authority_checker) A = authority(2, {key_weight{a, 2}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 1}}); { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {a}); + auto checker = make_auth_checker(GetCAuthority, 2, {a}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.all_keys_used()); } { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {b}); + auto checker = make_auth_checker(GetCAuthority, 2, {b}); BOOST_TEST(!checker.satisfied(A)); BOOST_TEST(checker.used_keys().size() == 0); BOOST_TEST(checker.unused_keys().size() == 1); BOOST_TEST(checker.unused_keys().count(b) == 1); } { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {c}); + auto checker = make_auth_checker(GetCAuthority, 2, {c}); BOOST_TEST(!checker.satisfied(A)); BOOST_TEST(checker.used_keys().size() == 0); BOOST_TEST(checker.unused_keys().size() == 1); BOOST_TEST(checker.unused_keys().count(c) == 1); } { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {b, c}); + auto checker = make_auth_checker(GetCAuthority, 2, {b, c}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 2); @@ -211,7 +306,7 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(checker.used_keys().count(c) == 1); } { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {b, c, a}); + auto checker = make_auth_checker(GetCAuthority, 2, {b, c, a}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 1); @@ -222,19 +317,13 @@ BOOST_AUTO_TEST_CASE(authority_checker) } A = authority(3, {key_weight{a, 2}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 3}}); - pv._log = true; { - pv.permissions.clear(); - pv.size_stack.clear(); - auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b}); + auto checker = make_auth_checker(GetCAuthority, 2, {a, b}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.all_keys_used()); - BOOST_TEST(pv.permissions.size() == 0); } { - pv.permissions.clear(); - pv.size_stack.clear(); - auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b, c}); + auto checker = make_auth_checker(GetCAuthority, 2, {a, b, c}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 1); @@ -242,21 +331,17 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(checker.unused_keys().size() == 2); BOOST_TEST(checker.unused_keys().count(a) == 1); BOOST_TEST(checker.unused_keys().count(b) == 1); - BOOST_TEST(pv.permissions.size() == 1); - BOOST_TEST(pv.permissions.back().actor == "hello"); - BOOST_TEST(pv.permissions.back().permission == "world"); } - pv._log = false; A = authority(2, {key_weight{a, 1}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 1}}); - BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {a}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {b}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {c}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetCAuthority, pv, 2, {a, b}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetCAuthority, pv, 2, {b, c}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetCAuthority, pv, 2, {a, c}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetCAuthority, 2, {a}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetCAuthority, 2, {b}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetCAuthority, 2, {c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetCAuthority, 2, {a, b}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetCAuthority, 2, {b, c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetCAuthority, 2, {a, c}).satisfied(A)); { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b, c}); + auto checker = make_auth_checker(GetCAuthority, 2, {a, b, c}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 2); @@ -265,12 +350,12 @@ BOOST_AUTO_TEST_CASE(authority_checker) } A = authority(2, {key_weight{a, 1}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 2}}); - BOOST_TEST(make_auth_checker(GetCAuthority, pv, 2, {a, b}).satisfied(A)); - BOOST_TEST(make_auth_checker(GetCAuthority, pv, 2, {c}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {a}).satisfied(A)); - BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {b}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetCAuthority, 2, {a, b}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetCAuthority, 2, {c}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetCAuthority, 2, {a}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetCAuthority, 2, {b}).satisfied(A)); { - auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b, c}); + auto checker = make_auth_checker(GetCAuthority, 2, {a, b, c}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 1); @@ -289,12 +374,12 @@ BOOST_AUTO_TEST_CASE(authority_checker) A = authority(5, {key_weight{a, 2}, key_weight{b, 2}, key_weight{c, 2}}, {permission_level_weight{{"top", "top"}, 5}}); { - auto checker = make_auth_checker(GetAuthority, pv, 2, {d, e}); + auto checker = make_auth_checker(GetAuthority, 2, {d, e}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.all_keys_used()); } { - auto checker = make_auth_checker(GetAuthority, pv, 2, {a, b, c, d, e}); + auto checker = make_auth_checker(GetAuthority, 2, {a, b, c, d, e}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 2); @@ -303,7 +388,7 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(checker.used_keys().count(e) == 1); } { - auto checker = make_auth_checker(GetAuthority, pv, 2, {a, b, c, e}); + auto checker = make_auth_checker(GetAuthority, 2, {a, b, c, e}); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(!checker.all_keys_used()); BOOST_TEST(checker.used_keys().size() == 3); @@ -312,28 +397,28 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(checker.used_keys().count(b) == 1); BOOST_TEST(checker.used_keys().count(c) == 1); } - BOOST_TEST(make_auth_checker(GetAuthority, pv, 1, {a, b, c}).satisfied(A)); + BOOST_TEST(make_auth_checker(GetAuthority, 1, {a, b, c}).satisfied(A)); // Fails due to short recursion depth limit - BOOST_TEST(!make_auth_checker(GetAuthority, pv, 1, {d, e}).satisfied(A)); + BOOST_TEST(!make_auth_checker(GetAuthority, 1, {d, e}).satisfied(A)); BOOST_TEST(b < a); BOOST_TEST(b < c); BOOST_TEST(a < c); { - // valid key order: c > a > b - A = authority(2, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}); - // valid key order: c > b - auto B = authority(1, {key_weight{c, 1}, key_weight{b, 1}}); - // invalid key order: b < c - auto C = authority(1, {key_weight{c, 1}, key_weight{b, 1}, key_weight{c, 1}}); + // valid key order: b < a < c + A = authority(2, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}); + // valid key order: b < c + auto B = authority(1, {key_weight{b, 1}, key_weight{c, 1}}); + // invalid key order: c > b + auto C = authority(1, {key_weight{b, 1}, key_weight{c, 1}, key_weight{b, 1}}); // invalid key order: duplicate c - auto D = authority(1, {key_weight{c, 1}, key_weight{c, 1}, key_weight{b, 1}}); + auto D = authority(1, {key_weight{b, 1}, key_weight{c, 1}, key_weight{c, 1}}); // invalid key order: duplicate b - auto E = authority(1, {key_weight{c, 1}, key_weight{b, 1}, key_weight{b, 1}}); + auto E = authority(1, {key_weight{b, 1}, key_weight{b, 1}, key_weight{c, 1}}); // unvalid: insufficient weight - auto F = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}); + auto F = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}); - auto checker = make_auth_checker(GetNullAuthority, pv, 2, {a, b, c}); + auto checker = make_auth_checker(GetNullAuthority, 2, {a, b, c}); BOOST_TEST(validate(A)); BOOST_TEST(validate(B)); BOOST_TEST(!validate(C)); @@ -342,51 +427,51 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_TEST(!validate(F)); BOOST_TEST(!checker.all_keys_used()); - BOOST_TEST(checker.unused_keys().count(c) == 1); - BOOST_TEST(checker.unused_keys().count(a) == 1); BOOST_TEST(checker.unused_keys().count(b) == 1); + BOOST_TEST(checker.unused_keys().count(a) == 1); + BOOST_TEST(checker.unused_keys().count(c) == 1); BOOST_TEST(checker.satisfied(A)); BOOST_TEST(checker.satisfied(B)); BOOST_TEST(!checker.all_keys_used()); - BOOST_TEST(checker.unused_keys().count(c) == 0); + BOOST_TEST(checker.unused_keys().count(b) == 0); BOOST_TEST(checker.unused_keys().count(a) == 0); - BOOST_TEST(checker.unused_keys().count(b) == 1); + BOOST_TEST(checker.unused_keys().count(c) == 1); } { - auto A2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hi", "world"}, 1}, - permission_level_weight{{"hello", "world"}, 1}, - permission_level_weight{{"a", "world"}, 1} + auto A2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"a", "world"}, 1}, + permission_level_weight{{"hello", "world"}, 1}, + permission_level_weight{{"hi", "world"}, 1} }); - auto B2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, + auto B2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, {permission_level_weight{{"hello", "world"}, 1} }); - auto C2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hello", "world"}, 1}, - permission_level_weight{{"hello", "there"}, 1} + auto C2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"hello", "there"}, 1}, + permission_level_weight{{"hello", "world"}, 1} }); // invalid: duplicate - auto D2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hello", "world"}, 1}, - permission_level_weight{{"hello", "world"}, 2} + auto D2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"hello", "world"}, 1}, + permission_level_weight{{"hello", "world"}, 2} }); // invalid: wrong order - auto E2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hello", "there"}, 1}, - permission_level_weight{{"hello", "world"}, 2} + auto E2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"hello", "world"}, 2}, + permission_level_weight{{"hello", "there"}, 1} }); // invalid: wrong order - auto F2 = authority(4, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hello", "world"}, 1}, - permission_level_weight{{"hi", "world"}, 2} + auto F2 = authority(4, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"hi", "world"}, 2}, + permission_level_weight{{"hello", "world"}, 1} }); // invalid: insufficient weight - auto G2 = authority(7, {key_weight{c, 1}, key_weight{a, 1}, key_weight{b, 1}}, - {permission_level_weight{{"hi", "world"}, 1}, - permission_level_weight{{"hello", "world"}, 1}, - permission_level_weight{{"a", "world"}, 1} - }); + auto G2 = authority(7, {key_weight{b, 1}, key_weight{a, 1}, key_weight{c, 1}}, + { permission_level_weight{{"a", "world"}, 1}, + permission_level_weight{{"hello", "world"}, 1}, + permission_level_weight{{"hi", "world"}, 1} + }); BOOST_TEST(validate(A2)); BOOST_TEST(validate(B2)); diff --git a/tests/wasm_tests/multi_index_tests.cpp b/unittests/multi_index_tests.cpp similarity index 94% rename from tests/wasm_tests/multi_index_tests.cpp rename to unittests/multi_index_tests.cpp index 226d3b1dc93..798754221dd 100644 --- a/tests/wasm_tests/multi_index_tests.cpp +++ b/unittests/multi_index_tests.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include @@ -17,7 +15,6 @@ #define TESTER validating_tester #endif -using namespace eosio::chain::contracts; using namespace eosio::testing; BOOST_AUTO_TEST_SUITE(multi_index_tests) diff --git a/unittests/multisig_tests.cpp b/unittests/multisig_tests.cpp new file mode 100644 index 00000000000..f25d48eb799 --- /dev/null +++ b/unittests/multisig_tests.cpp @@ -0,0 +1,305 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +using namespace eosio::testing; +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +using mvo = fc::mutable_variant_object; + +class eosio_msig_tester : public tester { +public: + + eosio_msig_tester() { + create_accounts( { N(eosio.msig), N(alice), N(bob), N(carol) } ); + produce_block(); + + auto trace = base_tester::push_action(config::system_account_name, N(setpriv), + config::system_account_name, mutable_variant_object() + ("account", "eosio.msig") + ("is_priv", 1) + ); + + set_code( N(eosio.msig), eosio_msig_wast ); + set_abi( N(eosio.msig), eosio_msig_abi ); + + produce_blocks(); + const auto& accnt = control->db().get( N(eosio.msig) ); + abi_def abi; + BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); + abi_ser.set_abi(abi); + } + + transaction_trace_ptr push_action( const account_name& signer, const action_name& name, const variant_object& data, bool auth = true ) { + vector accounts; + if( auth ) + accounts.push_back( signer ); + auto trace = base_tester::push_action( N(eosio.msig), name, accounts, data ); + produce_block(); + BOOST_REQUIRE_EQUAL( true, chain_has_transaction(trace->id) ); + return trace; + + /* + string action_type_name = abi_ser.get_action_type(name); + + action act; + act.account = N(eosio.msig); + act.name = name; + act.data = abi_ser.variant_to_binary( action_type_name, data ); + //std::cout << "test:\n" << fc::to_hex(act.data.data(), act.data.size()) << " size = " << act.data.size() << std::endl; + + return base_tester::push_action( std::move(act), auth ? uint64_t(signer) : 0 ); + */ + } + + transaction reqauth( account_name from, const vector& auths ); + + abi_serializer abi_ser; +}; + +transaction eosio_msig_tester::reqauth( account_name from, const vector& auths ) { + fc::variants v; + for ( auto& level : auths ) { + v.push_back(fc::mutable_variant_object() + ("actor", level.actor) + ("permission", level.permission) + ); + } + variant pretty_trx = fc::mutable_variant_object() + ("expiration", "2020-01-01T00:30") + ("ref_block_num", 2) + ("ref_block_prefix", 3) + ("max_net_usage_words", 0) + ("max_cpu_usage_ms", 0) + ("delay_sec", 0) + ("actions", fc::variants({ + fc::mutable_variant_object() + ("account", name(config::system_account_name)) + ("name", "reqauth") + ("authorization", v) + ("data", fc::mutable_variant_object() ("from", from) ) + }) + ); + transaction trx; + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + return trx; +} + +BOOST_AUTO_TEST_SUITE(eosio_msig_tests) + +BOOST_FIXTURE_TEST_CASE( propose_approve_execute, eosio_msig_tester ) try { + auto trx = reqauth("alice", {permission_level{N(alice), config::active_name}} ); + + push_action( N(alice), N(propose), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("trx", trx) + ("requested", vector{{ N(alice), config::active_name }}) + ); + + //fail to execute before approval + BOOST_REQUIRE_EXCEPTION( push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ), + fc::assert_exception, + eosio_assert_message_is("transaction authorization failed") + ); + + //approve and execute + push_action( N(alice), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(alice), config::active_name }) + ); + + transaction_trace_ptr trace; + control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); + push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ); + + BOOST_REQUIRE( bool(trace) ); + BOOST_REQUIRE_EQUAL( 1, trace->action_traces.size() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( propose_approve_unapprove, eosio_msig_tester ) try { + auto trx = reqauth("alice", {permission_level{N(alice), config::active_name}} ); + + push_action( N(alice), N(propose), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("trx", trx) + ("requested", vector{{ N(alice), config::active_name }}) + ); + + push_action( N(alice), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(alice), config::active_name }) + ); + + push_action( N(alice), N(unapprove), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(alice), config::active_name }) + ); + + BOOST_REQUIRE_EXCEPTION( push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ), + fc::assert_exception, + eosio_assert_message_is("transaction authorization failed") + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( propose_approve_by_two, eosio_msig_tester ) try { + auto trx = reqauth("alice", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } } ); + push_action( N(alice), N(propose), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("trx", trx) + ("requested", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } }) + ); + + //approve by alice + push_action( N(alice), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(alice), config::active_name }) + ); + + //fail because approval by bob is missing + BOOST_REQUIRE_EXCEPTION( push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ), + fc::assert_exception, + eosio_assert_message_is("transaction authorization failed") + ); + + //approve by bob and execute + push_action( N(bob), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(bob), config::active_name }) + ); + + transaction_trace_ptr trace; + control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); + + push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ); + + BOOST_REQUIRE( bool(trace) ); + BOOST_REQUIRE_EQUAL( 1, trace->action_traces.size() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( propose_with_wrong_requested_auth, eosio_msig_tester ) try { + auto trx = reqauth("alice", vector{ { N(alice), config::active_name }, { N(bob), config::active_name } } ); + //try with not enough requested auth + BOOST_REQUIRE_EXCEPTION( push_action( N(alice), N(propose), mvo() + ("proposer", "alice") + ("proposal_name", "third") + ("trx", trx) + ("requested", vector{ { N(alice), config::active_name } } ) + ), + fc::assert_exception, + eosio_assert_message_is("transaction authorization failed") + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( big_transaction, eosio_msig_tester ) try { + vector perm = { { N(alice), config::active_name }, { N(bob), config::active_name } }; + auto wasm = wast_to_wasm( exchange_wast ); + + variant pretty_trx = fc::mutable_variant_object() + ("expiration", "2020-01-01T00:30") + ("ref_block_num", 2) + ("ref_block_prefix", 3) + ("max_net_usage_words", 0) + ("max_cpu_usage_ms", 0) + ("delay_sec", 0) + ("actions", fc::variants({ + fc::mutable_variant_object() + ("account", name(config::system_account_name)) + ("name", "setcode") + ("authorization", perm) + ("data", fc::mutable_variant_object() + ("account", "alice") + ("vmtype", 0) + ("vmversion", 0) + ("code", bytes( wasm.begin(), wasm.end() )) + ) + }) + ); + + transaction trx; + abi_serializer::from_variant(pretty_trx, trx, get_resolver()); + + push_action( N(alice), N(propose), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("trx", trx) + ("requested", perm) + ); + + //approve by alice + push_action( N(alice), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(alice), config::active_name }) + ); + //approve by bob and execute + push_action( N(bob), N(approve), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("level", permission_level{ N(bob), config::active_name }) + ); + + transaction_trace_ptr trace; + control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); + + push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") + ); + + BOOST_REQUIRE( bool(trace) ); + BOOST_REQUIRE_EQUAL( 1, trace->action_traces.size() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/payloadless_tests.cpp b/unittests/payloadless_tests.cpp similarity index 88% rename from tests/wasm_tests/payloadless_tests.cpp rename to unittests/payloadless_tests.cpp index 56eb0956d74..69aa4dbc6ac 100644 --- a/tests/wasm_tests/payloadless_tests.cpp +++ b/unittests/payloadless_tests.cpp @@ -4,7 +4,7 @@ #pragma GCC diagnostic pop #include #include -#include +#include #include #include @@ -22,7 +22,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; @@ -39,7 +38,7 @@ BOOST_FIXTURE_TEST_CASE( test_doit, payloadless_tester ) { set_abi( N(payloadless), payloadless_abi ); auto trace = push_action(N(payloadless), N(doit), N(payloadless), mutable_variant_object()); - auto msg = trace.action_traces.front().console; + auto msg = trace->action_traces.front().console; BOOST_CHECK_EQUAL(msg == "Im a payloadless action", true); } diff --git a/tests/chain_tests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp similarity index 82% rename from tests/chain_tests/producer_schedule_tests.cpp rename to unittests/producer_schedule_tests.cpp index a40a84ccb8d..c70fee6aa35 100644 --- a/tests/chain_tests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #ifdef NON_VALIDATING_TEST @@ -15,16 +16,16 @@ using mvo = fc::mutable_variant_object; BOOST_AUTO_TEST_SUITE(producer_schedule_tests) // Calculate expected producer given the schedule and slot number - account_name get_expected_producer(const producer_schedule_type& schedule, const uint64_t slot) { - const auto& index = (slot % (schedule.producers.size() * config::producer_repetitions)) / config::producer_repetitions; - return schedule.producers.at(index).producer_name; + account_name get_expected_producer(const vector& schedule, const uint64_t slot) { + const auto& index = (slot % (schedule.size() * config::producer_repetitions)) / config::producer_repetitions; + return schedule.at(index).producer_name; }; // Check if two schedule is equal - bool is_schedule_equal(const producer_schedule_type& first, const producer_schedule_type& second) { - bool is_equal = first.producers.size() == second.producers.size(); - for (uint32_t i = 0; i < first.producers.size(); i++) { - is_equal = is_equal && first.producers.at(i) == second.producers.at(i); + bool is_schedule_equal(const vector& first, const vector& second) { + bool is_equal = first.size() == second.size(); + for (uint32_t i = 0; i < first.size(); i++) { + is_equal = is_equal && first.at(i) == second.at(i); } return is_equal; }; @@ -32,25 +33,25 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) // Calculate the block num of the next round first block // The new producer schedule will become effective when it's in the block of the next round first block // However, it won't be applied until the effective block num is deemed irreversible - uint64_t calc_block_num_of_next_round_first_block(const chain_controller& control){ - auto res = control.get_dynamic_global_properties().head_block_number + 1; - const auto blocks_per_round = control.get_global_properties().active_producers.producers.size() * config::producer_repetitions; + uint64_t calc_block_num_of_next_round_first_block(const controller& control){ + auto res = control.head_block_num() + 1; + const auto blocks_per_round = control.head_block_state()->active_schedule.producers.size() * config::producer_repetitions; while((res % blocks_per_round) != 0) { res++; } return res; }; - +#if 0 BOOST_FIXTURE_TEST_CASE( verify_producer_schedule, TESTER ) try { // Utility function to ensure that producer schedule work as expected - const auto& confirm_schedule_correctness = [&](const producer_schedule_type& new_prod_schd, const uint64_t eff_new_prod_schd_block_num) { + const auto& confirm_schedule_correctness = [&](const vector& new_prod_schd, const uint64_t eff_new_prod_schd_block_num) { const uint32_t check_duration = 1000; // number of blocks for (uint32_t i = 0; i < check_duration; ++i) { - const producer_schedule_type current_schedule = control->get_global_properties().active_producers; - const auto& current_absolute_slot = control->get_dynamic_global_properties().current_absolute_slot ; + const auto current_schedule = control->head_block_state()->active_schedule.producers; + const auto& current_absolute_slot = control->get_global_properties().proposed_schedule_block_num; // Determine expected producer - const auto& expected_producer = get_expected_producer(current_schedule, current_absolute_slot + 1); + const auto& expected_producer = get_expected_producer(current_schedule, *current_absolute_slot + 1); // The new schedule will only be applied once the effective block num is deemed irreversible const bool is_new_schedule_applied = control->last_irreversible_block_num() > eff_new_prod_schd_block_num; @@ -80,8 +81,9 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) // ---- Test first set of producers ---- // Send set prods action and confirm schedule correctness - const auto& first_prod_schd = set_producers(producers, 1); - const auto& eff_first_prod_schd_block_num = calc_block_num_of_next_round_first_block(*control); + set_producers(producers); + const auto first_prod_schd = get_producer_keys(producers); + const auto eff_first_prod_schd_block_num = calc_block_num_of_next_round_first_block(*control); confirm_schedule_correctness(first_prod_schd, eff_first_prod_schd_block_num); // ---- Test second set of producers ---- @@ -89,7 +91,8 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) producers[3], producers[6], producers[9], producers[12], producers[15], producers[18], producers[20] }; // Send set prods action and confirm schedule correctness - const auto& second_prod_schd = set_producers(second_set_of_producer, 2); + set_producers(second_set_of_producer); + const auto second_prod_schd = get_producer_keys(second_set_of_producer); const auto& eff_second_prod_schd_block_num = calc_block_num_of_next_round_first_block(*control); confirm_schedule_correctness(second_prod_schd, eff_second_prod_schd_block_num); @@ -107,7 +110,8 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) producers[1], producers[4], producers[7], producers[10], producers[13], producers[16], producers[19] }; // Send set prods action and confirm schedule correctness - const auto& third_prod_schd = set_producers(third_set_of_producer, 3); + set_producers(third_set_of_producer); + const auto third_prod_schd = get_producer_keys(third_set_of_producer); const auto& eff_third_prod_schd_block_num = calc_block_num_of_next_round_first_block(*control); confirm_schedule_correctness(third_prod_schd, eff_third_prod_schd_block_num); @@ -115,7 +119,7 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) BOOST_FIXTURE_TEST_CASE( verify_producers, TESTER ) try { - + vector valid_producers = { "inita", "initb", "initc", "initd", "inite", "initf", "initg", "inith", "initi", "initj", "initk", "initl", "initm", "initn", @@ -127,7 +131,7 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) // account initz does not exist vector nonexisting_producer = { "initz" }; BOOST_CHECK_THROW(set_producers(nonexisting_producer), wasm_execution_error); - + // replace initg with inita, inita is now duplicate vector invalid_producers = { "inita", "initb", "initc", "initd", "inite", "initf", "inita", @@ -190,5 +194,5 @@ BOOST_AUTO_TEST_SUITE(producer_schedule_tests) confirm_header_schd_ver_correctness(3, eff_third_prod_schd_block_num); } FC_LOG_AND_RETHROW() - +#endif BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/library_tests/chain/resource_limits_test.cpp b/unittests/resource_limits_test.cpp similarity index 72% rename from tests/library_tests/chain/resource_limits_test.cpp rename to unittests/resource_limits_test.cpp index ac7bb8bdc11..e7461209e49 100644 --- a/tests/library_tests/chain/resource_limits_test.cpp +++ b/unittests/resource_limits_test.cpp @@ -1,12 +1,12 @@ #include #include #include -#include +#include #include using namespace eosio::chain::resource_limits; -using namespace eosio::chain::test; +using namespace eosio::testing; using namespace eosio::chain; @@ -18,8 +18,8 @@ class resource_limits_fixture: private chainbase_fixture<512*1024>, public resou :chainbase_fixture() ,resource_limits_manager(*chainbase_fixture::_db) { + add_indices(); initialize_database(); - initialize_chain(); } ~resource_limits_fixture() {} @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) // relax from the starting state (congested) to the idle state as fast as possible uint32_t iterations = 0; - while (get_virtual_block_cpu_limit() < desired_virtual_limit && iterations <= expected_relax_iterations) { + while( get_virtual_block_cpu_limit() < desired_virtual_limit && iterations <= expected_relax_iterations ) { add_transaction_usage({account},0,0,iterations); process_block_usage(iterations++); } @@ -86,13 +86,13 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_REQUIRE_EQUAL(get_virtual_block_cpu_limit(), desired_virtual_limit); // push maximum resources to go from idle back to congested as fast as possible - iterations = 0; - while (get_virtual_block_cpu_limit() > config::default_max_block_cpu_usage && iterations <= expected_contract_iterations) { + while( get_virtual_block_cpu_limit() > config::default_max_block_cpu_usage + && iterations <= expected_relax_iterations + expected_contract_iterations ) { add_transaction_usage({account}, config::default_max_block_cpu_usage, 0, iterations); process_block_usage(iterations++); } - BOOST_REQUIRE_EQUAL(iterations, expected_contract_iterations); + BOOST_REQUIRE_EQUAL(iterations, expected_relax_iterations + expected_contract_iterations); BOOST_REQUIRE_EQUAL(get_virtual_block_cpu_limit(), config::default_max_block_cpu_usage); } FC_LOG_AND_RETHROW(); @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) // relax from the starting state (congested) to the idle state as fast as possible uint32_t iterations = 0; - while (get_virtual_block_net_limit() < desired_virtual_limit && iterations <= expected_relax_iterations) { + while( get_virtual_block_net_limit() < desired_virtual_limit && iterations <= expected_relax_iterations ) { add_transaction_usage({account},0,0,iterations); process_block_usage(iterations++); } @@ -125,19 +125,23 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_REQUIRE_EQUAL(get_virtual_block_net_limit(), desired_virtual_limit); // push maximum resources to go from idle back to congested as fast as possible - iterations = 0; - while (get_virtual_block_net_limit() > config::default_max_block_net_usage && iterations <= expected_contract_iterations) { + while( get_virtual_block_net_limit() > config::default_max_block_net_usage + && iterations <= expected_relax_iterations + expected_contract_iterations ) { add_transaction_usage({account},0, config::default_max_block_net_usage, iterations); process_block_usage(iterations++); } - BOOST_REQUIRE_EQUAL(iterations, expected_contract_iterations); + BOOST_REQUIRE_EQUAL(iterations, expected_relax_iterations + expected_contract_iterations); BOOST_REQUIRE_EQUAL(get_virtual_block_net_limit(), config::default_max_block_net_usage); } FC_LOG_AND_RETHROW(); /** * create 5 accounts with different weights, verify that the capacities are as expected and that usage properly enforces them */ + + +#warning restore weighted capacity cpu tests +#if 0 BOOST_FIXTURE_TEST_CASE(weighted_capacity_cpu, resource_limits_fixture) try { const vector weights = { 234, 511, 672, 800, 1213 }; const int64_t total = std::accumulate(std::begin(weights), std::end(weights), 0LL); @@ -163,7 +167,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) } // use too much, and expect failure; - BOOST_REQUIRE_THROW(add_transaction_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_resource_exhausted); + BOOST_REQUIRE_THROW(add_transaction_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_cpu_usage_exceeded); } } FC_LOG_AND_RETHROW(); @@ -195,9 +199,10 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) } // use too much, and expect failure; - BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_resource_exhausted); + BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_net_usage_exceeded); } } FC_LOG_AND_RETHROW(); +#endif BOOST_FIXTURE_TEST_CASE(enforce_block_limits_cpu, resource_limits_fixture) try { const account_name account(1); @@ -206,9 +211,9 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) process_account_limit_updates(); const uint64_t increment = 1000; - const uint64_t expected_iterations = (config::default_max_block_cpu_usage + increment - 1 ) / increment; + const uint64_t expected_iterations = config::default_max_block_cpu_usage / increment; - for (int idx = 0; idx < expected_iterations - 1; idx++) { + for (int idx = 0; idx < expected_iterations; idx++) { add_transaction_usage({account}, increment, 0, 0); } @@ -223,9 +228,9 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) process_account_limit_updates(); const uint64_t increment = 1000; - const uint64_t expected_iterations = (config::default_max_block_net_usage + increment - 1 ) / increment; + const uint64_t expected_iterations = config::default_max_block_net_usage / increment; - for (int idx = 0; idx < expected_iterations - 1; idx++) { + for (int idx = 0; idx < expected_iterations; idx++) { add_transaction_usage({account}, 0, increment, 0); } @@ -245,12 +250,36 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) process_account_limit_updates(); for (int idx = 0; idx < expected_iterations - 1; idx++) { - add_pending_account_ram_usage(account, increment); - synchronize_account_ram_usage( ); + add_pending_ram_usage(account, increment); + verify_account_ram_usage(account); } - add_pending_account_ram_usage(account, increment); - BOOST_REQUIRE_THROW(synchronize_account_ram_usage( ), tx_resource_exhausted); + add_pending_ram_usage(account, increment); + BOOST_REQUIRE_THROW(verify_account_ram_usage(account), ram_usage_exceeded); + } FC_LOG_AND_RETHROW(); + + BOOST_FIXTURE_TEST_CASE(enforce_account_ram_limit_underflow, resource_limits_fixture) try { + const account_name account(1); + initialize_account(account); + set_account_limits(account, 100, -1, -1 ); + verify_account_ram_usage(account); + process_account_limit_updates(); + BOOST_REQUIRE_THROW(add_pending_ram_usage(account, -101), transaction_exception); + + } FC_LOG_AND_RETHROW(); + + BOOST_FIXTURE_TEST_CASE(enforce_account_ram_limit_overflow, resource_limits_fixture) try { + const account_name account(1); + initialize_account(account); + set_account_limits(account, UINT64_MAX, -1, -1 ); + verify_account_ram_usage(account); + process_account_limit_updates(); + add_pending_ram_usage(account, UINT64_MAX/2); + verify_account_ram_usage(account); + add_pending_ram_usage(account, UINT64_MAX/2); + verify_account_ram_usage(account); + BOOST_REQUIRE_THROW(add_pending_ram_usage(account, 2), transaction_exception); + } FC_LOG_AND_RETHROW(); BOOST_FIXTURE_TEST_CASE(enforce_account_ram_commitment, resource_limits_fixture) try { @@ -264,15 +293,43 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) initialize_account(account); set_account_limits(account, limit, -1, -1 ); process_account_limit_updates(); - add_pending_account_ram_usage(account, commit); - synchronize_account_ram_usage( ); + add_pending_ram_usage(account, commit); + verify_account_ram_usage(account); for (int idx = 0; idx < expected_iterations - 1; idx++) { set_account_limits(account, limit - increment * idx, -1, -1); + verify_account_ram_usage(account); process_account_limit_updates(); } - BOOST_REQUIRE_THROW(set_account_limits(account, limit - increment * expected_iterations, -1, -1), wasm_execution_error); + set_account_limits(account, limit - increment * expected_iterations, -1, -1); + BOOST_REQUIRE_THROW(verify_account_ram_usage(account), ram_usage_exceeded); } FC_LOG_AND_RETHROW(); -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file + + BOOST_FIXTURE_TEST_CASE(sanity_check, resource_limits_fixture) try { + double total_staked_tokens = 1'000'000'000'0000.; + double user_stake = 1'0000.; + double max_block_cpu = 100000.; // us; + double blocks_per_day = 2*60*60*23; + double total_cpu_per_period = max_block_cpu * blocks_per_day * 3; + + double congested_cpu_time_per_period = total_cpu_per_period * user_stake / total_staked_tokens; + wdump((congested_cpu_time_per_period)); + double uncongested_cpu_time_per_period = (1000*total_cpu_per_period) * user_stake / total_staked_tokens; + wdump((uncongested_cpu_time_per_period)); + + + initialize_account( N(dan) ); + initialize_account( N(everyone) ); + set_account_limits( N(dan), 0, 0, 10000 ); + set_account_limits( N(everyone), 0, 0, 10000000000000ll ); + process_account_limit_updates(); + + add_transaction_usage( {N(dan)}, 10, 0, 1 ); /// dan should be able to do 10 us per 3 days + + + + } FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/special_accounts_tests.cpp b/unittests/special_accounts_tests.cpp similarity index 73% rename from tests/tests/special_accounts_tests.cpp rename to unittests/special_accounts_tests.cpp index 916e56832d7..9bdbc588e3a 100644 --- a/tests/tests/special_accounts_tests.cpp +++ b/unittests/special_accounts_tests.cpp @@ -7,9 +7,10 @@ #include #include -#include +#include #include #include +#include #include @@ -33,30 +34,30 @@ BOOST_FIXTURE_TEST_CASE(accounts_exists, tester) { try { tester test; - chain::chain_controller *control = test.control.get(); - chain::database &chain1_db = control->get_mutable_database(); + chain::controller *control = test.control.get(); + chain::database &chain1_db = control->db(); - auto nobody = chain1_db.find(config::nobody_account_name); + auto nobody = chain1_db.find(config::null_account_name); BOOST_CHECK(nobody != nullptr); - const auto& nobody_active_authority = chain1_db.get(boost::make_tuple(config::nobody_account_name, config::active_name)); - BOOST_CHECK_EQUAL(nobody_active_authority.auth.threshold, 0); + const auto& nobody_active_authority = chain1_db.get(boost::make_tuple(config::null_account_name, config::active_name)); + BOOST_CHECK_EQUAL(nobody_active_authority.auth.threshold, 1); BOOST_CHECK_EQUAL(nobody_active_authority.auth.accounts.size(), 0); BOOST_CHECK_EQUAL(nobody_active_authority.auth.keys.size(), 0); - const auto& nobody_owner_authority = chain1_db.get(boost::make_tuple(config::nobody_account_name, config::owner_name)); - BOOST_CHECK_EQUAL(nobody_owner_authority.auth.threshold, 0); + const auto& nobody_owner_authority = chain1_db.get(boost::make_tuple(config::null_account_name, config::owner_name)); + BOOST_CHECK_EQUAL(nobody_owner_authority.auth.threshold, 1); BOOST_CHECK_EQUAL(nobody_owner_authority.auth.accounts.size(), 0); BOOST_CHECK_EQUAL(nobody_owner_authority.auth.keys.size(), 0); auto producers = chain1_db.find(config::producers_account_name); BOOST_CHECK(producers != nullptr); - auto& gpo = chain1_db.get(); + const auto& active_producers = control->head_block_state()->active_schedule; const auto& producers_active_authority = chain1_db.get(boost::make_tuple(config::producers_account_name, config::active_name)); - auto expected_threshold = EOS_PERCENT_CEIL(gpo.active_producers.producers.size(), config::producers_authority_threshold_pct); + auto expected_threshold = (active_producers.producers.size() * 2)/3 + 1; BOOST_CHECK_EQUAL(producers_active_authority.auth.threshold, expected_threshold); - BOOST_CHECK_EQUAL(producers_active_authority.auth.accounts.size(), gpo.active_producers.producers.size()); + BOOST_CHECK_EQUAL(producers_active_authority.auth.accounts.size(), active_producers.producers.size()); BOOST_CHECK_EQUAL(producers_active_authority.auth.keys.size(), 0); std::vector active_auth; @@ -65,19 +66,21 @@ BOOST_FIXTURE_TEST_CASE(accounts_exists, tester) } std::vector diff; - for (int i = 0; i < std::max(active_auth.size(), gpo.active_producers.producers.size()); ++i) { + for (int i = 0; i < std::max(active_auth.size(), active_producers.producers.size()); ++i) { account_name n1 = i < active_auth.size() ? active_auth[i] : (account_name)0; - account_name n2 = i < gpo.active_producers.producers.size() ? gpo.active_producers.producers[i].producer_name : (account_name)0; + account_name n2 = i < active_producers.producers.size() ? active_producers.producers[i].producer_name : (account_name)0; if (n1 != n2) diff.push_back((uint64_t)n2 - (uint64_t)n1); } BOOST_CHECK_EQUAL(diff.size(), 0); const auto& producers_owner_authority = chain1_db.get(boost::make_tuple(config::producers_account_name, config::owner_name)); - BOOST_CHECK_EQUAL(producers_owner_authority.auth.threshold, 0); + BOOST_CHECK_EQUAL(producers_owner_authority.auth.threshold, 1); BOOST_CHECK_EQUAL(producers_owner_authority.auth.accounts.size(), 0); BOOST_CHECK_EQUAL(producers_owner_authority.auth.keys.size(), 0); + //TODO: Add checks on the other permissions of the producers account + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/tic_tac_toe_tests.cpp b/unittests/tic_tac_toe_tests.cpp similarity index 90% rename from tests/wasm_tests/tic_tac_toe_tests.cpp rename to unittests/tic_tac_toe_tests.cpp index 56c1182bc4b..216370ff1d1 100644 --- a/tests/wasm_tests/tic_tac_toe_tests.cpp +++ b/unittests/tic_tac_toe_tests.cpp @@ -4,7 +4,7 @@ #pragma GCC diagnostic pop #include #include -#include +#include #include #include @@ -27,7 +27,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; @@ -54,7 +53,7 @@ struct ttt_tester : TESTER { if(maybe_tid == nullptr) BOOST_FAIL("table for code=\"tic.tac.toe\" scope=\"" + scope.to_string() + "\" table=\"games\" does not exist"); - auto* o = control->get_database().find(boost::make_tuple(maybe_tid->id, key)); + auto* o = control->db().find(boost::make_tuple(maybe_tid->id, key)); if(o == nullptr) BOOST_FAIL("game for does not exist for challenger=\"" + key.to_string() + "\""); @@ -103,7 +102,7 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { ("host", "player1") ("by", "player1") ("mvt", mvt) - ), transaction_exception, assert_message_contains("not your turn")); + ), assert_exception, eosio_assert_message_starts_with("it's not your turn yet")); mvt = mutable_variant_object() ("row", 1) @@ -113,7 +112,7 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { ("host", "player1") ("by", "player2") ("mvt", mvt) - ), transaction_exception, assert_message_contains("not a valid movement")); + ), assert_exception, eosio_assert_message_starts_with("not a valid movement")); mvt = mutable_variant_object() ("row", 0) @@ -163,7 +162,7 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { ("host", "player1") ("by", "player2") ("mvt", mvt) - ), transaction_exception, assert_message_contains("the game has ended")); + ), assert_exception, eosio_assert_message_starts_with("the game has ended")); game current; chain.get_table_entry(current, N(tic.tac.toe), N(player1), N(games), N(player2)); @@ -182,13 +181,13 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { ("host", "player1") ("by", "player2") ("mvt", mvt) - ), transaction_exception, assert_message_contains("game doesn't exists")); + ), assert_exception, eosio_assert_message_starts_with("game doesn't exists")); BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(restart), N(player2), mutable_variant_object() ("challenger", "player2") ("host", "player1") ("by", "player2") - ), transaction_exception, assert_message_contains("game doesn't exists")); + ), assert_exception, eosio_assert_message_starts_with("game doesn't exists")); chain.push_action(N(tic.tac.toe), N(create), N(player2), mutable_variant_object() ("challenger", "player1") @@ -221,7 +220,7 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { ("host", "player2") ("by", "player2") ("mvt", mvt) - ), transaction_exception, assert_message_contains("not your turn")); + ), assert_exception, eosio_assert_message_starts_with("it's not your turn yet")); } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/wasm_tests.cpp b/unittests/wasm_tests.cpp similarity index 85% rename from tests/wasm_tests/wasm_tests.cpp rename to unittests/wasm_tests.cpp index c0f87e802ee..8bc66054e66 100644 --- a/tests/wasm_tests/wasm_tests.cpp +++ b/unittests/wasm_tests.cpp @@ -2,8 +2,9 @@ #include #include -#include +#include #include +#include #include #include #include @@ -37,7 +38,6 @@ using namespace eosio; using namespace eosio::chain; -using namespace eosio::chain::contracts; using namespace eosio::testing; using namespace fc; @@ -93,14 +93,14 @@ BOOST_FIXTURE_TEST_CASE( basic_test, TESTER ) try { set_transaction_headers(trx); trx.sign( get_private_key( N(asserter), "active" ), chain_id_type() ); auto result = push_transaction( trx ); - BOOST_CHECK_EQUAL(result.status, transaction_receipt::executed); - BOOST_CHECK_EQUAL(result.action_traces.size(), 1); - BOOST_CHECK_EQUAL(result.action_traces.at(0).receiver.to_string(), name(N(asserter)).to_string() ); - BOOST_CHECK_EQUAL(result.action_traces.at(0).act.account.to_string(), name(N(asserter)).to_string() ); - BOOST_CHECK_EQUAL(result.action_traces.at(0).act.name.to_string(), name(N(procassert)).to_string() ); - BOOST_CHECK_EQUAL(result.action_traces.at(0).act.authorization.size(), 1 ); - BOOST_CHECK_EQUAL(result.action_traces.at(0).act.authorization.at(0).actor.to_string(), name(N(asserter)).to_string() ); - BOOST_CHECK_EQUAL(result.action_traces.at(0).act.authorization.at(0).permission.to_string(), name(config::active_name).to_string() ); + BOOST_CHECK_EQUAL(result->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(result->action_traces.size(), 1); + BOOST_CHECK_EQUAL(result->action_traces.at(0).receipt.receiver.to_string(), name(N(asserter)).to_string() ); + BOOST_CHECK_EQUAL(result->action_traces.at(0).act.account.to_string(), name(N(asserter)).to_string() ); + BOOST_CHECK_EQUAL(result->action_traces.at(0).act.name.to_string(), name(N(procassert)).to_string() ); + BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.size(), 1 ); + BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).actor.to_string(), name(N(asserter)).to_string() ); + BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).permission.to_string(), name(config::active_name).to_string() ); no_assert_id = trx.id(); } @@ -120,7 +120,7 @@ BOOST_FIXTURE_TEST_CASE( basic_test, TESTER ) try { trx.sign( get_private_key( N(asserter), "active" ), chain_id_type() ); yes_assert_id = trx.id(); - BOOST_CHECK_THROW(push_transaction( trx ), transaction_exception); + BOOST_CHECK_THROW(push_transaction( trx ), assert_exception); } produce_blocks(1); @@ -130,51 +130,6 @@ BOOST_FIXTURE_TEST_CASE( basic_test, TESTER ) try { } FC_LOG_AND_RETHROW() /// basic_test -/** - * Make sure WASM doesn't allow function call depths greater than 250 - */ -BOOST_FIXTURE_TEST_CASE( call_depth_test, TESTER ) try { - produce_blocks(2); - create_accounts( {N(check)} ); - produce_block(); - // test that we can call to 249 - { - set_code(N(check), call_depth_almost_limit_wast); - produce_blocks(10); - - signed_transaction trx; - action act; - act.account = N(check); - act.name = N(); - act.authorization = vector{{N(check),config::active_name}}; - trx.actions.push_back(act); - - set_transaction_headers(trx); - trx.sign(get_private_key( N(check), "active" ), chain_id_type()); - push_transaction(trx); - produce_blocks(1); - BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); - } - // should fail at 250 - { - set_code(N(check), call_depth_limit_wast); - produce_blocks(10); - - signed_transaction trx; - action act; - act.account = N(check); - act.name = N(); - act.authorization = vector{{N(check),config::active_name}}; - trx.actions.push_back(act); - - set_transaction_headers(trx); - trx.sign(get_private_key( N(check), "active" ), chain_id_type()); - BOOST_CHECK_THROW( push_transaction(trx), wasm_execution_error ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL(false, chain_has_transaction(trx.id())); - } -} FC_LOG_AND_RETHROW() - /** * Prove the modifications to global variables are wiped between runs */ @@ -220,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE( abi_from_variant, TESTER ) try { auto resolver = [&,this]( const account_name& name ) -> optional { try { - const auto& accnt = this->control->get_database().get( name ); + const auto& accnt = this->control->db().get( name ); abi_def abi; if (abi_serializer::to_abi(accnt.abi, abi)) { return abi_serializer(abi); @@ -261,7 +216,6 @@ BOOST_FIXTURE_TEST_CASE( abi_from_variant, TESTER ) try { // test softfloat 32 bit operations BOOST_FIXTURE_TEST_CASE( f32_tests, TESTER ) try { produce_blocks(2); - account_name an = N(f_tests); create_accounts( {N(f32_tests)} ); produce_block(); { @@ -282,6 +236,11 @@ BOOST_FIXTURE_TEST_CASE( f32_tests, TESTER ) try { BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); const auto& receipt = get_transaction_receipt(trx.id()); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( f32_test_bitwise, TESTER ) try { + produce_blocks(2); + create_accounts( {N(f32_tests)} ); + produce_block(); { set_code(N(f32_tests), f32_bitwise_test_wast); produce_blocks(10); @@ -300,6 +259,11 @@ BOOST_FIXTURE_TEST_CASE( f32_tests, TESTER ) try { BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); const auto& receipt = get_transaction_receipt(trx.id()); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( f32_test_cmp, TESTER ) try { + produce_blocks(2); + create_accounts( {N(f32_tests)} ); + produce_block(); { set_code(N(f32_tests), f32_cmp_test_wast); produce_blocks(10); @@ -323,7 +287,6 @@ BOOST_FIXTURE_TEST_CASE( f32_tests, TESTER ) try { // test softfloat 64 bit operations BOOST_FIXTURE_TEST_CASE( f64_tests, TESTER ) try { produce_blocks(2); - create_accounts( {N(f_tests)} ); produce_block(); { @@ -344,6 +307,11 @@ BOOST_FIXTURE_TEST_CASE( f64_tests, TESTER ) try { BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); const auto& receipt = get_transaction_receipt(trx.id()); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( f64_test_bitwise, TESTER ) try { + produce_blocks(2); + create_accounts( {N(f_tests)} ); + produce_block(); { set_code(N(f_tests), f64_bitwise_test_wast); produce_blocks(10); @@ -362,6 +330,11 @@ BOOST_FIXTURE_TEST_CASE( f64_tests, TESTER ) try { BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); const auto& receipt = get_transaction_receipt(trx.id()); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( f64_test_cmp, TESTER ) try { + produce_blocks(2); + create_accounts( {N(f_tests)} ); + produce_block(); { set_code(N(f_tests), f64_cmp_test_wast); produce_blocks(10); @@ -448,14 +421,14 @@ BOOST_FIXTURE_TEST_CASE( f32_f64_overflow_tests, tester ) try { // the maximum value below 2^31 representable in IEEE float32 BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const 2147483520")); // -2^31 - BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483648")); + BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483648")); // the maximum value below -2^31 in IEEE float32 - BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483904")); + BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483904")); // //// float32 => uint32 - BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 0")); - BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const -1")); + BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 0")); + BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const -1")); // max value below 2^32 in IEEE float32 BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 4294967040")); BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 4294967296")); @@ -464,13 +437,13 @@ BOOST_FIXTURE_TEST_CASE( f32_f64_overflow_tests, tester ) try { //// double => int32 BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const 2147483648")); BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const 2147483647")); - BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483648")); - BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483649")); + BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483648")); + BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483649")); // //// double => uint32 - BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 0")); - BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const -1")); + BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 0")); + BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const -1")); BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 4294967295")); BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 4294967296")); @@ -481,16 +454,16 @@ BOOST_FIXTURE_TEST_CASE( f32_f64_overflow_tests, tester ) try { // the maximum value below 2^63 representable in IEEE float32 BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const 9223371487098961920")); // -2^63 - BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223372036854775808")); + BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223372036854775808")); // the maximum value below -2^63 in IEEE float32 - BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223373136366403584")); + BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223373136366403584")); //// float32 => uint64 BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const -1")); BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 0")); // max value below 2^64 in IEEE float32 - BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446742974197923840")); - BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446744073709551616")); + BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446742974197923840")); + BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446744073709551616")); //// double => int64 // 2^63 @@ -498,24 +471,24 @@ BOOST_FIXTURE_TEST_CASE( f32_f64_overflow_tests, tester ) try { // the maximum value below 2^63 representable in IEEE float64 BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const 9223372036854774784")); // -2^63 - BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854775808")); + BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854775808")); // the maximum value below -2^63 in IEEE float64 - BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854777856")); + BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854777856")); //// double => uint64 BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const -1")); BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 0")); // max value below 2^64 in IEEE float64 - BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709549568")); - BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709551616")); + BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709549568")); + BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709551616")); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE(misaligned_tests, tester ) try { produce_blocks(2); create_accounts( {N(aligncheck)} ); produce_block(); - - auto check_aligned = [&]( auto wast ) { + + auto check_aligned = [&]( auto wast ) { set_code(N(aligncheck), wast); produce_blocks(10); @@ -529,9 +502,8 @@ BOOST_FIXTURE_TEST_CASE(misaligned_tests, tester ) try { set_transaction_headers(trx); trx.sign(get_private_key( N(aligncheck), "active" ), chain_id_type()); push_transaction(trx); - auto sb = produce_block(); - block_trace trace(sb); - + produce_block(); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); }; @@ -539,14 +511,13 @@ BOOST_FIXTURE_TEST_CASE(misaligned_tests, tester ) try { check_aligned(misaligned_ref_wast); check_aligned(aligned_const_ref_wast); check_aligned(misaligned_const_ref_wast); - check_aligned(aligned_ptr_wast); - check_aligned(misaligned_ptr_wast); - check_aligned(misaligned_const_ptr_wast); } FC_LOG_AND_RETHROW() // test cpu usage -BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try { +/* Comment out this test due to not being robust to changes +BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try { +#warning This test does not appear to be very robust. create_accounts( {N(f_tests)} ); bool pass = false; @@ -557,12 +528,12 @@ BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try { (table 0 anyfunc) (memory $0 1) (export "apply" (func $apply)) - (func $i64_trunc_u_f64 (param $0 f64) (result i64) (i64.trunc_u/f64 (get_local $0))) - (func $test (param $0 i64)) + (func $test1 (param $0 i64)) + (func $test2 (param $0 i64) (result i64) (i64.add (get_local $0) (i64.const 32))) (func $apply (param $0 i64)(param $1 i64)(param $2 i64) )====="; for (int i = 0; i < 1024; ++i) { - code += "(call $test (call $i64_trunc_u_f64 (f64.const 1)))\n"; + code += "(call $test1 (call $test2(i64.const 1)))\n"; } code += "))"; @@ -570,8 +541,18 @@ BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try { set_code(N(f_tests), code.c_str()); produce_blocks(10); - int limit = 190; - while (!pass && limit < 200) { + uint32_t start = config::default_per_signature_cpu_usage + config::default_base_per_transaction_cpu_usage; + start += 100 * ( config::default_base_per_action_cpu_usage + + config::determine_payers_cpu_overhead_per_authorization + + config::base_check_authorization_cpu_per_authorization ); + start += config::resource_processing_cpu_overhead_per_billed_account; + start /= 1024; + start += 3077; // injected checktime amount + --start; + wdump((start)); + uint32_t end = start + 5; + uint32_t limit = start; + for( limit = start; limit < end; ++limit ) { signed_transaction trx; for (int i = 0; i < 100; ++i) { @@ -583,28 +564,28 @@ BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try { } set_transaction_headers(trx); - trx.max_kcpu_usage = limit++; + trx.max_cpu_usage_ms = limit++; trx.sign(get_private_key( N(f_tests), "active" ), chain_id_type()); try { push_transaction(trx); produce_blocks(1); BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); - pass = true; - } catch (eosio::chain::tx_resource_exhausted &) { - produce_blocks(1); + break; + } catch (eosio::chain::tx_cpu_usage_exceeded &) { } BOOST_REQUIRE_EQUAL(true, validate()); } -// NOTE: limit is 197 - BOOST_REQUIRE_EQUAL(true, limit > 190 && limit < 200); + wdump((limit)); + BOOST_CHECK_EQUAL(true, start < limit && limit < end); } FC_LOG_AND_RETHROW() +*/ // test weighted cpu limit BOOST_FIXTURE_TEST_CASE(weighted_cpu_limit_tests, tester ) try { - +#warning This test does not appear to be very robust. resource_limits_manager mgr = control->get_mutable_resource_limits_manager(); create_accounts( {N(f_tests)} ); create_accounts( {N(acc2)} ); @@ -635,7 +616,7 @@ BOOST_FIXTURE_TEST_CASE(weighted_cpu_limit_tests, tester ) try { while (count < 4) { signed_transaction trx; - for (int i = 0; i < 100; ++i) { + for (int i = 0; i < 10; ++i) { action act; act.account = N(f_tests); act.name = N() + (i * 16); @@ -647,15 +628,15 @@ BOOST_FIXTURE_TEST_CASE(weighted_cpu_limit_tests, tester ) try { trx.sign(get_private_key( N(f_tests), "active" ), chain_id_type()); try { - push_transaction(trx); + push_transaction(trx, fc::time_point::maximum(), 0); produce_blocks(1); BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); pass = true; count++; - } catch (eosio::chain::tx_resource_exhausted &) { + } catch( eosio::chain::leeway_deadline_exception& ) { BOOST_REQUIRE_EQUAL(count, 3); break; - } + } BOOST_REQUIRE_EQUAL(true, validate()); if (count == 2) { // add a big weight on acc2, making f_tests out of resource @@ -692,6 +673,31 @@ BOOST_FIXTURE_TEST_CASE( check_entry_behavior, TESTER ) try { BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( check_entry_behavior_2, TESTER ) try { + produce_blocks(2); + create_accounts( {N(entrycheck)} ); + produce_block(); + + set_code(N(entrycheck), entry_wast_2); + produce_blocks(10); + + signed_transaction trx; + action act; + act.account = N(entrycheck); + act.name = N(); + act.authorization = vector{{N(entrycheck),config::active_name}}; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( N(entrycheck), "active" ), chain_id_type()); + push_transaction(trx); + produce_blocks(1); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id())); + const auto& receipt = get_transaction_receipt(trx.id()); + BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status); +} FC_LOG_AND_RETHROW() + + /** * Ensure we can load a wasm w/o memory */ @@ -762,7 +768,7 @@ BOOST_FIXTURE_TEST_CASE( stl_test, TESTER ) try { set_abi(N(stltest), stltest_abi); produce_blocks(1); - const auto& accnt = control->get_database().get( N(stltest) ); + const auto& accnt = control->db().get( N(stltest) ); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_serializer abi_ser(abi); @@ -970,7 +976,7 @@ BOOST_FIXTURE_TEST_CASE(noop, TESTER) try { set_code(N(noop), noop_wast); set_abi(N(noop), noop_abi); - const auto& accnt = control->get_database().get(N(noop)); + const auto& accnt = control->db().get(N(noop)); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_serializer abi_ser(abi); @@ -1031,11 +1037,7 @@ BOOST_FIXTURE_TEST_CASE(noop, TESTER) try { BOOST_FIXTURE_TEST_CASE(eosio_abi, TESTER) try { produce_blocks(2); - set_code(config::system_account_name, eosio_system_wast); - set_abi(config::system_account_name, eosio_system_abi); - produce_block(); - - const auto& accnt = control->get_database().get(config::system_account_name); + const auto& accnt = control->db().get(config::system_account_name); abi_def abi; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true); abi_serializer abi_ser(abi); @@ -1045,12 +1047,11 @@ BOOST_FIXTURE_TEST_CASE(eosio_abi, TESTER) try { name a = N(alice); authority owner_auth = authority( get_public_key( a, "owner" ) ); trx.actions.emplace_back( vector{{config::system_account_name,config::active_name}}, - contracts::newaccount{ + newaccount{ .creator = config::system_account_name, .name = a, .owner = owner_auth, - .active = authority( get_public_key( a, "active" ) ), - .recovery = authority( get_public_key( a, "recovery" ) ), + .active = authority( get_public_key( a, "active" ) ) }); set_transaction_headers(trx); trx.sign( get_private_key( config::system_account_name, "active" ), chain_id_type() ); @@ -1059,7 +1060,7 @@ BOOST_FIXTURE_TEST_CASE(eosio_abi, TESTER) try { fc::variant pretty_output; // verify to_variant works on eos native contract type: newaccount // see abi_serializer::to_abi() - abi_serializer::to_variant(result, pretty_output, get_resolver()); + abi_serializer::to_variant(*result, pretty_output, get_resolver()); BOOST_TEST(fc::json::to_string(pretty_output).find("newaccount") != std::string::npos); @@ -1129,7 +1130,7 @@ BOOST_FIXTURE_TEST_CASE( check_table_maximum, TESTER ) try { trx.sign(get_private_key( N(tbl), "active" ), chain_id_type()); //should fail, a check to make sure assert() in wasm is being evaluated correctly - BOOST_CHECK_THROW(push_transaction(trx), transaction_exception); + BOOST_CHECK_THROW(push_transaction(trx), assert_exception); } produce_blocks(1); @@ -1247,7 +1248,7 @@ BOOST_FIXTURE_TEST_CASE( protected_globals, TESTER ) try { produce_blocks(1); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { +BOOST_FIXTURE_TEST_CASE( lotso_stack_1, TESTER ) try { produce_blocks(2); create_accounts( {N(stackz)} ); @@ -1266,6 +1267,13 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { set_code(N(stackz), ss.str().c_str()); produce_blocks(1); } +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( lotso_stack_2, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); { std::stringstream ss; ss << "(module "; @@ -1280,6 +1288,12 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { set_code(N(stackz), ss.str().c_str()); produce_blocks(1); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_3, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); //try to use contract with this many locals (so that it actually gets compiled). Note that //at this time not having an apply() is an acceptable non-error. @@ -1295,8 +1309,12 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { trx.sign(get_private_key( N(stackz), "active" ), chain_id_type()); push_transaction(trx); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_4, TESTER ) try { + produce_blocks(2); - + create_accounts( {N(stackz)} ); + produce_block(); //too many locals! should fail validation { std::stringstream ss; @@ -1311,6 +1329,12 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), fc::exception); produce_blocks(1); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_5, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); //try again but with parameters { @@ -1327,6 +1351,13 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { set_code(N(stackz), ss.str().c_str()); produce_blocks(1); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_6, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); + //try to use contract with this many params { signed_transaction trx; @@ -1340,6 +1371,12 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { trx.sign(get_private_key( N(stackz), "active" ), chain_id_type()); push_transaction(trx); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_7, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); //too many params! { @@ -1355,6 +1392,12 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), fc::exception); produce_blocks(1); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_8, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); //let's mix params and locals are make sure it's counted correctly in mixed case { @@ -1370,6 +1413,13 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { set_code(N(stackz), ss.str().c_str()); produce_blocks(1); } +} FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( lotso_stack_9, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(stackz)} ); + produce_block(); + { std::stringstream ss; ss << "(module "; @@ -1383,8 +1433,6 @@ BOOST_FIXTURE_TEST_CASE( lotso_stack, TESTER ) try { BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), fc::exception); produce_blocks(1); } - - } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( apply_export_and_signature, TESTER ) try { @@ -1401,22 +1449,19 @@ BOOST_FIXTURE_TEST_CASE( apply_export_and_signature, TESTER ) try { BOOST_FIXTURE_TEST_CASE( trigger_serialization_errors, TESTER) try { produce_blocks(2); - const vector proper_wasm = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x13, 0x04, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x00, 0x01, 0x7f, 0x60, - 0x00, 0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x1e, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0c, 0x65, 0x6f, 0x73, 0x69, 0x6f, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x03, 0x6e, 0x6f, 0x77, 0x00, 0x01, 0x03, 0x03, - 0x02, 0x02, 0x03, 0x04, 0x04, 0x01, 0x70, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x07, 0x1a, 0x03, 0x06, 0x6d, 0x65, 0x6d, - 0x6f, 0x72, 0x79, 0x02, 0x00, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x00, 0x02, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00, 0x03, - 0x08, 0x01, 0x02, 0x0a, 0x1a, 0x02, 0x09, 0x00, 0x41, 0x00, 0x10, 0x01, 0x36, 0x02, 0x04, 0x0b, 0x0e, 0x00, 0x41, 0x00, 0x28, - 0x02, 0x04, 0x10, 0x01, 0x46, 0x41, 0x00, 0x10, 0x00, 0x0b}; - - const vector malformed_wasm = {0x00, 0x61, 0x73, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x04, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x00, 0x01, 0x7f, 0x60, - 0x00, 0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x1e, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0c, 0x65, 0x6f, 0x73, 0x69, 0x6f, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x03, 0x6e, 0x6f, 0x77, 0x00, 0x01, 0x03, 0x03, - 0x02, 0x02, 0x03, 0x04, 0x04, 0x01, 0x70, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x07, 0x1a, 0x03, 0x06, 0x0d, 0x65, 0x0d, - 0x6f, 0x72, 0x79, 0x02, 0x00, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x00, 0x02, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00, 0x03, - 0x08, 0x01, 0x02, 0x0a, 0x1a, 0x02, 0x09, 0x00, 0x41, 0x00, 0x10, 0x01, 0x36, 0x02, 0x04, 0x0b, 0x0e, 0x00, 0x41, 0x00, 0x28, - 0x02, 0x04, 0x10, 0x01, 0x46, 0x41, 0x00, 0x10, 0x00, 0x0b}; - + const vector proper_wasm = { 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x60, 0x03, 0x7f, 0x7f, 0x7f, + 0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x73, + 0x68, 0x61, 0x32, 0x35, 0x36, 0x00, 0x00, 0x03, 0x02, 0x01, 0x01, 0x04, 0x04, 0x01, 0x70, 0x00, + 0x00, 0x05, 0x03, 0x01, 0x00, 0x20, 0x07, 0x09, 0x01, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00, + 0x01, 0x0a, 0x0c, 0x01, 0x0a, 0x00, 0x41, 0x04, 0x41, 0x05, 0x41, 0x10, 0x10, 0x00, 0x0b, 0x0b, + 0x0b, 0x01, 0x00, 0x41, 0x04, 0x0b, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f }; + + const vector malformed_wasm = { 0x00, 0x61, 0x03, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x60, 0x03, 0x7f, 0x7f, 0x7f, + 0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x73, + 0x68, 0x61, 0x32, 0x38, 0x36, 0x00, 0x00, 0x03, 0x03, 0x01, 0x01, 0x04, 0x04, 0x01, 0x70, 0x00, + 0x00, 0x05, 0x03, 0x01, 0x00, 0x20, 0x07, 0x09, 0x01, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00, + 0x01, 0x0a, 0x0c, 0x01, 0x0a, 0x00, 0x41, 0x04, 0x41, 0x05, 0x41, 0x10, 0x10, 0x00, 0x0b, 0x0b, + 0x0b, 0x01, 0x00, 0x41, 0x04, 0x0b, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f }; create_accounts( {N(bbb)} ); produce_block(); @@ -1436,6 +1481,9 @@ BOOST_FIXTURE_TEST_CASE( protect_injected, TESTER ) try { produce_blocks(1); } FC_LOG_AND_RETHROW() + +#warning restore net_usage_tests +#if 0 BOOST_FIXTURE_TEST_CASE(net_usage_tests, tester ) try { int count = 0; auto check = [&](int coderepeat, int max_net_usage)-> bool { @@ -1461,7 +1509,7 @@ BOOST_FIXTURE_TEST_CASE(net_usage_tests, tester ) try { signed_transaction trx; auto wasm = ::eosio::chain::wast_to_wasm(code); trx.actions.emplace_back( vector{{account,config::active_name}}, - contracts::setcode{ + setcode{ .account = account, .vmtype = 0, .vmversion = 0, @@ -1471,17 +1519,18 @@ BOOST_FIXTURE_TEST_CASE(net_usage_tests, tester ) try { if (max_net_usage) trx.max_net_usage_words = max_net_usage; trx.sign( get_private_key( account, "active" ), chain_id_type() ); try { - push_transaction(trx); + packed_transaction ptrx(trx); + push_transaction(ptrx); produce_blocks(1); return true; - } catch (tx_resource_exhausted &) { + } catch (tx_net_usage_exceeded &) { return false; } catch (transaction_exception &) { return false; } }; BOOST_REQUIRE_EQUAL(true, check(1024, 0)); // default behavior - BOOST_REQUIRE_EQUAL(false, check(1024, 1000)); // transaction max_net_usage too small + BOOST_REQUIRE_EQUAL(false, check(1024, 100)); // transaction max_net_usage too small BOOST_REQUIRE_EQUAL(false, check(10240, 0)); // larger than global maximum } FC_LOG_AND_RETHROW() @@ -1513,7 +1562,7 @@ BOOST_FIXTURE_TEST_CASE(weighted_net_usage_tests, tester ) try { signed_transaction trx; auto wasm = ::eosio::chain::wast_to_wasm(code); trx.actions.emplace_back( vector{{account,config::active_name}}, - contracts::setcode{ + setcode{ .account = account, .vmtype = 0, .vmversion = 0, @@ -1522,10 +1571,11 @@ BOOST_FIXTURE_TEST_CASE(weighted_net_usage_tests, tester ) try { set_transaction_headers(trx); trx.sign( get_private_key( account, "active" ), chain_id_type() ); try { - push_transaction(trx); + packed_transaction ptrx(trx); + push_transaction(ptrx ); produce_blocks(1); return true; - } catch (tx_resource_exhausted &) { + } catch (tx_net_usage_exceeded &) { return false; } }; @@ -1534,11 +1584,12 @@ BOOST_FIXTURE_TEST_CASE(weighted_net_usage_tests, tester ) try { resource_limits_manager mgr = control->get_mutable_resource_limits_manager(); mgr.set_account_limits(account, -1, 1, -1); // set weight = 1 for account - BOOST_REQUIRE_EQUAL(true, check(128)); + BOOST_REQUIRE_EQUAL(true, check(128)); mgr.set_account_limits(acc2, -1, 1000, -1); // set a big weight for other account BOOST_REQUIRE_EQUAL(false, check(128)); } FC_LOG_AND_RETHROW() +#endif BOOST_AUTO_TEST_SUITE_END()