diff --git a/.gitignore b/.gitignore index d5de0097..c4d159d2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ compile_commands.json libraries/utilities/git_revision.cpp +libraries/wallet/Doxyfile +libraries/wallet/api_documentation.cpp +libraries/wallet/doxygen + programs/cli_wallet/cli_wallet programs/js_operation_serializer/js_operation_serializer programs/witness_node/witness_node @@ -23,3 +27,12 @@ doxygen wallet.json witness_node_data_dir + +*.wallet + +programs/witness_node/object_database/* + +object_database/* + +*.pyc +*.pyo diff --git a/.gitmodules b/.gitmodules index 9680fb37..425e818f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,11 +1,8 @@ [submodule "docs"] path = docs url = https://github.com/cryptonomex/graphene.wiki.git + ignore = dirty [submodule "libraries/fc"] path = libraries/fc url = git@git.syncad.com:/fc.git ignore = dirty -[submodule "libraries/leveldb"] - path = libraries/leveldb - url = https://github.com/bitcoin/leveldb.git - ignore = dirty diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f231365..09e42a8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ IF( WIN32 ) set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries ENDIF(WIN32) -FIND_PACKAGE(Boost 1.53 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +FIND_PACKAGE(Boost 1.57 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) # For Boost 1.53 on windows, coroutine was not in BOOST_LIBRARYDIR and do not need it to build, but if boost versin >= 1.54, find coroutine otherwise will cause link errors IF(NOT "${Boost_VERSION}" MATCHES "1.53(.*)") SET(BOOST_LIBRARIES_TEMP ${Boost_LIBRARIES}) @@ -65,22 +65,8 @@ IF(NOT "${Boost_VERSION}" MATCHES "1.53(.*)") SET(Boost_LIBRARIES ${BOOST_LIBRARIES_TEMP} ${Boost_LIBRARIES}) ENDIF() -set( LEVEL_DB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/leveldb" ) - -file( GLOB LEVEL_DB_SOURCES "${LEVEL_DB_DIR}/db/*.cc" - "${LEVEL_DB_DIR}/helpers/memenv/memenv.cc" - "${LEVEL_DB_DIR}/table/*.cc" - "${LEVEL_DB_DIR}/util/*.cc" ) -foreach( filename ${LEVEL_DB_SOURCES} ) - if( ${filename} MATCHES ".*_test.cc" OR ${filename} MATCHES ".*_bench.cc" OR ${filename} MATCHES ".*_main.cc" ) - list( REMOVE_ITEM LEVEL_DB_SOURCES ${filename} ) - endif() -endforeach() -set(LEVELDB_BUILD_DEFINES) -set(LEVELDB_BUILD_LIBRARIES) -set(LEVELDB_BUILD_PRIVATE_INCLUDES "${LEVEL_DB_DIR}") - if( WIN32 ) + message( STATUS "Configuring Graphene on WIN32") set( DB_VERSION 60 ) set( BDB_STATIC_LIBS 1 ) @@ -112,20 +98,7 @@ if( WIN32 ) SET(TCL_LIBS "${TCL_LIBS}${TCL_LIB_PATH}/${TCL_LIB_NAME}g${TCL_LIB_EXT}") SET(TCL_LIBRARY ${TCL_LIBS}) - SET(LEVELDB_PORT_FILE "${LEVEL_DB_DIR}/port/port_win.cc" ) - list(APPEND LEVELDB_BUILD_DEFINES OS_WINDOWS LEVELDB_PLATFORM_WINDOWS ) - list(APPEND LEVELDB_BUILD_LIBRARIES shlwapi.lib) - list(INSERT LEVELDB_BUILD_PRIVATE_INCLUDES 0 "${CMAKE_CURRENT_SOURCE_DIR}/libraries/leveldb-msvc/include") else( WIN32 ) # Apple AND Linux - SET(LEVELDB_PORT_FILE "${LEVEL_DB_DIR}/port/port_posix.cc" ) - - list(APPEND LEVELDB_BUILD_DEFINES LEVELDB_PLATFORM_POSIX LEVELDB_ATOMIC_PRESENT) - if( APPLE ) - list(APPEND LEVELDB_BUILD_DEFINES OS_MACOSX) - else() # Linux - list(APPEND LEVELDB_BUILD_DEFINES OS_LINUX) - list(APPEND LEVELDB_BUILD_LIBRARIES pthread) - endif() find_library(READLINE_LIBRARIES NAMES readline) find_path(READLINE_INCLUDE_DIR readline/readline.h) @@ -169,13 +142,6 @@ else( WIN32 ) # Apple AND Linux endif( WIN32 ) -list(APPEND LEVEL_DB_SOURCES "${LEVELDB_PORT_FILE}") -add_library( leveldb ${LEVEL_DB_SOURCES} ) -target_link_libraries( leveldb ${LEVELDB_BUILD_LIBRARIES} ) -target_include_directories( leveldb PRIVATE ${LEVELDB_BUILD_PRIVATE_INCLUDES} - PUBLIC "${LEVEL_DB_DIR}/include" ) -set_target_properties(leveldb PROPERTIES COMPILE_DEFINITIONS "${LEVELDB_BUILD_DEFINES}") - find_package( BerkeleyDB ) set(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL "Build Graphene for code coverage analysis") diff --git a/CMakeModules/cotire.cmake b/CMakeModules/cotire.cmake index ddfe4bfd..0df9a4a2 100644 --- a/CMakeModules/cotire.cmake +++ b/CMakeModules/cotire.cmake @@ -3,7 +3,7 @@ # See the cotire manual for usage hints. # #============================================================================= -# Copyright 2012-2014 Sascha Kratky +# Copyright 2012-2015 Sascha Kratky # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -37,49 +37,81 @@ set(__COTIRE_INCLUDED TRUE) if (NOT CMAKE_SCRIPT_MODE_FILE) cmake_policy(PUSH) endif() -# we need the CMake variables CMAKE_SCRIPT_MODE_FILE and CMAKE_ARGV available since 2.8.5 -# we need APPEND_STRING option for set_property available since 2.8.6 -cmake_minimum_required(VERSION 2.8.6) +cmake_minimum_required(VERSION 2.8.12) if (NOT CMAKE_SCRIPT_MODE_FILE) cmake_policy(POP) endif() +# activate select policies +if (POLICY CMP0038) + # targets may not link directly to themselves + cmake_policy(SET CMP0038 NEW) +endif() + +if (POLICY CMP0039) + # utility targets may not have link dependencies + cmake_policy(SET CMP0039 NEW) +endif() + +if (POLICY CMP0040) + # target in the TARGET signature of add_custom_command() must exist + cmake_policy(SET CMP0040 NEW) +endif() + +if (POLICY CMP0045) + # error on non-existent target in get_target_property + cmake_policy(SET CMP0045 NEW) +endif() + +if (POLICY CMP0046) + # error on non-existent dependency in add_dependencies + cmake_policy(SET CMP0046 NEW) +endif() + +if (POLICY CMP0049) + # do not expand variables in target source entries + cmake_policy(SET CMP0049 NEW) +endif() + +if (POLICY CMP0050) + # disallow add_custom_command SOURCE signatures + cmake_policy(SET CMP0050 NEW) +endif() + +if (POLICY CMP0051) + # include TARGET_OBJECTS expressions in a target's SOURCES property + cmake_policy(SET CMP0051 NEW) +endif() + +if (POLICY CMP0053) + # simplify variable reference and escape sequence evaluation + cmake_policy(SET CMP0053 NEW) +endif() + +if (POLICY CMP0054) + # only interpret if() arguments as variables or keywords when unquoted + cmake_policy(SET CMP0054 NEW) +endif() + set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}") -set (COTIRE_CMAKE_MODULE_VERSION "1.6.1") +set (COTIRE_CMAKE_MODULE_VERSION "1.7.2") include(CMakeParseArguments) include(ProcessorCount) -function (cotire_determine_compiler_version _language _versionPrefix) - if (NOT ${_versionPrefix}_VERSION) - # use CMake's predefined compiler version variable (available since CMake 2.8.8) - if (DEFINED CMAKE_${_language}_COMPILER_VERSION) - set (${_versionPrefix}_VERSION "${CMAKE_${_language}_COMPILER_VERSION}") - elseif (WIN32) - # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared - unset (ENV{VS_UNICODE_OUTPUT}) - string (STRIP "${CMAKE_${_language}_COMPILER_ARG1}" _compilerArg1) - execute_process (COMMAND ${CMAKE_${_language}_COMPILER} ${_compilerArg1} - ERROR_VARIABLE _versionLine OUTPUT_QUIET TIMEOUT 10) - string (REGEX REPLACE ".*Version *([0-9]+(\\.[0-9]+)*).*" "\\1" ${_versionPrefix}_VERSION "${_versionLine}") - else() - # assume GCC like command line interface - string (STRIP "${CMAKE_${_language}_COMPILER_ARG1}" _compilerArg1) - execute_process (COMMAND ${CMAKE_${_language}_COMPILER} ${_compilerArg1} "-dumpversion" - OUTPUT_VARIABLE ${_versionPrefix}_VERSION - RESULT_VARIABLE _result - OUTPUT_STRIP_TRAILING_WHITESPACE TIMEOUT 10) - if (_result) - set (${_versionPrefix}_VERSION "") - endif() - endif() - if (${_versionPrefix}_VERSION) - set (${_versionPrefix}_VERSION "${${_versionPrefix}_VERSION}" CACHE INTERNAL "${_language} compiler version") - endif() - set (${_versionPrefix}_VERSION "${${_versionPrefix}_VERSION}" PARENT_SCOPE) - if (COTIRE_DEBUG) - message (STATUS "${CMAKE_${_language}_COMPILER} version ${${_versionPrefix}_VERSION}") - endif() +function (cotire_get_configuration_types _configsVar) + set (_configs "") + if (CMAKE_CONFIGURATION_TYPES) + list (APPEND _configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + if (CMAKE_BUILD_TYPE) + list (APPEND _configs "${CMAKE_BUILD_TYPE}") + endif() + if (_configs) + list (REMOVE_DUPLICATES _configs) + set (${_configsVar} ${_configs} PARENT_SCOPE) + else() + set (${_configsVar} "None" PARENT_SCOPE) endif() endfunction() @@ -109,10 +141,7 @@ macro (cotire_check_is_path_relative_to _path _isRelativeVar) endif() endmacro() -function (cotire_filter_language_source_files _language _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) - set (_sourceFiles "") - set (_excludedSourceFiles "") - set (_cotiredSourceFiles "") +function (cotire_filter_language_source_files _language _target _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}") else() @@ -128,17 +157,29 @@ function (cotire_filter_language_source_files _language _sourceFilesVar _exclude else() set (_excludeExtensions "") endif() - if (COTIRE_DEBUG) + if (COTIRE_DEBUG AND _languageExtensions) message (STATUS "${_language} source file extensions: ${_languageExtensions}") + endif() + if (COTIRE_DEBUG AND _ignoreExtensions) message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}") + endif() + if (COTIRE_DEBUG AND _excludeExtensions) message (STATUS "${_language} exclude extensions: ${_excludeExtensions}") endif() - foreach (_sourceFile ${ARGN}) + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_allSourceFiles ${ARGN}) + else() + # as of CMake 3.1 target sources may contain generator expressions + # since we cannot obtain required property information about source files added + # through generator expressions at configure time, we filter them out + string (GENEX_STRIP "${ARGN}" _allSourceFiles) + endif() + set (_filteredSourceFiles "") + set (_excludedSourceFiles "") + foreach (_sourceFile ${_allSourceFiles}) get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY) get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT) get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC) - get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) - set (_sourceIsFiltered FALSE) if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic) cotire_get_source_file_extension("${_sourceFile}" _sourceExt) if (_sourceExt) @@ -150,28 +191,37 @@ function (cotire_filter_language_source_files _language _sourceFilesVar _exclude else() list (FIND _languageExtensions "${_sourceExt}" _sourceIndex) if (_sourceIndex GREATER -1) - set (_sourceIsFiltered TRUE) - elseif ("${_sourceLanguage}" STREQUAL "${_language}") - # add to excluded sources, if file is not ignored and has correct language without having the correct extension - list (APPEND _excludedSourceFiles "${_sourceFile}") + # consider source file unless it is excluded explicitly + get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) + if (_sourceIsExcluded) + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (APPEND _filteredSourceFiles "${_sourceFile}") + endif() + else() + get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) + if ("${_sourceLanguage}" STREQUAL "${_language}") + # add to excluded sources, if file is not ignored and has correct language without having the correct extension + list (APPEND _excludedSourceFiles "${_sourceFile}") + endif() endif() endif() endif() endif() endif() - if (COTIRE_DEBUG) - message (STATUS "${_sourceFile} filtered=${_sourceIsFiltered} language=${_sourceLanguage} header=${_sourceIsHeaderOnly}") - endif() - if (_sourceIsFiltered) - get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) - get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) + endforeach() + # separate filtered source files from already cotired ones + # the COTIRE_TARGET property of a source file may be set while a target is being processed by cotire + set (_sourceFiles "") + set (_cotiredSourceFiles "") + foreach (_sourceFile ${_filteredSourceFiles}) + get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) + if (_sourceIsCotired) + list (APPEND _cotiredSourceFiles "${_sourceFile}") + else() get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS) - if (COTIRE_DEBUG) - message (STATUS "${_sourceFile} excluded=${_sourceIsExcluded} cotired=${_sourceIsCotired} compileFlags=${_sourceCompileFlags}") - endif() - if (_sourceIsCotired) - list (APPEND _cotiredSourceFiles "${_sourceFile}") - elseif (_sourceIsExcluded OR _sourceCompileFlags) + if (_sourceCompileFlags) + # add to excluded sources, if file has custom compile flags list (APPEND _excludedSourceFiles "${_sourceFile}") else() list (APPEND _sourceFiles "${_sourceFile}") @@ -179,10 +229,15 @@ function (cotire_filter_language_source_files _language _sourceFilesVar _exclude endif() endforeach() if (COTIRE_DEBUG) - message (STATUS "All: ${ARGN}") - message (STATUS "${_language}: ${_sourceFiles}") - message (STATUS "Excluded: ${_excludedSourceFiles}") - message (STATUS "Cotired: ${_cotiredSourceFiles}") + if (_sourceFiles) + message (STATUS "Filtered ${_target} ${_language} sources: ${_sourceFiles}") + endif() + if (_excludedSourceFiles) + message (STATUS "Excluded ${_target} ${_language} sources: ${_excludedSourceFiles}") + endif() + if (_cotiredSourceFiles) + message (STATUS "Cotired ${_target} ${_language} sources: ${_cotiredSourceFiles}") + endif() endif() set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE) set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE) @@ -255,6 +310,29 @@ function (cotire_copy_set_properites _configurations _type _source _target) endforeach() endfunction() +function (cotire_get_target_usage_requirements _target _targetRequirementsVar) + set (_targetRequirements "") + get_target_property(_librariesToProcess ${_target} LINK_LIBRARIES) + while (_librariesToProcess) + # remove from head + list (GET _librariesToProcess 0 _library) + list (REMOVE_AT _librariesToProcess 0) + if (TARGET ${_library}) + list (FIND _targetRequirements ${_library} _index) + if (_index LESS 0) + list (APPEND _targetRequirements ${_library}) + # process transitive libraries + get_target_property(_libraries ${_library} INTERFACE_LINK_LIBRARIES) + if (_libraries) + list (APPEND _librariesToProcess ${_libraries}) + list (REMOVE_DUPLICATES _librariesToProcess) + endif() + endif() + endif() + endwhile() + set (${_targetRequirementsVar} ${_targetRequirements} PARENT_SCOPE) +endfunction() + function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar) if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") set (_flagPrefix "[/-]") @@ -291,20 +369,17 @@ function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _ if (_optionFlag) list (APPEND _matchedOptions "${_optionFlag}") endif() - if (COTIRE_DEBUG) - message (STATUS "Filter ${_flagFilter}") - if (_matchedOptions) - message (STATUS "Matched ${_matchedOptions}") - endif() - if (_unmatchedOptions) - message (STATUS "Unmatched ${_unmatchedOptions}") - endif() + if (COTIRE_DEBUG AND _matchedOptions) + message (STATUS "Filter ${_flagFilter} matched: ${_matchedOptions}") + endif() + if (COTIRE_DEBUG AND _unmatchedOptions) + message (STATUS "Filter ${_flagFilter} unmatched: ${_unmatchedOptions}") endif() set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE) set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE) endfunction() -function (cotire_get_target_compile_flags _config _language _directory _target _flagsVar) +function (cotire_get_target_compile_flags _config _language _target _flagsVar) string (TOUPPER "${_config}" _upperConfig) # collect options from CMake language variables set (_compileFlags "") @@ -315,57 +390,11 @@ function (cotire_get_target_compile_flags _config _language _directory _target _ set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}") endif() if (_target) - # add option from CMake target type variable - get_target_property(_targetType ${_target} TYPE) - if (POLICY CMP0018) - # handle POSITION_INDEPENDENT_CODE property introduced with CMake 2.8.9 if policy CMP0018 is turned on - cmake_policy(GET CMP0018 _PIC_Policy) - else() - # default to old behavior - set (_PIC_Policy "OLD") - endif() - if (COTIRE_DEBUG) - message(STATUS "CMP0018=${_PIC_Policy}") - endif() - if (_PIC_Policy STREQUAL "NEW") - # NEW behavior: honor the POSITION_INDEPENDENT_CODE target property - get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) - if (_targetPIC) - if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") - elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") - endif() - endif() - else() - # OLD behavior or policy not set: use the value of CMAKE_SHARED_LIBRARY__FLAGS - if (_targetType STREQUAL "MODULE_LIBRARY") - # flags variable for module library uses different name SHARED_MODULE - # (e.g., CMAKE_SHARED_MODULE_C_FLAGS) - set (_targetType SHARED_MODULE) - endif() - if (CMAKE_${_targetType}_${_language}_FLAGS) - set (_compileFlags "${_compileFlags} ${CMAKE_${_targetType}_${_language}_FLAGS}") - endif() - endif() - endif() - if (_directory) - # add_definitions may have been used to add flags to the compiler command - get_directory_property(_dirDefinitions DIRECTORY "${_directory}" DEFINITIONS) - if (_dirDefinitions) - set (_compileFlags "${_compileFlags} ${_dirDefinitions}") - endif() - endif() - if (_target) - # add target compile options + # add target compile flags get_target_property(_targetflags ${_target} COMPILE_FLAGS) if (_targetflags) set (_compileFlags "${_compileFlags} ${_targetflags}") endif() - get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) - if (_targetOptions) - set (_compileFlags "${_compileFlags} ${_targetOptions}") - endif() endif() if (UNIX) separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}") @@ -374,15 +403,47 @@ function (cotire_get_target_compile_flags _config _language _directory _target _ else() separate_arguments(_compileFlags) endif() + # target compile options + if (_target) + get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endif() + # interface compile options from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetOptions ${_linkedTarget} INTERFACE_COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endforeach() + endif() + # handle the POSITION_INDEPENDENT_CODE target property + if (_target) + get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) + if (_targetPIC) + get_target_property(_targetType ${_target} TYPE) + if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") + elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") + endif() + endif() + endif() # platform specific flags if (APPLE) get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig}) if (NOT _architectures) get_target_property(_architectures ${_target} OSX_ARCHITECTURES) endif() - foreach (_arch ${_architectures}) - list (APPEND _compileFlags "-arch" "${_arch}") - endforeach() + if (_architectures) + foreach (_arch ${_architectures}) + list (APPEND _compileFlags "-arch" "${_arch}") + endforeach() + endif() if (CMAKE_OSX_SYSROOT) if (CMAKE_${_language}_SYSROOT_FLAG) list (APPEND _compileFlags "${CMAKE_${_language}_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") @@ -399,33 +460,55 @@ function (cotire_get_target_compile_flags _config _language _directory _target _ endif() endif() if (COTIRE_DEBUG AND _compileFlags) - message (STATUS "Target ${_target} compile flags ${_compileFlags}") + message (STATUS "Target ${_target} compile flags: ${_compileFlags}") endif() set (${_flagsVar} ${_compileFlags} PARENT_SCOPE) endfunction() -function (cotire_get_target_include_directories _config _language _targetSourceDir _targetBinaryDir _target _includeDirsVar) +function (cotire_get_target_include_directories _config _language _target _includeDirsVar _systemIncludeDirsVar) set (_includeDirs "") + set (_systemIncludeDirs "") # default include dirs if (CMAKE_INCLUDE_CURRENT_DIR) - list (APPEND _includeDirs "${_targetBinaryDir}") - list (APPEND _includeDirs "${_targetSourceDir}") + list (APPEND _includeDirs "${CMAKE_CURRENT_BINARY_DIR}") + list (APPEND _includeDirs "${CMAKE_CURRENT_SOURCE_DIR}") endif() # parse additional include directories from target compile flags set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_targetSourceDir}" "${_target}" _targetFlags) + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) cotire_filter_compile_flags("${_language}" "I" _dirs _ignore ${_targetFlags}) if (_dirs) list (APPEND _includeDirs ${_dirs}) endif() # target include directories - get_directory_property(_dirs DIRECTORY "${_targetSourceDir}" INCLUDE_DIRECTORIES) + get_directory_property(_dirs DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" INCLUDE_DIRECTORIES) if (_target) get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES) if (_targetDirs) list (APPEND _dirs ${_targetDirs}) - list (REMOVE_DUPLICATES _dirs) endif() + get_target_property(_targetDirs ${_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endif() + # interface include directories from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _dirs ${_targetDirs}) + endif() + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endforeach() + endif() + if (dirs) + list (REMOVE_DUPLICATES _dirs) endif() list (LENGTH _includeDirs _projectInsertIndex) foreach (_dir ${_dirs}) @@ -447,26 +530,19 @@ function (cotire_get_target_include_directories _config _language _targetSourceD endif() endforeach() list (REMOVE_DUPLICATES _includeDirs) + list (REMOVE_DUPLICATES _systemIncludeDirs) if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES) list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES}) endif() if (COTIRE_DEBUG AND _includeDirs) - message (STATUS "Target ${_target} include dirs ${_includeDirs}") + message (STATUS "Target ${_target} include dirs: ${_includeDirs}") endif() set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE) -endfunction() - -macro (cotire_make_C_identifier _identifierVar _str) - if (CMAKE_VERSION VERSION_LESS "2.8.12") - # mimic CMake SystemTools::MakeCindentifier behavior - if ("${_str}" MATCHES "^[0-9].+$") - set (_str "_${str}") - endif() - string (REGEX REPLACE "[^a-zA-Z0-9]" "_" ${_identifierVar} "${_str}") - else() - string (MAKE_C_IDENTIFIER "${_identifierVar}" "${_str}") + if (COTIRE_DEBUG AND _systemIncludeDirs) + message (STATUS "Target ${_target} system include dirs: ${_systemIncludeDirs}") endif() -endmacro() + set (${_systemIncludeDirsVar} ${_systemIncludeDirs} PARENT_SCOPE) +endfunction() function (cotire_get_target_export_symbol _target _exportSymbolVar) set (_exportSymbol "") @@ -478,12 +554,12 @@ function (cotire_get_target_export_symbol _target _exportSymbolVar) if (NOT _exportSymbol) set (_exportSymbol "${_target}_EXPORTS") endif() - cotire_make_C_identifier(_exportSymbol "${_exportSymbol}") + string (MAKE_C_IDENTIFIER "${_exportSymbol}" _exportSymbol) endif() set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE) endfunction() -function (cotire_get_target_compile_definitions _config _language _directory _target _definitionsVar) +function (cotire_get_target_compile_definitions _config _language _target _definitionsVar) string (TOUPPER "${_config}" _upperConfig) set (_configDefinitions "") # CMAKE_INTDIR for multi-configuration build systems @@ -496,11 +572,11 @@ function (cotire_get_target_compile_definitions _config _language _directory _ta list (APPEND _configDefinitions "${_defineSymbol}") endif() # directory compile definitions - get_directory_property(_definitions DIRECTORY "${_directory}" COMPILE_DEFINITIONS) + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS) if (_definitions) list (APPEND _configDefinitions ${_definitions}) endif() - get_directory_property(_definitions DIRECTORY "${_directory}" COMPILE_DEFINITIONS_${_upperConfig}) + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS_${_upperConfig}) if (_definitions) list (APPEND _configDefinitions ${_definitions}) endif() @@ -513,29 +589,38 @@ function (cotire_get_target_compile_definitions _config _language _directory _ta if (_definitions) list (APPEND _configDefinitions ${_definitions}) endif() + # interface compile definitions from linked library targets + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_definitions ${_linkedTarget} INTERFACE_COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + endforeach() # parse additional compile definitions from target compile flags # and don't look at directory compile definitions, which we already handled set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "" "${_target}" _targetFlags) + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags}) if (_definitions) list (APPEND _configDefinitions ${_definitions}) endif() list (REMOVE_DUPLICATES _configDefinitions) if (COTIRE_DEBUG AND _configDefinitions) - message (STATUS "Target ${_target} compile definitions ${_configDefinitions}") + message (STATUS "Target ${_target} compile definitions: ${_configDefinitions}") endif() set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) endfunction() -function (cotire_get_target_compiler_flags _config _language _directory _target _compilerFlagsVar) +function (cotire_get_target_compiler_flags _config _language _target _compilerFlagsVar) # parse target compile flags omitting compile definitions and include directives set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_directory}" "${_target}" _targetFlags) + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) set (_compilerFlags "") cotire_filter_compile_flags("${_language}" "[ID]" _ignore _compilerFlags ${_targetFlags}) if (COTIRE_DEBUG AND _compilerFlags) - message (STATUS "Target ${_target} compiler flags ${_compilerFlags}") + message (STATUS "Target ${_target} compiler flags: ${_compilerFlags}") endif() set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE) endfunction() @@ -554,9 +639,6 @@ function (cotire_add_sys_root_paths _pathsVar) endif() endif() set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE) - if (COTIRE_DEBUG) - message (STATUS "${_pathsVar}=${${_pathsVar}}") - endif() endfunction() function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar) @@ -599,7 +681,7 @@ function (cotire_get_source_compile_definitions _config _language _sourceFile _d list (APPEND _compileDefinitions ${_definitions}) endif() if (COTIRE_DEBUG AND _compileDefinitions) - message (STATUS "Source ${_sourceFile} compile definitions ${_compileDefinitions}") + message (STATUS "Source ${_sourceFile} compile definitions: ${_compileDefinitions}") endif() set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE) endfunction() @@ -628,7 +710,7 @@ function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar) list (APPEND _sourceUndefs ${_undefs}) endif() if (COTIRE_DEBUG AND _sourceUndefs) - message (STATUS "Source ${_sourceFile} ${_property} undefs ${_sourceUndefs}") + message (STATUS "Source ${_sourceFile} ${_property} undefs: ${_sourceUndefs}") endif() set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) endfunction() @@ -678,13 +760,18 @@ macro (cotire_add_definitions_to_cmd _cmdVar _language) endforeach() endmacro() -macro (cotire_add_includes_to_cmd _cmdVar _language) - foreach (_include ${ARGN}) +macro (cotire_add_includes_to_cmd _cmdVar _language _includeSystemFlag _includesVar _systemIncludesVar) + foreach (_include ${${_includesVar}}) if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") file (TO_NATIVE_PATH "${_include}" _include) list (APPEND ${_cmdVar} "/I${_include}") else() - list (APPEND ${_cmdVar} "-I${_include}") + list (FIND ${_systemIncludesVar} ${_include} _index) + if(_index GREATER -1 AND NOT "${_includeSystemFlag}" STREQUAL "") + list (APPEND ${_cmdVar} "${_includeSystemFlag}${_include}") + else() + list (APPEND ${_cmdVar} "-I${_include}") + endif() endif() endforeach() endmacro() @@ -714,30 +801,38 @@ macro (cotire_add_compile_flags_to_cmd _cmdVar) endmacro() function (cotire_check_file_up_to_date _fileIsUpToDateVar _file) - set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) - set (_triggerFile "") - foreach (_dependencyFile ${ARGN}) - if (EXISTS "${_dependencyFile}" AND "${_dependencyFile}" IS_NEWER_THAN "${_file}") - set (_triggerFile "${_dependencyFile}") - break() - endif() - endforeach() - get_filename_component(_fileName "${_file}" NAME) if (EXISTS "${_file}") + set (_triggerFile "") + foreach (_dependencyFile ${ARGN}) + if (EXISTS "${_dependencyFile}") + # IS_NEWER_THAN returns TRUE if both files have the same timestamp + # thus we do the comparison in both directions to exclude ties + if ("${_dependencyFile}" IS_NEWER_THAN "${_file}" AND + NOT "${_file}" IS_NEWER_THAN "${_dependencyFile}") + set (_triggerFile "${_dependencyFile}") + break() + endif() + endif() + endforeach() if (_triggerFile) if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) message (STATUS "${_fileName} update triggered by ${_triggerFile} change.") endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) else() if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) message (STATUS "${_fileName} is up-to-date.") endif() set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE) endif() else() if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) message (STATUS "${_fileName} does not exist yet.") endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) endif() endfunction() @@ -761,12 +856,12 @@ macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar) endforeach() endmacro() -macro (cotire_check_header_file_location _headerFile _insideIncudeDirs _outsideIncudeDirs _headerIsInside) +macro (cotire_check_header_file_location _headerFile _insideIncludeDirs _outsideIncludeDirs _headerIsInside) # check header path against ignored and honored include directories - cotire_find_closest_relative_path("${_headerFile}" "${_insideIncudeDirs}" _insideRelPath) + cotire_find_closest_relative_path("${_headerFile}" "${_insideIncludeDirs}" _insideRelPath) if (_insideRelPath) # header is inside, but could be become outside if there is a shorter outside match - cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncudeDirs}" _outsideRelPath) + cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncludeDirs}" _outsideRelPath) if (_outsideRelPath) string (LENGTH "${_insideRelPath}" _insideRelPathLen) string (LENGTH "${_outsideRelPath}" _outsideRelPathLen) @@ -817,7 +912,7 @@ macro (cotire_parse_line _line _headerFileVar _headerDepthVar) # English: "Note: including file: C:\directory\file" # German: "Hinweis: Einlesen der Datei: C:\directory\file" # We use a very general regular expression, relying on the presence of the : characters - if (_line MATCHES ":( +)([^:]+:[^:]+)$") + if (_line MATCHES "( +)([a-zA-Z]:[^:]+)$") # Visual Studio compiler output string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE) @@ -841,7 +936,7 @@ macro (cotire_parse_line _line _headerFileVar _headerDepthVar) endif() endmacro() -function (cotire_parse_includes _language _scanOutput _ignoredIncudeDirs _honoredIncudeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) +function (cotire_parse_includes _language _scanOutput _ignoredIncludeDirs _honoredIncludeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) if (WIN32) # prevent CMake macro invocation errors due to backslash characters in Windows paths string (REPLACE "\\" "/" _scanOutput "${_scanOutput}") @@ -861,11 +956,11 @@ function (cotire_parse_includes _language _scanOutput _ignoredIncudeDirs _honore if (_ignoredExtensions) message (STATUS "Ignored extensions: ${_ignoredExtensions}") endif() - if (_ignoredIncudeDirs) - message (STATUS "Ignored paths: ${_ignoredIncudeDirs}") + if (_ignoredIncludeDirs) + message (STATUS "Ignored paths: ${_ignoredIncludeDirs}") endif() - if (_honoredIncudeDirs) - message (STATUS "Included paths: ${_honoredIncudeDirs}") + if (_honoredIncludeDirs) + message (STATUS "Included paths: ${_honoredIncludeDirs}") endif() endif() set (_sourceFiles ${ARGN}) @@ -877,7 +972,7 @@ function (cotire_parse_includes _language _scanOutput _ignoredIncudeDirs _honore if (_line) cotire_parse_line("${_line}" _headerFile _headerDepth) if (_headerFile) - cotire_check_header_file_location("${_headerFile}" "${_ignoredIncudeDirs}" "${_honoredIncudeDirs}" _headerIsInside) + cotire_check_header_file_location("${_headerFile}" "${_ignoredIncludeDirs}" "${_honoredIncludeDirs}" _headerIsInside) if (COTIRE_DEBUG) message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}") endif() @@ -945,8 +1040,9 @@ endfunction() function (cotire_scan_includes _includesVar) set(_options "") - set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_VERSION LANGUAGE UNPARSED_LINES) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS) + set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_VERSION INCLUDE_SYSTEM_FLAG LANGUAGE UNPARSED_LINES) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES + IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) if (NOT _option_LANGUAGE) @@ -955,11 +1051,14 @@ function (cotire_scan_includes _includesVar) if (NOT _option_COMPILER_ID) set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() set (_cmd "${_option_COMPILER_EXECUTABLE}" ${_option_COMPILER_ARG1}) cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" "${_option_INCLUDE_SYSTEM_FLAG}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES}) cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd) # only consider existing source files for scanning @@ -978,14 +1077,15 @@ function (cotire_scan_includes _includesVar) message (STATUS "execute_process: ${_cmd}") endif() if (_option_COMPILER_ID MATCHES "MSVC") - if (COTIRE_DEBUG) - message (STATUS "clearing VS_UNICODE_OUTPUT") - endif() # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared unset (ENV{VS_UNICODE_OUTPUT}) endif() - execute_process(COMMAND ${_cmd} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE _result OUTPUT_QUIET ERROR_VARIABLE _output) + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result + OUTPUT_QUIET + ERROR_VARIABLE _output) if (_result) message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.") endif() @@ -995,6 +1095,21 @@ function (cotire_scan_includes _includesVar) "${_option_IGNORE_EXTENSIONS}" _includes _unparsedLines ${_sourceFiles}) + if (_option_INCLUDE_PRIORITY_PATH) + set (_sortedIncludes "") + foreach (_priorityPath ${_option_INCLUDE_PRIORITY_PATH}) + foreach (_include ${_includes}) + string (FIND ${_include} ${_priorityPath} _position) + if (_position GREATER -1) + list (APPEND _sortedIncludes ${_include}) + endif() + endforeach() + endforeach() + if (_sortedIncludes) + list (INSERT _includes 0 ${_sortedIncludes}) + list (REMOVE_DUPLICATES _includes) + endif() + endif() set (${_includesVar} ${_includes} PARENT_SCOPE) if (_option_UNPARSED_LINES) set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE) @@ -1110,11 +1225,12 @@ function (cotire_generate_unity_source _unityFile) list (INSERT _compileUndefinitions 0 "${_definition}") endif() endforeach() - get_filename_component(_sourceFile "${_sourceFile}" ABSOLUTE) + # use absolute path as source file location + get_filename_component(_sourceFileLocation "${_sourceFile}" ABSOLUTE) if (WIN32) - file (TO_NATIVE_PATH "${_sourceFile}" _sourceFile) + file (TO_NATIVE_PATH "${_sourceFileLocation}" _sourceFileLocation) endif() - list (APPEND _contents "#include \"${_sourceFile}\"") + list (APPEND _contents "#include \"${_sourceFileLocation}\"") endforeach() if (_compileUndefinitions) cotire_append_undefs(_contents ${_compileUndefinitions}) @@ -1136,13 +1252,23 @@ endfunction() function (cotire_generate_prefix_header _prefixFile) set(_options "") - set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION) + set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION INCLUDE_SYSTEM_FLAG) set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS - INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS) + INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH + IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() if (_option_DEPENDS) cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS}) if (_prefixFileIsUpToDate) + # create empty log file + set (_unparsedLinesFile "${_prefixFile}.log") + file (WRITE "${_unparsedLinesFile}" "") return() endif() endif() @@ -1168,9 +1294,12 @@ function (cotire_generate_prefix_header _prefixFile) COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS} COMPILE_FLAGS ${_option_COMPILE_FLAGS} INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES} + INCLUDE_SYSTEM_FLAG ${_option_INCLUDE_SYSTEM_FLAG} + SYSTEM_INCLUDE_DIRECTORIES ${_option_SYSTEM_INCLUDE_DIRECTORIES} IGNORE_PATH ${_option_IGNORE_PATH} INCLUDE_PATH ${_option_INCLUDE_PATH} IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS} + INCLUDE_PRIORITY_PATH ${_option_INCLUDE_PRIORITY_PATH} UNPARSED_LINES _unparsedLines) cotire_generate_unity_source("${_prefixFile}" PROLOGUE ${_prologue} EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders}) @@ -1178,8 +1307,7 @@ function (cotire_generate_prefix_header _prefixFile) if (_unparsedLines) if (COTIRE_VERBOSE OR NOT _selectedHeaders) list (LENGTH _unparsedLines _skippedLineCount) - file (RELATIVE_PATH _unparsedLinesFileRelPath "${CMAKE_BINARY_DIR}" "${_unparsedLinesFile}") - message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesFileRelPath}") + message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesFile}") endif() string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}") endif() @@ -1226,12 +1354,13 @@ function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flags # Clang options used # -H print the name of each header file used # -E invoke preprocessor + # -fno-color-diagnostics don't prints diagnostics in color if (_flags) # append to list - list (APPEND _flags -H -E) + list (APPEND _flags -H -E -fno-color-diagnostics) else() # return as a flag string - set (_flags "-H -E") + set (_flags "-H -E -fno-color-diagnostics") endif() elseif (_compilerID MATCHES "Intel") if (WIN32) @@ -1270,7 +1399,7 @@ function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flags endif() endif() else() - message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") endif() set (${_flagsVar} ${_flags} PARENT_SCOPE) endfunction() @@ -1288,30 +1417,38 @@ function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersio # /TC treat all files named on the command line as C source files # /TP treat all files named on the command line as C++ source files # /Zs syntax check only + # /Zm precompiled header memory allocation scaling factor set (_sourceFileTypeC "/TC") set (_sourceFileTypeCXX "/TP") if (_flags) # append to list list (APPEND _flags /nologo "${_sourceFileType${_language}}" "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() else() # return as a flag string set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() endif() elseif (_compilerID MATCHES "GNU|Clang") # GCC / Clang options used - # -w disable all warnings # -x specify the source language # -c compile but do not link # -o place output in file + # note that we cannot use -w to suppress all warnings upon pre-compiling, because turning off a warning may + # alter compile flags as a side effect (e.g., -Wwrite-string implies -fconst-strings) set (_xLanguage_C "c-header") set (_xLanguage_CXX "c++-header") if (_flags) # append to list - list (APPEND _flags "-w" "-x" "${_xLanguage_${_language}}" "-c" "${_prefixFile}" -o "${_pchFile}") + list (APPEND _flags "-x" "${_xLanguage_${_language}}" "-c" "${_prefixFile}" -o "${_pchFile}") else() # return as a flag string - set (_flags "-w -x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") + set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") endif() elseif (_compilerID MATCHES "Intel") if (WIN32) @@ -1372,7 +1509,7 @@ function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersio endif() endif() else() - message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") endif() set (${_flagsVar} ${_flags} PARENT_SCOPE) endfunction() @@ -1385,14 +1522,21 @@ function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerV # /Yu uses a precompiled header file during build # /Fp specifies precompiled header binary file name # /FI forces inclusion of file + # /Zm precompiled header memory allocation scaling factor if (_pchFile) file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) if (_flags) # append to list list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() else() # return as a flag string set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() endif() else() # no precompiled header, force inclusion of prefix header @@ -1408,6 +1552,7 @@ function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerV # GCC options used # -include process include file as the first line of the primary source file # -Winvalid-pch warns if precompiled header is found but cannot be used + # note: ccache requires the -include flag to be used in order to process precompiled header correctly if (_flags) # append to list list (APPEND _flags "-Winvalid-pch" "-include" "${_prefixFile}") @@ -1420,24 +1565,13 @@ function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerV # -include process include file as the first line of the primary source file # -include-pch include precompiled header file # -Qunused-arguments don't emit warning for unused driver arguments - if (_pchFile AND NOT CMAKE_${_language}_COMPILER MATCHES "ccache") - if (_flags) - # append to list - list (APPEND _flags "-Qunused-arguments" "-include-pch" "${_pchFile}") - else() - # return as a flag string - set (_flags "-Qunused-arguments -include-pch \"${_pchFile}\"") - endif() + # note: ccache requires the -include flag to be used in order to process precompiled header correctly + if (_flags) + # append to list + list (APPEND _flags "-Qunused-arguments" "-include" "${_prefixFile}") else() - # no precompiled header, force inclusion of prefix header - # ccache requires the -include flag to be used in order to process precompiled header correctly - if (_flags) - # append to list - list (APPEND _flags "-Qunused-arguments" "-include" "${_prefixFile}") - else() - # return as a flag string - set (_flags "-Qunused-arguments -include \"${_prefixFile}\"") - endif() + # return as a flag string + set (_flags "-Qunused-arguments -include \"${_prefixFile}\"") endif() elseif (_compilerID MATCHES "Intel") if (WIN32) @@ -1506,15 +1640,15 @@ function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerV endif() endif() else() - message (FATAL_ERROR "Unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") endif() set (${_flagsVar} ${_flags} PARENT_SCOPE) endfunction() function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) set(_options "") - set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION LANGUAGE) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES) + set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION INCLUDE_SYSTEM_FLAG LANGUAGE) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES SYS) cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) if (NOT _option_LANGUAGE) set (_option_LANGUAGE "CXX") @@ -1522,10 +1656,13 @@ function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) if (NOT _option_COMPILER_ID) set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" "${_option_INCLUDE_SYSTEM_FLAG}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_INCLUDE_DIRECTORIES}) cotire_add_pch_compilation_flags( "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" @@ -1534,19 +1671,19 @@ function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) message (STATUS "execute_process: ${_cmd}") endif() if (_option_COMPILER_ID MATCHES "MSVC") - if (COTIRE_DEBUG) - message (STATUS "clearing VS_UNICODE_OUTPUT") - endif() # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared unset (ENV{VS_UNICODE_OUTPUT}) endif() - execute_process(COMMAND ${_cmd} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE _result) + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result) if (_result) - message (FATAL_ERROR "Error ${_result} precompiling ${_prefixFile}.") + message (FATAL_ERROR "cotire: error ${_result} precompiling ${_prefixFile}.") endif() endfunction() -function (cotire_check_precompiled_header_support _language _targetSourceDir _target _msgVar) +function (cotire_check_precompiled_header_support _language _target _msgVar) set (_unsupportedCompiler "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}") if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") @@ -1555,10 +1692,8 @@ function (cotire_check_precompiled_header_support _language _targetSourceDir _ta set (${_msgVar} "" PARENT_SCOPE) elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") # GCC PCH support requires version >= 3.4 - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) - if ("${COTIRE_${_language}_COMPILER_VERSION}" MATCHES ".+" AND - "${COTIRE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") - set (${_msgVar} "${_unsupportedCompiler} version ${COTIRE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) else() set (${_msgVar} "" PARENT_SCOPE) endif() @@ -1567,10 +1702,8 @@ function (cotire_check_precompiled_header_support _language _targetSourceDir _ta set (${_msgVar} "" PARENT_SCOPE) elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") # Intel PCH support requires version >= 8.0.0 - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) - if ("${COTIRE_${_language}_COMPILER_VERSION}" MATCHES ".+" AND - "${COTIRE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") - set (${_msgVar} "${_unsupportedCompiler} version ${COTIRE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) else() set (${_msgVar} "" PARENT_SCOPE) endif() @@ -1578,24 +1711,18 @@ function (cotire_check_precompiled_header_support _language _targetSourceDir _ta set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE) endif() if (CMAKE_${_language}_COMPILER MATCHES "ccache") - if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros") + if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros|pch_defines") set (${_msgVar} - "ccache requires the environment variable CCACHE_SLOPPINESS to be set to time_macros." + "ccache requires the environment variable CCACHE_SLOPPINESS to be set to \"pch_defines,time_macros\"." PARENT_SCOPE) endif() endif() if (APPLE) # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64) - if (CMAKE_CONFIGURATION_TYPES) - set (_configs ${CMAKE_CONFIGURATION_TYPES}) - elseif (CMAKE_BUILD_TYPE) - set (_configs ${CMAKE_BUILD_TYPE}) - else() - set (_configs "None") - endif() + cotire_get_configuration_types(_configs) foreach (_config ${_configs}) set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_targetSourceDir}" "${_target}" _targetFlags) + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags}) list (LENGTH _architectures _numberOfArchitectures) if (_numberOfArchitectures GREATER 1) @@ -1610,6 +1737,7 @@ function (cotire_check_precompiled_header_support _language _targetSourceDir _ta endfunction() macro (cotire_get_intermediate_dir _cotireDir) + # ${CMAKE_CFG_INTDIR} may reference a build-time variable when using a generator which supports configuration types get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE) endmacro() @@ -1618,6 +1746,8 @@ macro (cotire_setup_file_extension_variables) set (_unityFileExt_CXX ".cxx") set (_prefixFileExt_C ".h") set (_prefixFileExt_CXX ".hxx") + set (_prefixSourceFileExt_C ".c") + set (_prefixSourceFileExt_CXX ".cxx") endmacro() function (cotire_make_single_unity_source_file_path _language _target _unityFileVar) @@ -1631,9 +1761,6 @@ function (cotire_make_single_unity_source_file_path _language _target _unityFile cotire_get_intermediate_dir(_baseDir) set (_unityFile "${_baseDir}/${_unityFileName}") set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE) - if (COTIRE_DEBUG) - message(STATUS "${_unityFile}") - endif() endfunction() function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar) @@ -1673,8 +1800,8 @@ function (cotire_make_unity_source_file_paths _language _target _maxIncludes _un list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") endif() set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE) - if (COTIRE_DEBUG) - message(STATUS "${_unityFiles}") + if (COTIRE_DEBUG AND _unityFiles) + message (STATUS "unity files: ${_unityFiles}") endif() endfunction() @@ -1691,6 +1818,16 @@ function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixF set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE) endfunction() +function (cotire_prefix_header_to_source_file_path _language _prefixHeaderFile _prefixSourceFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _prefixSourceFileExt_${_language}) + set (${_prefixSourceFileVar} "" PARENT_SCOPE) + return() + endif() + string (REGEX REPLACE "${_prefixFileExt_${_language}}$" "${_prefixSourceFileExt_${_language}}" _prefixSourceFile "${_prefixHeaderFile}") + set (${_prefixSourceFileVar} "${_prefixSourceFile}" PARENT_SCOPE) +endfunction() + function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar) cotire_setup_file_extension_variables() if (NOT _language) @@ -1714,18 +1851,18 @@ function (cotire_make_prefix_file_path _language _target _prefixFileVar) if (NOT _language) set (_language "C") endif() - if (MSVC OR CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel") + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel|MSVC") cotire_get_intermediate_dir(_baseDir) set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE) endif() endif() endfunction() -function (cotire_make_pch_file_path _language _targetSourceDir _target _pchFileVar) +function (cotire_make_pch_file_path _language _target _pchFileVar) cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) set (${_pchFileVar} "" PARENT_SCOPE) if (_prefixFileBaseName AND _prefixFileName) - cotire_check_precompiled_header_support("${_language}" "${_targetSourceDir}" "${_target}" _msg) + cotire_check_precompiled_header_support("${_language}" "${_target}" _msg) if (NOT _msg) if (XCODE) # For Xcode, we completely hand off the compilation of the prefix header to the IDE @@ -1775,7 +1912,8 @@ endfunction() function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar) set (_dependencySources "") # depend on target's generated source files - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${ARGN}) + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) if (_generatedSources) # but omit all generated source files that have the COTIRE_EXCLUDED property set to true cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources}) @@ -1792,38 +1930,36 @@ function (cotire_get_unity_source_dependencies _language _target _dependencySour endif() endif() if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} unity source depends on ${_dependencySources}") + message (STATUS "${_language} ${_target} unity source dependencies: ${_dependencySources}") endif() set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) endfunction() function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar) - # depend on target source files marked with custom COTIRE_DEPENDENCY property set (_dependencySources "") - cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") - # Clang raises a fatal error if a file is not found during preprocessing + # depend on target source files marked with custom COTIRE_DEPENDENCY property + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${_targetSourceFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GCC and clang raise a fatal error if a file is not found during preprocessing # thus we depend on target's generated source files for prefix header generation - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${ARGN}) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) if (_generatedSources) list (APPEND _dependencySources ${_generatedSources}) endif() endif() if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} prefix header DEPENDS ${_dependencySources}") + message (STATUS "${_language} ${_target} prefix header dependencies: ${_dependencySources}") endif() set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) endfunction() -function (cotire_generate_target_script _language _configurations _targetSourceDir _targetBinaryDir _target _targetScriptVar) - set (COTIRE_TARGET_SOURCES ${ARGN}) - get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) - set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") - cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${COTIRE_TARGET_SOURCES}) - cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${COTIRE_TARGET_SOURCES}) +function (cotire_generate_target_script _language _configurations _target _targetScriptVar _targetConfigScriptVar) + set (_targetSources ${ARGN}) + cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${_targetSources}) + cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${_targetSources}) # set up variables to be configured set (COTIRE_TARGET_LANGUAGE "${_language}") - cotire_determine_compiler_version("${COTIRE_TARGET_LANGUAGE}" COTIRE_${_language}_COMPILER) get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH) cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH) get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH) @@ -1831,113 +1967,142 @@ function (cotire_generate_target_script _language _configurations _targetSourceD get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS) get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS) get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${COTIRE_TARGET_SOURCES}) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${COTIRE_TARGET_SOURCES}) + get_target_property(COTIRE_TARGET_INCLUDE_PRIORITY_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${_targetSources}) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${_targetSources}) + string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" COTIRE_INCLUDE_SYSTEM_FLAG) set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}") foreach (_config ${_configurations}) string (TOUPPER "${_config}" _upperConfig) cotire_get_target_include_directories( - "${_config}" "${_language}" "${_targetSourceDir}" "${_targetBinaryDir}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}) + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig} COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}) cotire_get_target_compile_definitions( - "${_config}" "${_language}" "${_targetSourceDir}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) cotire_get_target_compiler_flags( - "${_config}" "${_language}" "${_targetSourceDir}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) cotire_get_source_files_compile_definitions( - "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${COTIRE_TARGET_SOURCES}) + "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${_targetSources}) endforeach() + # set up COTIRE_TARGET_SOURCES + set (COTIRE_TARGET_SOURCES "") + foreach (_sourceFile ${_targetSources}) + get_source_file_property(_generated "${_sourceFile}" GENERATED) + if (_generated) + # use absolute paths for generated files only, retrieving the LOCATION property is an expensive operation + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND COTIRE_TARGET_SOURCES "${_sourceLocation}") + else() + list (APPEND COTIRE_TARGET_SOURCES "${_sourceFile}") + endif() + endforeach() + # copy variable definitions to cotire target script get_cmake_property(_vars VARIABLES) string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}") # remove COTIRE_VERBOSE which is passed as a CMake define on command line list (REMOVE_ITEM _matchVars COTIRE_VERBOSE) set (_contents "") + set (_contentsHasGeneratorExpressions FALSE) foreach (_var IN LISTS _matchVars ITEMS - MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES - CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 + XCODE MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES + CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER_VERSION + CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) if (DEFINED ${_var}) string (REPLACE "\"" "\\\"" _value "${${_var}}") set (_contents "${_contents}set (${_var} \"${_value}\")\n") + if (NOT _contentsHasGeneratorExpressions) + if ("${_value}" MATCHES "\\$<.*>") + set (_contentsHasGeneratorExpressions TRUE) + endif() + endif() endif() endforeach() + # generate target script file + get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) + set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE) + if (_contentsHasGeneratorExpressions) + # use file(GENERATE ...) to expand generator expressions in the target script at CMake generate-time + set (_configNameOrNoneGeneratorExpression "$<$:None>$<$>:$>") + set (_targetCotireConfigScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_configNameOrNoneGeneratorExpression}_${_moduleName}") + file (GENERATE OUTPUT "${_targetCotireConfigScript}" INPUT "${_targetCotireScript}") + else() + set (_targetCotireConfigScript "${_targetCotireScript}") + endif() set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE) + set (${_targetConfigScriptVar} "${_targetCotireConfigScript}" PARENT_SCOPE) endfunction() -function (cotire_setup_pch_file_compilation _language _target _targetSourceDir _targetScript _prefixFile _pchFile) +function (cotire_setup_pch_file_compilation _language _target _targetScript _prefixFile _pchFile _hostFile) set (_sourceFiles ${ARGN}) if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - # for Visual Studio and Intel, we attach the precompiled header compilation to the first source file + # for Visual Studio and Intel, we attach the precompiled header compilation to the host file # the remaining files include the precompiled header, see cotire_setup_pch_file_inclusion if (_sourceFiles) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - list (GET _sourceFiles 0 _hostFile) set (_flags "") - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) cotire_add_pch_compilation_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}" + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags) set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}") - # make first source file depend on prefix header + # make object file generated from host file depend on prefix header set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") - # mark first source file as cotired to prevent it from being used in another cotired target + # mark host file as cotired to prevent it from being used in another cotired target set_property (SOURCE ${_hostFile} PROPERTY COTIRE_TARGET "${_target}") endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja") + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") # for makefile based generator, we add a custom command to precompile the prefix header if (_targetScript) cotire_set_cmd_to_prologue(_cmds) - list (GET _sourceFiles 0 _hostFile) list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}") file (RELATIVE_PATH _pchFileRelPath "${CMAKE_BINARY_DIR}" "${_pchFile}") if (COTIRE_DEBUG) message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} IMPLICIT_DEPENDS ${_language} ${_prefixFile}") endif() set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE) - add_custom_command(OUTPUT "${_pchFile}" + add_custom_command( + OUTPUT "${_pchFile}" COMMAND ${_cmds} DEPENDS "${_prefixFile}" IMPLICIT_DEPENDS ${_language} "${_prefixFile}" - WORKING_DIRECTORY "${_targetSourceDir}" - COMMENT "Building ${_language} precompiled header ${_pchFileRelPath}" VERBATIM) + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Building ${_language} precompiled header ${_pchFileRelPath}" + VERBATIM) endif() endif() endfunction() -function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile) - set (_sourceFiles ${ARGN}) +function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile _hostFile) if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - # for Visual Studio and Intel, we include the precompiled header in all but the first source file - # the first source file does the precompiled header compilation, see cotire_setup_pch_file_compilation + # for Visual Studio and Intel, we include the precompiled header in all but the host file + # the host file does the precompiled header compilation, see cotire_setup_pch_file_compilation + set (_sourceFiles ${ARGN}) list (LENGTH _sourceFiles _numberOfSourceFiles) - if (_numberOfSourceFiles GREATER 1) + if (_numberOfSourceFiles GREATER 0) # mark sources as cotired to prevent them from being used in another cotired target set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - list (REMOVE_AT _sourceFiles 0) set (_flags "") - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}" + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" "${_prefixFile}" "${_pchFile}" _flags) set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # make source files depend on precompiled header + # make object files generated from source files depend on precompiled header set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja") + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + set (_sourceFiles ${_hostFile} ${ARGN}) if (NOT _wholeTarget) # for makefile based generator, we force the inclusion of the prefix header for a subset # of the source files, if this is a multi-language target or has excluded files set (_flags "") - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}" + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" "${_prefixFile}" "${_pchFile}" _flags) set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") # mark sources as cotired to prevent them from being used in another cotired target set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") endif() - # make source files depend on precompiled header + # make object files generated from source files depend on precompiled header set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") endif() endfunction() @@ -1946,14 +2111,14 @@ function (cotire_setup_prefix_file_inclusion _language _target _prefixFile) set (_sourceFiles ${ARGN}) # force the inclusion of the prefix header for the given source files set (_flags "") - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) + set (_pchFile "") cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "" _flags) + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") # mark sources as cotired to prevent them from being used in another cotired target set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - # make source files depend on prefix header + # make object files generated from source files depend on prefix header set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") endfunction() @@ -1969,21 +2134,12 @@ function (cotire_get_first_set_property_value _propertyValueVar _type _object) set (${_propertyValueVar} "" PARENT_SCOPE) endfunction() -function (cotire_setup_combine_command _language _sourceDir _targetScript _joinedFile _cmdsVar) +function (cotire_setup_combine_command _language _targetScript _joinedFile _cmdsVar) set (_files ${ARGN}) set (_filesPaths "") foreach (_file ${_files}) - if (IS_ABSOLUTE "${_file}") - set (_filePath "${_file}") - else() - get_filename_component(_filePath "${_sourceDir}/${_file}" ABSOLUTE) - endif() - file (RELATIVE_PATH _fileRelPath "${_sourceDir}" "${_filePath}") - if (NOT IS_ABSOLUTE "${_fileRelPath}" AND NOT "${_fileRelPath}" MATCHES "^\\.\\.") - list (APPEND _filesPaths "${_fileRelPath}") - else() - list (APPEND _filesPaths "${_filePath}") - endif() + get_filename_component(_filePath "${_file}" ABSOLUTE) + list (APPEND _filesPaths "${_filePath}") endforeach() cotire_set_cmd_to_prologue(_prefixCmd) list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine") @@ -1996,11 +2152,16 @@ function (cotire_setup_combine_command _language _sourceDir _targetScript _joine endif() set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE) file (RELATIVE_PATH _joinedFileRelPath "${CMAKE_BINARY_DIR}" "${_joinedFile}") - get_filename_component(_joinedFileName "${_joinedFileRelPath}" NAME_WE) - if (_language AND _joinedFileName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") + get_filename_component(_joinedFileBaseName "${_joinedFile}" NAME_WE) + get_filename_component(_joinedFileExt "${_joinedFile}" EXT) + if (_language AND _joinedFileBaseName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") set (_comment "Generating ${_language} unity source ${_joinedFileRelPath}") - elseif (_language AND _joinedFileName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") - set (_comment "Generating ${_language} prefix header ${_joinedFileRelPath}") + elseif (_language AND _joinedFileBaseName MATCHES "${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}$") + if (_joinedFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_joinedFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_joinedFileRelPath}") + endif() else() set (_comment "Generating ${_joinedFileRelPath}") endif() @@ -2009,15 +2170,15 @@ function (cotire_setup_combine_command _language _sourceDir _targetScript _joine COMMAND ${_prefixCmd} DEPENDS ${_files} COMMENT "${_comment}" - WORKING_DIRECTORY "${_sourceDir}" VERBATIM) + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) endfunction() -function (cotire_setup_target_pch_usage _languages _targetSourceDir _target _wholeTarget) +function (cotire_setup_target_pch_usage _languages _target _wholeTarget) if (XCODE) # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers - # if necessary, we also generate a single prefix header which includes all language specific prefix headers set (_prefixFiles "") foreach (_language ${_languages}) get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) @@ -2028,22 +2189,29 @@ function (cotire_setup_target_pch_usage _languages _targetSourceDir _target _who set (_cmds ${ARGN}) list (LENGTH _prefixFiles _numberOfPrefixFiles) if (_numberOfPrefixFiles GREATER 1) - cotire_make_prefix_file_path("" ${_target} _prefixHeader) - cotire_setup_combine_command("" "${_targetSourceDir}" "" "${_prefixHeader}" _cmds ${_prefixFiles}) + # we also generate a generic, single prefix header which includes all language specific prefix headers + set (_language "") + set (_targetScript "") + cotire_make_prefix_file_path("${_language}" ${_target} _prefixHeader) + cotire_setup_combine_command("${_language}" "${_targetScript}" "${_prefixHeader}" _cmds ${_prefixFiles}) else() set (_prefixHeader "${_prefixFiles}") endif() if (COTIRE_DEBUG) message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}") endif() - add_custom_command(TARGET "${_target}" + # because CMake PRE_BUILD command does not support dependencies, + # we check dependencies explicity in cotire script mode when the pre-build action is run + add_custom_command( + TARGET "${_target}" PRE_BUILD ${_cmds} - WORKING_DIRECTORY "${_targetSourceDir}" - COMMENT "Updating target ${_target} prefix headers" VERBATIM) + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Updating target ${_target} prefix headers" + VERBATIM) # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++ set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}") - elseif ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja") + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") # for makefile based generator, we force inclusion of the prefix header for all target source files # if this is a single-language target without any excluded files if (_wholeTarget) @@ -2052,59 +2220,73 @@ function (cotire_setup_target_pch_usage _languages _targetSourceDir _target _who # see cotire_setup_pch_file_inclusion if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) - set (_flags "") - cotire_determine_compiler_version("${_language}" COTIRE_${_language}_COMPILER) - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${COTIRE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property(TARGET ${_target} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + if (_prefixFile) + get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) + set (_options COMPILE_OPTIONS) + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _options) + set_property(TARGET ${_target} APPEND PROPERTY ${_options}) + endif() endif() endif() endif() endfunction() -function (cotire_setup_unity_generation_commands _language _targetSourceDir _target _targetScript _unityFiles _cmdsVar) +function (cotire_setup_unity_generation_commands _language _target _targetScript _targetConfigScript _unityFiles _cmdsVar) set (_dependencySources "") cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN}) foreach (_unityFile ${_unityFiles}) - file (RELATIVE_PATH _unityFileRelPath "${CMAKE_BINARY_DIR}" "${_unityFile}") set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE) - # set up compiled unity source dependencies + # set up compiled unity source dependencies via OBJECT_DEPENDS # this ensures that missing source files are generated before the unity file is compiled if (COTIRE_DEBUG AND _dependencySources) message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}") endif() if (_dependencySources) - set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_dependencySources}) + # the OBJECT_DEPENDS property requires a list of full paths + set (_objectDependsPaths "") + foreach (_sourceFile ${_dependencySources}) + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND _objectDependsPaths "${_sourceLocation}") + endforeach() + set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_objectDependsPaths}) endif() if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") # unity file compilation results in potentially huge object file, thus use /bigobj by default unter MSVC and Windows Intel set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj") endif() cotire_set_cmd_to_prologue(_unityCmd) - list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetScript}" "${_unityFile}") + list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetConfigScript}" "${_unityFile}") + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_unityCmdDepends "${_targetScript}") + else() + # CMake 3.1.0 supports generator expressions in arguments to DEPENDS + set (_unityCmdDepends "${_targetConfigScript}") + endif() + file (RELATIVE_PATH _unityFileRelPath "${CMAKE_BINARY_DIR}" "${_unityFile}") if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_targetScript}") + message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_unityCmdDepends}") endif() add_custom_command( OUTPUT "${_unityFile}" COMMAND ${_unityCmd} - DEPENDS "${_targetScript}" + DEPENDS ${_unityCmdDepends} COMMENT "Generating ${_language} unity source ${_unityFileRelPath}" - WORKING_DIRECTORY "${_targetSourceDir}" VERBATIM) + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) list (APPEND ${_cmdsVar} COMMAND ${_unityCmd}) endforeach() list (LENGTH _unityFiles _numberOfUnityFiles) if (_numberOfUnityFiles GREATER 1) # create a joint unity file from all unity file segments cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) - cotire_setup_combine_command(${_language} "${_targetSourceDir}" "${_targetScript}" "${_unityFile}" ${_cmdsVar} ${_unityFiles}) + cotire_setup_combine_command(${_language} "${_targetConfigScript}" "${_unityFile}" ${_cmdsVar} ${_unityFiles}) endif() set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) endfunction() -function (cotire_setup_single_prefix_generation_command _language _target _targetSourceDir _targetScript _prefixFile _unityFile _cmdsVar) +function (cotire_setup_prefix_generation_command _language _target _targetScript _prefixFile _unityFile _cmdsVar) set (_sourceFiles ${ARGN}) set (_dependencySources "") cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles}) @@ -2112,31 +2294,64 @@ function (cotire_setup_single_prefix_generation_command _language _target _targe list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" "${_unityFile}") set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE) if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_targetScript} ${_unityFile} ${_dependencySources}") + message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_unityFile} ${_dependencySources}") endif() file (RELATIVE_PATH _prefixFileRelPath "${CMAKE_BINARY_DIR}" "${_prefixFile}") + get_filename_component(_prefixFileExt "${_prefixFile}" EXT) + if (_prefixFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_prefixFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_prefixFileRelPath}") + endif() add_custom_command( OUTPUT "${_prefixFile}" "${_prefixFile}.log" COMMAND ${_prefixCmd} - DEPENDS "${_targetScript}" "${_unityFile}" ${_dependencySources} - COMMENT "Generating ${_language} prefix header ${_prefixFileRelPath}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM) + DEPENDS "${_unityFile}" ${_dependencySources} + COMMENT "${_comment}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) endfunction() -function (cotire_setup_multi_prefix_generation_command _language _target _targetSourceDir _targetScript _prefixFile _unityFiles _cmdsVar) +function (cotire_setup_prefix_generation_from_unity_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) set (_sourceFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() list (LENGTH _unityFiles _numberOfUnityFiles) if (_numberOfUnityFiles GREATER 1) cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) - cotire_setup_single_prefix_generation_command( - ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}" - "${_prefixFile}" "${_unityFile}" ${_cmdsVar} ${_sourceFiles}) + cotire_setup_prefix_generation_command( + ${_language} ${_target} "${_targetScript}" + "${_prefixSourceFile}" "${_unityFile}" ${_cmdsVar} ${_sourceFiles}) else() - cotire_setup_single_prefix_generation_command( - ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}" - "${_prefixFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) + cotire_setup_prefix_generation_command( + ${_language} ${_target} "${_targetScript}" + "${_prefixSourceFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) + endif() + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) + endif() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_from_provided_command _language _target _targetScript _prefixFile _cmdsVar) + set (_prefixHeaderFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixSourceFile}" _cmds ${_prefixHeaderFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) endif() set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) endfunction() @@ -2166,6 +2381,10 @@ function (cotire_init_cotire_target_properties _target) if (NOT _isSet) set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "") endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH "") + endif() get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET) if (NOT _isSet) set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "") @@ -2176,7 +2395,7 @@ function (cotire_init_cotire_target_properties _target) endif() get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT SET) if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "") + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "COPY_UNITY") endif() get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET) if (NOT _isSet) @@ -2236,12 +2455,13 @@ function (cotire_make_target_message _target _languages _disableMsg _targetMsgVa set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE) endfunction() -function (cotire_choose_target_languages _targetSourceDir _target _targetLanguagesVar) +function (cotire_choose_target_languages _target _targetLanguagesVar _wholeTargetVar) set (_languages ${ARGN}) set (_allSourceFiles "") set (_allExcludedSourceFiles "") set (_allCotiredSourceFiles "") set (_targetLanguages "") + set (_pchEligibleTargetLanguages "") get_target_property(_targetType ${_target} TYPE) get_target_property(_targetSourceFiles ${_target} SOURCES) get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) @@ -2251,12 +2471,12 @@ function (cotire_choose_target_languages _targetSourceDir _target _targetLanguag get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER) get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE) if (_prefixHeader OR _unityBuildFile) - message (STATUS "Target ${_target} has already been cotired.") + message (STATUS "cotire: target ${_target} has already been cotired.") set (${_targetLanguagesVar} "" PARENT_SCOPE) return() endif() if (_targetUsePCH AND "${_language}" MATCHES "^C|CXX$") - cotire_check_precompiled_header_support("${_language}" "${_targetSourceDir}" "${_target}" _disableMsg) + cotire_check_precompiled_header_support("${_language}" "${_target}" _disableMsg) if (_disableMsg) set (_targetUsePCH FALSE) endif() @@ -2264,13 +2484,17 @@ function (cotire_choose_target_languages _targetSourceDir _target _targetLanguag set (_sourceFiles "") set (_excludedSources "") set (_cotiredSources "") - cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) if (_sourceFiles OR _excludedSources OR _cotiredSources) list (APPEND _targetLanguages ${_language}) endif() if (_sourceFiles) list (APPEND _allSourceFiles ${_sourceFiles}) endif() + list (LENGTH _sourceFiles _numberOfSources) + if (NOT _numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + list (APPEND _pchEligibleTargetLanguages ${_language}) + endif() if (_excludedSources) list (APPEND _allExcludedSourceFiles ${_excludedSources}) endif() @@ -2286,11 +2510,7 @@ function (cotire_choose_target_languages _targetSourceDir _target _targetLanguag set (_targetAddSCU FALSE) endif() if (_targetUsePCH) - list (LENGTH _allSourceFiles _numberOfSources) - if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) - set (_disableMsg "Too few applicable sources.") - set (_targetUsePCH FALSE) - elseif (_allCotiredSourceFiles) + if (_allCotiredSourceFiles) cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles}) list (REMOVE_DUPLICATES _cotireTargets) string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}") @@ -2299,6 +2519,9 @@ function (cotire_choose_target_languages _targetSourceDir _target _targetLanguag set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.") set (_targetMsgLevel SEND_ERROR) set (_targetUsePCH FALSE) + elseif (NOT _pchEligibleTargetLanguages) + set (_disableMsg "Too few applicable sources.") + set (_targetUsePCH FALSE) elseif (XCODE AND _allExcludedSourceFiles) # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target set (_disableMsg "Exclusion of source files not supported for generator Xcode.") @@ -2323,6 +2546,12 @@ function (cotire_choose_target_languages _targetSourceDir _target _targetLanguag message (${_targetMsgLevel} "${_targetMsg}") endif() endif() + list (LENGTH _targetLanguages _numberOfLanguages) + if (_numberOfLanguages GREATER 1 OR _allExcludedSourceFiles) + set (${_wholeTargetVar} FALSE PARENT_SCOPE) + else() + set (${_wholeTargetVar} TRUE PARENT_SCOPE) + endif() set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE) endfunction() @@ -2345,22 +2574,21 @@ function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar) set (_maxIncludes 0) endif() if (COTIRE_DEBUG) - message (STATUS "${_target} unity source max includes = ${_maxIncludes}") + message (STATUS "${_target} unity source max includes: ${_maxIncludes}") endif() set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE) endfunction() -function (cotire_process_target_language _language _configurations _targetSourceDir _targetBinaryDir _target _wholeTargetVar _cmdsVar) +function (cotire_process_target_language _language _configurations _target _wholeTarget _cmdsVar) set (${_cmdsVar} "" PARENT_SCOPE) get_target_property(_targetSourceFiles ${_target} SOURCES) set (_sourceFiles "") set (_excludedSources "") set (_cotiredSources "") - cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) if (NOT _sourceFiles AND NOT _cotiredSources) return() endif() - set (_wholeTarget ${${_wholeTargetVar}}) set (_cmds "") # check for user provided unity source file list get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT) @@ -2368,40 +2596,44 @@ function (cotire_process_target_language _language _configurations _targetSource set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources}) endif() cotire_generate_target_script( - ${_language} "${_configurations}" "${_targetSourceDir}" "${_targetBinaryDir}" ${_target} _targetScript ${_unitySourceFiles}) + ${_language} "${_configurations}" ${_target} _targetScript _targetConfigScript ${_unitySourceFiles}) cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles}) cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles}) if (NOT _unityFiles) return() endif() cotire_setup_unity_generation_commands( - ${_language} "${_targetSourceDir}" ${_target} "${_targetScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) + ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) cotire_make_prefix_file_path(${_language} ${_target} _prefixFile) if (_prefixFile) # check for user provided prefix header files get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) if (_prefixHeaderFiles) - cotire_setup_combine_command(${_language} "${_targetSourceDir}" "${_targetScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) + cotire_setup_prefix_generation_from_provided_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) else() - cotire_setup_multi_prefix_generation_command( - ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}" "${_prefixFile}" "${_unityFiles}" _cmds ${_unitySourceFiles}) + cotire_setup_prefix_generation_from_unity_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_unityFiles}" _cmds ${_unitySourceFiles}) + endif() + # check if selected language has enough sources at all + list (LENGTH _sourceFiles _numberOfSources) + if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + set (_targetUsePCH FALSE) + else() + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) endif() - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) if (_targetUsePCH) - cotire_make_pch_file_path(${_language} "${_targetSourceDir}" ${_target} _pchFile) + cotire_make_pch_file_path(${_language} ${_target} _pchFile) if (_pchFile) + # first file in _sourceFiles is passed as the host file cotire_setup_pch_file_compilation( - ${_language} ${_target} "${_targetSourceDir}" "${_targetScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) - if (_excludedSources) - set (_wholeTarget FALSE) - endif() + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) cotire_setup_pch_file_inclusion( ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) endif() elseif (_prefixHeaderFiles) - # user provided prefix header must be included - cotire_setup_prefix_file_inclusion( - ${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) + # user provided prefix header must be included unconditionally + cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) endif() endif() # mark target as cotired for language @@ -2412,7 +2644,6 @@ function (cotire_process_target_language _language _configurations _targetSource set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}") endif() endif() - set (${_wholeTargetVar} ${_wholeTarget} PARENT_SCOPE) set (${_cmdsVar} ${_cmds} PARENT_SCOPE) endfunction() @@ -2422,14 +2653,17 @@ function (cotire_setup_clean_target _target) cotire_set_cmd_to_prologue(_cmds) get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE) list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}") - add_custom_target(${_cleanTargetName} COMMAND ${_cmds} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Cleaning up target ${_target} cotire generated files" VERBATIM) + add_custom_target(${_cleanTargetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up target ${_target} cotire generated files" + VERBATIM) cotire_init_target("${_cleanTargetName}") endif() endfunction() function (cotire_setup_pch_target _languages _configurations _target) - if ("${CMAKE_GENERATOR}" MATCHES "Makefiles|Ninja") + if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") # for makefile based generators, we add a custom target to trigger the generation of the cotire related files set (_dependsFiles "") foreach (_language ${_languages}) @@ -2455,7 +2689,28 @@ function (cotire_setup_pch_target _languages _configurations _target) endif() endfunction() -function (cotire_setup_unity_build_target _languages _configurations _targetSourceDir _target) +function (cotire_collect_unity_target_sources _target _languages _unityTargetSourcesVar) + get_target_property(_targetSourceFiles ${_target} SOURCES) + set (_unityTargetSources ${_targetSourceFiles}) + foreach (_language ${_languages}) + get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) + if (_unityFiles) + # remove source files that are included in the unity source + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (_sourceFiles OR _cotiredSources) + list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) + endif() + # add unity source files instead + list (APPEND _unityTargetSources ${_unityFiles}) + endif() + endforeach() + set (${_unityTargetSourcesVar} ${_unityTargetSources} PARENT_SCOPE) +endfunction() + +function (cotire_setup_unity_build_target _languages _configurations _target) get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) if (NOT _unityTargetName) set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}") @@ -2467,45 +2722,28 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") set (_unityTargetSubType "${CMAKE_MATCH_1}") else() - message (WARNING "Unknown target type ${_targetType}.") + message (WARNING "cotire: target ${_target} has unknown target type ${_targetType}.") return() endif() # determine unity target sources - get_target_property(_targetSourceFiles ${_target} SOURCES) - set (_unityTargetSources ${_targetSourceFiles}) - foreach (_language ${_languages}) - get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) - if (_unityFiles) - # remove source files that are included in the unity source - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (_sourceFiles OR _cotiredSources) - list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) - endif() - # if cotire is applied to a target which has not been added in the current source dir, - # non-existing files cannot be referenced from the unity build target (this is a CMake restriction) - if (NOT "${_targetSourceDir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") - set (_nonExistingFiles "") - foreach (_file ${_unityTargetSources}) - if (NOT EXISTS "${_file}") - list (APPEND _nonExistingFiles "${_file}") - endif() - endforeach() - if (_nonExistingFiles) - if (COTIRE_VERBOSE) - message (STATUS "removing non-existing ${_nonExistingFiles} from ${_unityTargetName}") - endif() - list (REMOVE_ITEM _unityTargetSources ${_nonExistingFiles}) - endif() - endif() - # add unity source files instead - list (APPEND _unityTargetSources ${_unityFiles}) - endif() - endforeach() + set (_unityTargetSources "") + cotire_collect_unity_target_sources(${_target} "${_languages}" _unityTargetSources) + # handle automatic Qt processing + get_target_property(_targetAutoMoc ${_target} AUTOMOC) + get_target_property(_targetAutoUic ${_target} AUTOUIC) + get_target_property(_targetAutoRcc ${_target} AUTORCC) + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # if the original target sources are subject to CMake's automatic Qt processing, + # also include implicitly generated _automoc.cpp file + list (APPEND _unityTargetSources "${_target}_automoc.cpp") + set_property (SOURCE "${_target}_automoc.cpp" PROPERTY GENERATED TRUE) + endif() + # prevent AUTOMOC, AUTOUIC and AUTORCC properties from being set when the unity target is created + set (CMAKE_AUTOMOC OFF) + set (CMAKE_AUTOUIC OFF) + set (CMAKE_AUTORCC OFF) if (COTIRE_DEBUG) - message (STATUS "add ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") + message (STATUS "add target ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") endif() # generate unity target if ("${_targetType}" STREQUAL "EXECUTABLE") @@ -2513,16 +2751,21 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour else() add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) endif() + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # depend on the original target's implicity generated _automoc target + add_dependencies(${_unityTargetName} ${_target}_automoc) + endif() + # copy output location properties set (_outputDirProperties ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_ LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_ RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_) - # copy output location properties if (COTIRE_UNITY_OUTPUT_DIRECTORY) set (_setDefaultOutputDir TRUE) if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}") set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}") else() + # append relative COTIRE_UNITY_OUTPUT_DIRECTORY to target's actual output directory cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) cotire_resolve_config_properites("${_configurations}" _properties ${_outputDirProperties}) foreach (_property ${_properties}) @@ -2544,7 +2787,8 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour RUNTIME_OUTPUT_DIRECTORY "${_outputDir}") endif() else() - cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ${_outputDirProperties}) endif() # copy output name cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} @@ -2552,7 +2796,8 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_ OUTPUT_NAME OUTPUT_NAME_ RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_ - PREFIX _POSTFIX SUFFIX) + PREFIX _POSTFIX SUFFIX + IMPORT_PREFIX IMPORT_SUFFIX) # copy compile stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} COMPILE_DEFINITIONS COMPILE_DEFINITIONS_ @@ -2562,11 +2807,19 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_ POSITION_INDEPENDENT_CODE C_VISIBILITY_PRESET CXX_VISIBILITY_PRESET VISIBILITY_INLINES_HIDDEN) + # copy compile features + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + C_EXTENSIONS C_STANDARD C_STANDARD_REQUIRED + CXX_EXTENSIONS CXX_STANDARD CXX_STANDARD_REQUIRED + COMPILE_FEATURES) # copy interface stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} - COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_STRING - INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_OPTIONS INTERFACE_INCLUDE_DIRECTORIES - INTERFACE_LINK_LIBRARIES INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_NUMBER_MAX COMPATIBLE_INTERFACE_NUMBER_MIN + COMPATIBLE_INTERFACE_STRING + INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_FEATURES INTERFACE_COMPILE_OPTIONS + INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SOURCES + INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES + INTERFACE_AUTOUIC_OPTIONS NO_SYSTEM_FROM_IMPORTED) # copy link stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} BUILD_WITH_INSTALL_RPATH INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH @@ -2577,23 +2830,27 @@ function (cotire_setup_unity_build_target _languages _configurations _targetSour LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_ NO_SONAME SOVERSION VERSION) - # copy Qt stuff - cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} - AUTOMOC AUTOMOC_MOC_OPTIONS) # copy cmake stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK) # copy Apple platform specific stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} - BUNDLE BUNDLE_EXTENSION FRAMEWORK INSTALL_NAME_DIR MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST - MACOSX_RPATH OSX_ARCHITECTURES OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE) + BUNDLE BUNDLE_EXTENSION FRAMEWORK INSTALL_NAME_DIR MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST + MACOSX_FRAMEWORK_INFO_PLIST MACOSX_RPATH OSX_ARCHITECTURES + OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE) # copy Windows platform specific stuff cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} GNUtoMS + COMPILE_PDB_NAME COMPILE_PDB_NAME_ + COMPILE_PDB_OUTPUT_DIRECTORY COMPILE_PDB_OUTPUT_DIRECTORY_ PDB_NAME PDB_NAME_ PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_ - VS_DOTNET_REFERENCES VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE VS_KEYWORD - VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER - VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES WIN32_EXECUTABLE) + VS_DOTNET_REFERENCES VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE + VS_KEYWORD VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER + VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES VS_WINRT_COMPONENT + VS_DOTNET_TARGET_FRAMEWORK_VERSION WIN32_EXECUTABLE) + # copy Android platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ANDROID_API ANDROID_API_MIN ANDROID_GUI) # use output name from original target get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME) if (NOT _targetOutputName) @@ -2614,31 +2871,19 @@ endfunction(cotire_setup_unity_build_target) function (cotire_target _target) set(_options "") - set(_oneValueArgs SOURCE_DIR BINARY_DIR) + set(_oneValueArgs "") set(_multiValueArgs LANGUAGES CONFIGURATIONS) cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_SOURCE_DIR) - set (_option_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - endif() - if (NOT _option_BINARY_DIR) - set (_option_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() if (NOT _option_LANGUAGES) get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) endif() if (NOT _option_CONFIGURATIONS) - if (CMAKE_CONFIGURATION_TYPES) - set (_option_CONFIGURATIONS ${CMAKE_CONFIGURATION_TYPES}) - elseif (CMAKE_BUILD_TYPE) - set (_option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}") - else() - set (_option_CONFIGURATIONS "None") - endif() + cotire_get_configuration_types(_option_CONFIGURATIONS) endif() # trivial checks get_target_property(_imported ${_target} IMPORTED) if (_imported) - message (WARNING "Imported target ${_target} cannot be cotired.") + message (WARNING "cotire: imported target ${_target} cannot be cotired.") return() endif() # resolve alias @@ -2665,32 +2910,30 @@ function (cotire_target _target) return() endif() endif() + # when not using configuration types, immediately create cotire intermediate dir + if (NOT CMAKE_CONFIGURATION_TYPES) + cotire_get_intermediate_dir(_baseDir) + file (MAKE_DIRECTORY "${_baseDir}") + endif() # choose languages that apply to the target - cotire_choose_target_languages("${_option_SOURCE_DIR}" "${_target}" _targetLanguages ${_option_LANGUAGES}) + cotire_choose_target_languages("${_target}" _targetLanguages _wholeTarget ${_option_LANGUAGES}) if (NOT _targetLanguages) return() endif() - list (LENGTH _targetLanguages _numberOfLanguages) - if (_numberOfLanguages GREATER 1) - set (_wholeTarget FALSE) - else() - set (_wholeTarget TRUE) - endif() set (_cmds "") foreach (_language ${_targetLanguages}) - cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" - "${_option_SOURCE_DIR}" "${_option_BINARY_DIR}" ${_target} _wholeTarget _cmd) + cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" ${_target} ${_wholeTarget} _cmd) if (_cmd) list (APPEND _cmds ${_cmd}) endif() endforeach() get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) if (_targetAddSCU) - cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" "${_option_SOURCE_DIR}" ${_target}) + cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) endif() get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) if (_targetUsePCH) - cotire_setup_target_pch_usage("${_targetLanguages}" "${_option_SOURCE_DIR}" ${_target} ${_wholeTarget} ${_cmds}) + cotire_setup_target_pch_usage("${_targetLanguages}" ${_target} ${_wholeTarget} ${_cmds}) cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) endif() get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN) @@ -2699,7 +2942,26 @@ function (cotire_target _target) endif() endfunction(cotire_target) -function(cotire_target_link_libraries _target) +function (cotire_map_libraries _strategy _mappedLibrariesVar) + set (_mappedLibraries "") + foreach (_library ${ARGN}) + if (TARGET "${_library}" AND "${_strategy}" MATCHES "COPY_UNITY") + # use target's corresponding unity target, if available + get_target_property(_libraryUnityTargetName ${_library} COTIRE_UNITY_TARGET_NAME) + if (TARGET "${_libraryUnityTargetName}") + list (APPEND _mappedLibraries "${_libraryUnityTargetName}") + else() + list (APPEND _mappedLibraries "${_library}") + endif() + else() + list (APPEND _mappedLibraries "${_library}") + endif() + endforeach() + list (REMOVE_DUPLICATES _mappedLibraries) + set (${_mappedLibrariesVar} ${_mappedLibraries} PARENT_SCOPE) +endfunction() + +function (cotire_target_link_libraries _target) get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) if (TARGET "${_unityTargetName}") get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) @@ -2707,32 +2969,21 @@ function(cotire_target_link_libraries _target) message (STATUS "unity target ${_unityTargetName} link strategy: ${_linkLibrariesStrategy}") endif() if ("${_linkLibrariesStrategy}" MATCHES "^(COPY|COPY_UNITY)$") - if (CMAKE_VERSION VERSION_LESS "2.8.11") - message (WARNING "Unity target link strategy ${_linkLibrariesStrategy} requires CMake 2.8.11 or later. Defaulting to NONE for ${_target}.") - return() - endif() + set (_unityLinkLibraries "") get_target_property(_linkLibraries ${_target} LINK_LIBRARIES) if (_linkLibraries) - if (COTIRE_DEBUG) - message (STATUS "target ${_target} link libraries: ${_linkLibraries}") - endif() - set (_unityTargetLibraries "") - foreach (_library ${_linkLibraries}) - if (TARGET "${_library}" AND "${_linkLibrariesStrategy}" MATCHES "COPY_UNITY") - get_target_property(_libraryUnityTargetName ${_library} COTIRE_UNITY_TARGET_NAME) - if (TARGET "${_libraryUnityTargetName}") - list (APPEND _unityTargetLibraries "${_libraryUnityTargetName}") - else() - list (APPEND _unityTargetLibraries "${_library}") - endif() - else() - list (APPEND _unityTargetLibraries "${_library}") - endif() - endforeach() - set_property(TARGET ${_unityTargetName} APPEND PROPERTY LINK_LIBRARIES ${_unityTargetLibraries}) - if (COTIRE_DEBUG) - message (STATUS "set unity target ${_unityTargetName} link libraries: ${_unityTargetLibraries}") - endif() + list (APPEND _unityLinkLibraries ${_linkLibraries}) + endif() + get_target_property(_interfaceLinkLibraries ${_target} INTERFACE_LINK_LIBRARIES) + if (_interfaceLinkLibraries) + list (APPEND _unityLinkLibraries ${_interfaceLinkLibraries}) + endif() + cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkLibraries ${_unityLinkLibraries}) + if (COTIRE_DEBUG) + message (STATUS "unity target ${_unityTargetName} libraries: ${_unityLinkLibraries}") + endif() + if (_unityLinkLibraries) + target_link_libraries(${_unityTargetName} ${_unityLinkLibraries}) endif() endif() endif() @@ -2755,7 +3006,7 @@ function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName) endforeach() if (_filesToRemove) if (COTIRE_VERBOSE) - message (STATUS "removing ${_filesToRemove}") + message (STATUS "cleaning up ${_filesToRemove}") endif() file (REMOVE ${_filesToRemove}) endif() @@ -2765,6 +3016,7 @@ function (cotire_init_target _targetName) if (COTIRE_TARGETS_FOLDER) set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}") endif() + set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_ALL TRUE) if (MSVC_IDE) set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) endif() @@ -2773,7 +3025,9 @@ endfunction() function (cotire_add_to_pch_all_target _pchTargetName) set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}") if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM) + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) cotire_init_target("${_targetName}") endif() cotire_setup_clean_all_target() @@ -2783,7 +3037,9 @@ endfunction() function (cotire_add_to_unity_all_target _unityTargetName) set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}") if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" VERBATIM) + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) cotire_init_target("${_targetName}") endif() cotire_setup_clean_all_target() @@ -2795,30 +3051,26 @@ function (cotire_setup_clean_all_target) if (NOT TARGET "${_targetName}") cotire_set_cmd_to_prologue(_cmds) list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}") - add_custom_target(${_targetName} COMMAND ${_cmds} - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Cleaning up all cotire generated files" VERBATIM) + add_custom_target(${_targetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up all cotire generated files" + VERBATIM) cotire_init_target("${_targetName}") endif() endfunction() function (cotire) set(_options "") - set(_oneValueArgs SOURCE_DIR BINARY_DIR) + set(_oneValueArgs "") set(_multiValueArgs LANGUAGES CONFIGURATIONS) cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) set (_targets ${_option_UNPARSED_ARGUMENTS}) - if (NOT _option_SOURCE_DIR) - set (_option_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - endif() - if (NOT _option_BINARY_DIR) - set (_option_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() foreach (_target ${_targets}) if (TARGET ${_target}) - cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS} - SOURCE_DIR "${_option_SOURCE_DIR}" BINARY_DIR "${_option_BINARY_DIR}") + cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}) else() - message (WARNING "${_target} is not a target.") + message (WARNING "cotire: ${_target} is not a target.") endif() endforeach() foreach (_target ${_targets}) @@ -2852,19 +3104,12 @@ if (CMAKE_SCRIPT_MODE_FILE) message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}") endif() - if (WIN32) - # for MSVC, compiler IDs may not always be set correctly - if (MSVC) - set (CMAKE_C_COMPILER_ID "MSVC") - set (CMAKE_CXX_COMPILER_ID "MSVC") - endif() - endif() - if (NOT COTIRE_BUILD_TYPE) set (COTIRE_BUILD_TYPE "None") endif() string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig) set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}}) + set (_systemIncludeDirs ${COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}}) set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}}) set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}}) # check if target has been cotired for actual build type COTIRE_BUILD_TYPE @@ -2877,6 +3122,7 @@ if (CMAKE_SCRIPT_MODE_FILE) message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})") endif() set (_sources "") + set (_sourceLocations "") set (_sourcesDefinitions "") endif() set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS}) @@ -2886,19 +3132,36 @@ if (CMAKE_SCRIPT_MODE_FILE) if ("${COTIRE_ARGV1}" STREQUAL "unity") + if (XCODE) + # executing pre-build action under Xcode, check dependency on target script + set (_dependsOption DEPENDS "${COTIRE_ARGV2}") + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources}) + cotire_generate_unity_source( "${COTIRE_ARGV3}" ${_sources} LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - DEPENDS "${COTIRE_ARGV0}" "${COTIRE_ARGV2}" SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions} PRE_UNDEFS ${_targetPreUndefs} POST_UNDEFS ${_targetPostUndefs} SOURCES_PRE_UNDEFS ${_sourcesPreUndefs} - SOURCES_POST_UNDEFS ${_sourcesPostUndefs}) + SOURCES_POST_UNDEFS ${_sourcesPostUndefs} + ${_dependsOption}) elseif ("${COTIRE_ARGV1}" STREQUAL "prefix") + if (XCODE) + # executing pre-build action under Xcode, check dependency on unity file and prefix dependencies + set (_dependsOption DEPENDS "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}) + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + set (_files "") foreach (_index RANGE 4 ${COTIRE_ARGC}) if (COTIRE_ARGV${_index}) @@ -2911,15 +3174,18 @@ if (CMAKE_SCRIPT_MODE_FILE) COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${COTIRE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - DEPENDS "${COTIRE_ARGV0}" "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS} IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}" INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH} IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}" + INCLUDE_PRIORITY_PATH ${COTIRE_TARGET_INCLUDE_PRIORITY_PATH} + INCLUDE_SYSTEM_FLAG "${COTIRE_INCLUDE_SYSTEM_FLAG}" INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} COMPILE_DEFINITIONS ${_compileDefinitions} - COMPILE_FLAGS ${_compileFlags}) + COMPILE_FLAGS ${_compileFlags} + ${_dependsOption}) elseif ("${COTIRE_ARGV1}" STREQUAL "precompile") @@ -2935,18 +3201,22 @@ if (CMAKE_SCRIPT_MODE_FILE) COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${COTIRE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + INCLUDE_SYSTEM_FLAG "${COTIRE_INCLUDE_SYSTEM_FLAG}" INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} COMPILE_DEFINITIONS ${_compileDefinitions} COMPILE_FLAGS ${_compileFlags}) elseif ("${COTIRE_ARGV1}" STREQUAL "combine") if (COTIRE_TARGET_LANGUAGE) - set (_startIndex 3) + set (_combinedFile "${COTIRE_ARGV3}") + set (_startIndex 4) else() - set (_startIndex 2) + set (_combinedFile "${COTIRE_ARGV2}") + set (_startIndex 3) endif() set (_files "") foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC}) @@ -2954,10 +3224,22 @@ if (CMAKE_SCRIPT_MODE_FILE) list (APPEND _files "${COTIRE_ARGV${_index}}") endif() endforeach() - if (COTIRE_TARGET_LANGUAGE) - cotire_generate_unity_source(${_files} LANGUAGE "${COTIRE_TARGET_LANGUAGE}") + + if (XCODE) + # executing pre-build action under Xcode, check dependency on files to be combined + set (_dependsOption DEPENDS ${_files}) else() - cotire_generate_unity_source(${_files}) + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + if (COTIRE_TARGET_LANGUAGE) + cotire_generate_unity_source( + "${_combinedFile}" ${_files} + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + ${_dependsOption}) + else() + cotire_generate_unity_source("${_combinedFile}" ${_files} ${_dependsOption}) endif() elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup") @@ -2965,7 +3247,7 @@ if (CMAKE_SCRIPT_MODE_FILE) cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}") else() - message (FATAL_ERROR "Unknown cotire command \"${COTIRE_ARGV1}\".") + message (FATAL_ERROR "cotire: unknown command \"${COTIRE_ARGV1}\".") endif() else() @@ -2973,9 +3255,6 @@ else() # cotire is being run in include mode # set up all variable and property definitions - unset (COTIRE_C_COMPILER_VERSION CACHE) - unset (COTIRE_CXX_COMPILER_VERSION CACHE) - if (NOT DEFINED COTIRE_DEBUG_INIT) if (DEFINED COTIRE_DEBUG) set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG}) @@ -3043,6 +3322,13 @@ else() if (NOT COTIRE_PCH_TARGET_SUFFIX) set (COTIRE_PCH_TARGET_SUFFIX "_pch") endif() + if (MSVC) + # MSVC default PCH memory scaling factor of 100 percent (75 MB) is too small for template heavy C++ code + # use a bigger default factor of 140 percent (105 MB) + if (NOT DEFINED COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (COTIRE_PCH_MEMORY_SCALING_FACTOR "140") + endif() + endif() if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX) set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity") endif() @@ -3148,6 +3434,13 @@ else() "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH." ) + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" + BRIEF_DOCS "Header paths matching one of these directories are put at the top of the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH." + ) + define_property( DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file." @@ -3234,6 +3527,16 @@ else() "If not set, this property is initialized to the empty list." ) + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" INHERITED + BRIEF_DOCS "Header paths matching one of these directories are put at the top of prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "Header file paths matching one of these directories will be inserted at the beginning of the generated prefix header." + "Header files are sorted according to the order of the directories in the property." + "If not set, this property is initialized to the empty list." + ) + define_property( TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file." @@ -3289,7 +3592,7 @@ else() TARGET PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" INHERITED BRIEF_DOCS "Define strategy for setting up unity target's link libraries." FULL_DOCS - "If this property is empty, the generated unity target's link libraries have to be set up manually." + "If this property is empty or set to NONE, the generated unity target's link libraries have to be set up manually." "If this property is set to COPY, the unity target's link libraries will be copied from this target." "If this property is set to COPY_UNITY, the unity target's link libraries will be copied from this target with considering existing unity targets." "Inherited from directory." diff --git a/README.md b/README.md index f4e8fbd3..53f74942 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ This is a quick introduction to get new developers up to speed on Graphene. Starting Graphene ----------------- +For Ubuntu 14.04 LTS users, see this link first: + https://github.com/cryptonomex/graphene/wiki/build-ubuntu + +and then proceed with: + git clone https://github.com/cryptonomex/graphene.git cd graphene git submodule update --init --recursive @@ -13,7 +18,9 @@ Starting Graphene make ./programs/witness_node/witness_node -This will launch the witness node. If you would like to launch the command-line wallet, you must first specify a port for communication with the witness node. To do this, add text to `witness_node_data_dir/config.ini` as follows, then restart the node: +This will launch the witness node. If you would like to launch the command-line wallet, you must first specify a port +for communication with the witness node. To do this, add text to `witness_node_data_dir/config.ini` as follows, then +restart the node: rpc-endpoint = 127.0.0.1:8090 @@ -26,14 +33,43 @@ To set your iniital password to 'password' use: >>> set_password password >>> unlock password +To import your initial balance: + + >>> import_balance nathan [5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3] true + If you send private keys over this connection, `rpc-endpoint` should be bound to localhost for security. -A list of CLI wallet commands is available [here](https://bitshares.github.io/doxygen/classgraphene_1_1wallet_1_1wallet__api.html). +A list of CLI wallet commands is available +[here](https://github.com/cryptonomex/graphene/blob/master/libraries/wallet/include/graphene/wallet/wallet.hpp). Code coverage testing --------------------- -TODO: Write something here +Check how much code is covered by unit tests, using gcov/lcov (see http://ltp.sourceforge.net/coverage/lcov.php ). + + cmake -D ENABLE_COVERAGE_TESTING=true -D CMAKE_BUILD_TYPE=Debug . + make + lcov --capture --initial --directory . --output-file base.info --no-external + libraries/fc/bloom_test + libraries/fc/task_cancel_test + libraries/fc/api + libraries/fc/blind + libraries/fc/ecc_test test + libraries/fc/real128_test + libraries/fc/lzma_test README.md + libraries/fc/ntp_test + tests/intense_test + tests/app_test + tests/chain_bench + tests/chain_test + tests/performance_test + lcov --capture --directory . --output-file test.info --no-external + lcov --add-tracefile base.info --add-tracefile test.info --output-file total.info + lcov -o interesting.info -r total.info libraries/fc/vendor/\* libraries/fc/tests/\* tests/\* + mkdir -p lcov + genhtml interesting.info --output-directory lcov --prefix `pwd` + +Now open `lcov/index.html` in a browser. Unit testing ------------ @@ -46,7 +82,9 @@ Witness node The role of the witness node is to broadcast transactions, download blocks, and optionally sign them. -./witness_node --rpc-endpoint "127.0.0.1:8090" --enable-stale-production -w \""1.7.0"\" \""1.7.1"\" \""1.7.2"\" \""1.7.3"\" \""1.7.4"\" --private-key "[\"1.2.0\",\"aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e\"]" +``` +./witness_node --rpc-endpoint "127.0.0.1:8090" --enable-stale-production -w \""1.6.0"\" \""1.6.1"\" \""1.6.2"\" \""1.6.3"\" \""1.6.4"\" +``` Running specific tests ---------------------- @@ -61,32 +99,57 @@ When running `witness_node`, initially two API's are available: API 0 provides read-only access to the database, while API 1 is used to login and gain access to additional, restricted API's. +TODO: the following examples use the old authority definition and thus do not work. They need to be updated. + Here is an example using `wscat` package from `npm` for websockets: $ npm install -g wscat $ wscat -c ws://127.0.0.1:8090 - > {"id":1, "method":"call", "params":[0,"get_accounts",[["1.3.0"]]]} - < {"id":1,"result":[{"id":"1.3.0","annotations":[],"registrar":"1.3.0","referrer":"1.3.0","referrer_percent":0,"name":"genesis","owner":{"weight_threshold":1,"auths":[["1.2.0",1]]},"active":{"weight_threshold":1,"auths":[["1.2.0",1]]},"memo_key":"1.2.0","voting_account":"1.3.0","num_witness":0,"num_committee":0,"votes":[],"statistics":"2.7.0","whitelisting_accounts":[],"blacklisting_accounts":[]}]} + > {"id":1, "method":"call", "params":[0,"get_accounts",[["1.2.0"]]]} + < {"id":1,"result":[{"id":"1.2.0","annotations":[],"registrar":"1.2.0","referrer":"1.2.0","referrer_percent":0,"name":"genesis","owner":{"weight_threshold":1,"key_auths":[["PUBLIC_KEY",1]]},"active":{"weight_threshold":1,"key_auths":[["PUBLIC_KEY",1]]},"memo_key":"PUBLIC_KEY","voting_account":"1.2.0","num_witness":0,"num_committee":0,"votes":[],"statistics":"2.7.0","whitelisting_accounts":[],"blacklisting_accounts":[]}]} $ We can do the same thing using an HTTP client such as `curl` for API's which do not require login or other session state: - $ curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0, "get_accounts", [["1.3.0"]]], "id": 1}' http://127.0.0.1:8090/rpc - {"id":1,"result":[{"id":"1.3.0","annotations":[],"registrar":"1.3.0","referrer":"1.3.0","referrer_percent":0,"name":"genesis","owner":{"weight_threshold":1,"auths":[["1.2.0",1]]},"active":{"weight_threshold":1,"auths":[["1.2.0",1]]},"memo_key":"1.2.0","voting_account":"1.3.0","num_witness":0,"num_committee":0,"votes":[],"statistics":"2.7.0","whitelisting_accounts":[],"blacklisting_accounts":[]}]} + $ curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0, "get_accounts", [["1.2.0"]]], "id": 1}' http://127.0.0.1:8090/rpc + {"id":1,"result":[{"id":"1.2.0","annotations":[],"registrar":"1.2.0","referrer":"1.2.0","referrer_percent":0,"name":"genesis","owner":{"weight_threshold":1,"key_auths":[["PUBLIC_KEY",1]]},"active":{"weight_threshold":1,"key_auths":[["PUBLIC_KEY",1]]},"memo_key":"PUBLIC_KEY","voting_account":"1.2.0","num_witness":0,"num_committee":0,"votes":[],"statistics":"2.7.0","whitelisting_accounts":[],"blacklisting_accounts":[]}]} API 0 is accessible using regular JSON-RPC: - $ curl --data '{"jsonrpc": "2.0", "method": "get_accounts", "params": [["1.3.0"]], "id": 1}' http://127.0.0.1:8090/rpc + $ curl --data '{"jsonrpc": "2.0", "method": "get_accounts", "params": [["1.2.0"]], "id": 1}' http://127.0.0.1:8090/rpc -You can use the login API to obtain `network`, `database` and `history` API's. Here is an example of how to call `add_node` from the `network` API: +Accessing restricted API's +-------------------------- + +You can restrict API's to particular users by specifying an `apiaccess` file in `config.ini`. Here is an example `apiaccess` file which allows +user `bytemaster` with password `supersecret` to access four different API's: + + { + "permission_map" : + [ + [ + "bytemaster", + { + "password_hash_b64" : "9e9GF7ooXVb9k4BoSfNIPTelXeGOZ5DrgOYMj94elaY=", + "password_salt_b64" : "INDdM6iCi/8=", + "allowed_apis" : ["database_api", "network_broadcast_api", "history_api", "network_node_api"] + } + ] + ] + } + +Passwords are stored in `base64` as as salted `sha256` hashes. A simple Python script, `saltpass.py` is avaliable to obtain hash and salt values from a password. +A single asterisk `"*"` may be specified as username or password hash to accept any value. + +With the above configuration, here is an example of how to call `add_node` from the `network_node` API: {"id":1, "method":"call", "params":[1,"login",["bytemaster", "supersecret"]]} - {"id":2, "method":"call", "params":[1,"network",[]]} + {"id":2, "method":"call", "params":[1,"network_node",[]]} {"id":3, "method":"call", "params":[2,"add_node",["127.0.0.1:9090"]]} -Note, the call to `network` is necessary to obtain the correct API identifier for the network API. It is not guaranteed that the network API identifier will always be `2`. +Note, the call to `network_node` is necessary to obtain the correct API identifier for the network API. It is not guaranteed that the network API identifier will always be `2`. -Since the `network` API requires login, it is only accessible over the websocket RPC. Our `doxygen` documentation contains the most up-to-date information +Since the `network_node` API requires login, it is only accessible over the websocket RPC. Our `doxygen` documentation contains the most up-to-date information about API's for the [witness node](https://bitshares.github.io/doxygen/namespacegraphene_1_1app.html) and the [wallet](https://bitshares.github.io/doxygen/classgraphene_1_1wallet_1_1wallet__api.html). If you want information which is not available from an API, it might be available @@ -109,16 +172,16 @@ witness just because it has the correct private key to do so. There are ten witnesses at genesis of the testnet, block production can be enabled for all of them by specifying multiple times in `config.ini`: - witness-id = "1.7.0" - witness-id = "1.7.1" - witness-id = "1.7.2" - witness-id = "1.7.3" - witness-id = "1.7.4" - witness-id = "1.7.5" - witness-id = "1.7.6" - witness-id = "1.7.7" - witness-id = "1.7.8" - witness-id = "1.7.9" + witness-id = "1.6.0" + witness-id = "1.6.1" + witness-id = "1.6.2" + witness-id = "1.6.3" + witness-id = "1.6.4" + witness-id = "1.6.5" + witness-id = "1.6.6" + witness-id = "1.6.7" + witness-id = "1.6.8" + witness-id = "1.6.9" Questions --------- @@ -126,7 +189,15 @@ Questions - Is there a way to generate help with parameter names and method descriptions? Yes. Documentation of the code base, including APIs, can be generated using Doxygen. Simply run `doxygen` in this directory. - We are thinking of integrating Doxygen's XML output format to provide a better `help` command to the CLI wallet. + + If both Doxygen and perl are available in your build environment, the CLI wallet's `help` and `gethelp` + commands will display help generated from the doxygen documentation. + + If your CLI wallet's `help` command displays descriptions without parameter names like + `signed_transaction transfer(string, string, string, string, string, bool)` + it means CMake was unable to find Doxygen or perl during configuration. If found, the + output should look like this: + `signed_transaction transfer(string from, string to, string amount, string asset_symbol, string memo, bool broadcast)` - Is there a way to allow external program to drive `cli_wallet` via websocket, JSONRPC, or HTTP? @@ -159,13 +230,13 @@ Questions - The answer to the previous question was really confusing. Can you make it clearer? - All account ID's are of the form `1.3.x`. If you were the 9735th account to be registered, - your account's ID will be `1.3.9735`. Account `0` is special (it's the "genesis account," - which is controlled by the delegates and has a few abilities and restrictions other accounts + All account ID's are of the form `1.2.x`. If you were the 9735th account to be registered, + your account's ID will be `1.2.9735`. Account `0` is special (it's the "committee account," + which is controlled by the committee members and has a few abilities and restrictions other accounts do not). - All asset ID's are of the form `1.4.x`. If you were the 29th asset to be registered, - your asset's ID will be `1.4.29`. Asset `0` is special (it's BTS, which is considered the "core asset"). + All asset ID's are of the form `1.3.x`. If you were the 29th asset to be registered, + your asset's ID will be `1.3.29`. Asset `0` is special (it's BTS, which is considered the "core asset"). - The first and second number together identify the kind of thing you're talking about (`1.3` for accounts, - `1.4` for assets). The third number identifies the particular thing. + The first and second number together identify the kind of thing you're talking about (`1.2` for accounts, + `1.3` for assets). The third number identifies the particular thing. diff --git a/docs b/docs index 1b53a8ec..97435c1a 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 1b53a8eca77783d073ce7cc95991447c3f34b927 +Subproject commit 97435c1a622e41e0a5fc1be72aaadea62e1b7adb diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index 308ee736..4e798712 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -6,10 +6,10 @@ add_library( graphene_app plugin.cpp ) -target_link_libraries( graphene_app graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities ) +target_link_libraries( graphene_app graphene_market_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) if(MSVC) - set_source_files_properties( application.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) + set_source_files_properties( application.cpp api.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 98d25611..f192c765 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -16,11 +16,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include -#include #include #include -#include +#include #include @@ -61,6 +61,13 @@ namespace graphene { namespace app { { return _db.fetch_block_by_number(block_num); } + processed_transaction database_api::get_transaction(uint32_t block_num, uint32_t trx_num)const + { + auto opt_block = _db.fetch_block_by_number(block_num); + FC_ASSERT( opt_block ); + FC_ASSERT( opt_block->transactions.size() > trx_num ); + return opt_block->transactions[trx_num]; + } vector> database_api::lookup_account_names(const vector& account_names)const { @@ -98,17 +105,6 @@ namespace graphene { namespace app { return _db.get(dynamic_global_property_id_type()); } - vector> database_api::get_keys(const vector& key_ids)const - { - vector> result; result.reserve(key_ids.size()); - std::transform(key_ids.begin(), key_ids.end(), std::back_inserter(result), - [this](key_id_type id) -> optional { - if(auto o = _db.find(id)) - return *o; - return {}; - }); - return result; - } vector> database_api::get_accounts(const vector& account_ids)const { @@ -167,7 +163,7 @@ namespace graphene { namespace app { else { result.reserve(assets.size()); - + std::transform(assets.begin(), assets.end(), std::back_inserter(result), [this, acnt](asset_id_type id) { return _db.get_balance(acnt, id); }); } @@ -215,22 +211,6 @@ namespace graphene { namespace app { return result; } - vector database_api::get_short_orders(asset_id_type a, uint32_t limit)const - { - const auto& short_order_idx = _db.get_index_type(); - const auto& sell_price_idx = short_order_idx.indices().get(); - const asset_object& mia = _db.get(a); - - FC_ASSERT( mia.is_market_issued(), "must be a market issued asset" ); - - price index_price = price::min(mia.get_id(), mia.bitasset_data(_db).options.short_backing_asset); - - auto short_itr = sell_price_idx.lower_bound(index_price.max()); - auto short_end = sell_price_idx.upper_bound(index_price.min()); - - return vector(short_itr, short_end); - } - vector database_api::get_call_orders(asset_id_type a, uint32_t limit)const { const auto& call_index = _db.get_index_type().indices().get(); @@ -258,7 +238,7 @@ namespace graphene { namespace app { auto itr = assets_by_symbol.lower_bound(lower_bound_symbol); - if( lower_bound_symbol == "" ) + if( lower_bound_symbol == "" ) itr = assets_by_symbol.begin(); while(limit-- && itr != assets_by_symbol.end()) @@ -267,46 +247,213 @@ namespace graphene { namespace app { return result; } + fc::optional database_api::get_committee_member_by_account(account_id_type account) const + { + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find(account); + if( itr != idx.end() ) + return *itr; + return {}; + } + + fc::optional database_api::get_witness_by_account(account_id_type account) const + { + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find(account); + if( itr != idx.end() ) + return *itr; + return {}; + } + + uint64_t database_api::get_witness_count()const + { + return _db.get_index_type().indices().size(); + } + + map database_api::lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const + { + FC_ASSERT( limit <= 1000 ); + const auto& witnesses_by_id = _db.get_index_type().indices().get(); + + // we want to order witnesses by account name, but that name is in the account object + // so the witness_index doesn't have a quick way to access it. + // get all the names and look them all up, sort them, then figure out what + // records to return. This could be optimized, but we expect the + // number of witnesses to be few and the frequency of calls to be rare + std::map witnesses_by_account_name; + for (const witness_object& witness : witnesses_by_id) + if (auto account_iter = _db.find(witness.witness_account)) + if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name + witnesses_by_account_name.insert(std::make_pair(account_iter->name, witness.id)); + + auto end_iter = witnesses_by_account_name.begin(); + while (end_iter != witnesses_by_account_name.end() && limit--) + ++end_iter; + witnesses_by_account_name.erase(end_iter, witnesses_by_account_name.end()); + return witnesses_by_account_name; + } + + map database_api::lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const + { + FC_ASSERT( limit <= 1000 ); + const auto& committee_members_by_id = _db.get_index_type().indices().get(); + + // we want to order committee_members by account name, but that name is in the account object + // so the committee_member_index doesn't have a quick way to access it. + // get all the names and look them all up, sort them, then figure out what + // records to return. This could be optimized, but we expect the + // number of committee_members to be few and the frequency of calls to be rare + std::map committee_members_by_account_name; + for (const committee_member_object& committee_member : committee_members_by_id) + if (auto account_iter = _db.find(committee_member.committee_member_account)) + if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name + committee_members_by_account_name.insert(std::make_pair(account_iter->name, committee_member.id)); + + auto end_iter = committee_members_by_account_name.begin(); + while (end_iter != committee_members_by_account_name.end() && limit--) + ++end_iter; + committee_members_by_account_name.erase(end_iter, committee_members_by_account_name.end()); + return committee_members_by_account_name; + } + + vector> database_api::get_witnesses(const vector& witness_ids)const + { + vector> result; result.reserve(witness_ids.size()); + std::transform(witness_ids.begin(), witness_ids.end(), std::back_inserter(result), + [this](witness_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; + } + + vector> database_api::get_committee_members(const vector& committee_member_ids)const + { + vector> result; result.reserve(committee_member_ids.size()); + std::transform(committee_member_ids.begin(), committee_member_ids.end(), std::back_inserter(result), + [this](committee_member_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; + } + login_api::login_api(application& a) :_app(a) { } + login_api::~login_api() { } bool login_api::login(const string& user, const string& password) { - auto db_api = std::make_shared(std::ref(*_app.chain_database())); - auto net_api = std::make_shared(std::ref(_app)); - auto hist_api = std::make_shared(_app); - _database_api = db_api; - _network_api = net_api; - _history_api = hist_api; + optional< api_access_info > acc = _app.get_api_access_info( user ); + if( !acc.valid() ) + return false; + if( acc->password_hash_b64 != "*" ) + { + std::string password_salt = fc::base64_decode( acc->password_salt_b64 ); + std::string acc_password_hash = fc::base64_decode( acc->password_hash_b64 ); + + fc::sha256 hash_obj = fc::sha256::hash( password + password_salt ); + if( hash_obj.data_size() != acc_password_hash.length() ) + return false; + if( memcmp( hash_obj.data(), acc_password_hash.c_str(), hash_obj.data_size() ) != 0 ) + return false; + } + + for( const std::string& api_name : acc->allowed_apis ) + enable_api( api_name ); return true; } - void network_api::add_node(const fc::ip::endpoint& ep) + void login_api::enable_api( const std::string& api_name ) { - _app.p2p_node()->add_node(ep); + if( api_name == "database_api" ) + { + _database_api = std::make_shared< database_api >( std::ref( *_app.chain_database() ) ); + } + else if( api_name == "network_broadcast_api" ) + { + _network_broadcast_api = std::make_shared< network_broadcast_api >( std::ref( _app ) ); + } + else if( api_name == "history_api" ) + { + _history_api = std::make_shared< history_api >( _app ); + } + else if( api_name == "network_node_api" ) + { + _network_node_api = std::make_shared< network_node_api >( std::ref(_app) ); + } + return; } - void network_api::broadcast_transaction(const signed_transaction& trx) + network_broadcast_api::network_broadcast_api(application& a):_app(a) + { + _applied_block_connection = _app.chain_database()->applied_block.connect([this](const signed_block& b){ on_applied_block(b); }); + } + + void network_broadcast_api::on_applied_block( const signed_block& b ) + { + if( _callbacks.size() ) + { + for( uint32_t trx_num = 0; trx_num < b.transactions.size(); ++trx_num ) + { + const auto& trx = b.transactions[trx_num]; + auto id = trx.id(); + auto itr = _callbacks.find(id); + auto block_num = b.block_num(); + if( itr != _callbacks.end() ) + { + fc::async( [=](){ itr->second( fc::variant(transaction_confirmation{ id, block_num, trx_num, trx}) ); } ); + } + } + } + } + + void network_broadcast_api::broadcast_transaction(const signed_transaction& trx) { trx.validate(); _app.chain_database()->push_transaction(trx); _app.p2p_node()->broadcast_transaction(trx); } - std::vector network_api::get_connected_peers() const + void network_broadcast_api::broadcast_transaction_with_callback( confirmation_callback cb, const signed_transaction& trx) + { + trx.validate(); + _callbacks[trx.id()] = cb; + _app.chain_database()->push_transaction(trx); + _app.p2p_node()->broadcast_transaction(trx); + } + + network_node_api::network_node_api( application& a ) : _app( a ) + { + } + + void network_node_api::add_node(const fc::ip::endpoint& ep) + { + _app.p2p_node()->add_node(ep); + } + + std::vector network_node_api::get_connected_peers() const { return _app.p2p_node()->get_connected_peers(); } - fc::api login_api::network()const + fc::api login_api::network_broadcast()const { - FC_ASSERT(_network_api); - return *_network_api; + FC_ASSERT(_network_broadcast_api); + return *_network_broadcast_api; + } + + fc::api login_api::network_node()const + { + FC_ASSERT(_network_node_api); + return *_network_node_api; } fc::api login_api::database()const @@ -336,6 +483,10 @@ namespace graphene { namespace app { { _subscriptions[id](obj->to_variant()); } + else + { + _subscriptions[id](fc::variant(id)); + } } }); } @@ -358,15 +509,11 @@ namespace graphene { namespace app { case operation::tag::value: market = op.op.get().get_market(); break; - case operation::tag::value: - market = op.op.get().get_market(); - break; case operation::tag::value: market = op.op.get().get_market(); break; /* case operation::tag::value: - case operation::tag::value: */ default: break; } @@ -428,7 +575,7 @@ namespace graphene { namespace app { return fc::to_hex(fc::raw::pack(trx)); } - vector history_api::get_account_history(account_id_type account, operation_history_id_type stop, int limit, operation_history_id_type start) const + vector history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const { FC_ASSERT(_app.chain_database()); const auto& db = *_app.chain_database(); @@ -450,4 +597,127 @@ namespace graphene { namespace app { return result; } + + flat_set history_api::get_market_history_buckets()const + { + auto hist = _app.get_plugin( "market_history" ); + FC_ASSERT( hist ); + return hist->tracked_buckets(); + } + + vector history_api::get_market_history( asset_id_type a, asset_id_type b, + uint32_t bucket_seconds, fc::time_point_sec start, fc::time_point_sec end )const + { try { + FC_ASSERT(_app.chain_database()); + const auto& db = *_app.chain_database(); + vector result; + result.reserve(100); + + if( a > b ) std::swap(a,b); + + const auto& bidx = db.get_index_type(); + const auto& by_key_idx = bidx.indices().get(); + + auto itr = by_key_idx.lower_bound( bucket_key( a, b, bucket_seconds, start ) ); + while( itr != by_key_idx.end() && itr->key.open <= end && result.size() < 100 ) + { + if( !(itr->key.base == a && itr->key.quote == b && itr->key.seconds == bucket_seconds) ) + return result; + result.push_back(*itr); + ++itr; + } + return result; + } FC_CAPTURE_AND_RETHROW( (a)(b)(bucket_seconds)(start)(end) ) } + + /** + * @return all accounts that referr to the key or account id in their owner or active authorities. + */ + vector database_api::get_account_references( account_id_type account_id )const + { + const auto& idx = _db.get_index_type(); + const auto& aidx = dynamic_cast&>(idx); + const auto& refs = aidx.get_secondary_index(); + auto itr = refs.account_to_account_memberships.find(account_id); + vector result; + + if( itr != refs.account_to_account_memberships.end() ) + { + result.reserve( itr->second.size() ); + for( auto item : itr->second ) result.push_back(item); + } + return result; + } + /** + * @return all accounts that referr to the key or account id in their owner or active authorities. + */ + vector database_api::get_key_references( public_key_type key )const + { + const auto& idx = _db.get_index_type(); + const auto& aidx = dynamic_cast&>(idx); + const auto& refs = aidx.get_secondary_index(); + auto itr = refs.account_to_key_memberships.find(key); + vector result; + + if( itr != refs.account_to_key_memberships.end() ) + { + result.reserve( itr->second.size() ); + for( auto item : itr->second ) result.push_back(item); + } + return result; + } + + /** TODO: add secondary index that will accelerate this process */ + vector database_api::get_proposed_transactions( account_id_type id )const + { + const auto& idx = _db.get_index_type(); + vector result; + + idx.inspect_all_objects( [&](const object& obj){ + const proposal_object& p = static_cast(obj); + if( p.required_active_approvals.find( id ) != p.required_active_approvals.end() ) + result.push_back(p); + else if ( p.required_owner_approvals.find( id ) != p.required_owner_approvals.end() ) + result.push_back(p); + else if ( p.available_active_approvals.find( id ) != p.available_active_approvals.end() ) + result.push_back(p); + }); + return result; + } + + vector database_api::get_margin_positions( const account_id_type& id )const + { try { + const auto& idx = _db.get_index_type(); + const auto& aidx = idx.indices().get(); + auto start = aidx.lower_bound( boost::make_tuple( id, 0 ) ); + auto end = aidx.lower_bound( boost::make_tuple( id+1, 0 ) ); + vector result; + while( start != end ) + { + result.push_back(*start); + ++start; + } + return result; + } FC_CAPTURE_AND_RETHROW( (id) ) } + + + vector database_api::get_balance_objects( const vector
& addrs )const + { try { + const auto& bal_idx = _db.get_index_type(); + const auto& by_owner_idx = bal_idx.indices().get(); + + vector result; + + for( const auto& owner : addrs ) + { + auto itr = by_owner_idx.lower_bound( boost::make_tuple( owner, asset_id_type(0) ) ); + while( itr != by_owner_idx.end() && itr->owner == owner ) + { + result.push_back( *itr ); + ++itr; + } + } + return result; + } FC_CAPTURE_AND_RETHROW( (addrs) ) } + + } } // graphene::app diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 13a62f20..9eb15af7 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -15,20 +15,24 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include #include #include -#include #include #include #include +#include +#include #include #include #include +#include #include @@ -50,8 +54,37 @@ using chain::block_id_type; using std::vector; +namespace bpo = boost::program_options; + namespace detail { + genesis_state_type create_example_genesis() { + auto nathan_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + dlog("Allocating all stake to ${key}", ("key", utilities::key_to_wif(nathan_key))); + genesis_state_type initial_state; + initial_state.initial_parameters.current_fees = fee_schedule::get_default();//->set_all_fees(GRAPHENE_BLOCKCHAIN_PRECISION); + initial_state.initial_active_witnesses = 10; + initial_state.initial_timestamp = time_point_sec(time_point::now().sec_since_epoch() / + initial_state.initial_parameters.block_interval * + initial_state.initial_parameters.block_interval); + for( int i = 0; i < initial_state.initial_active_witnesses; ++i ) + { + auto name = "init"+fc::to_string(i); + initial_state.initial_accounts.emplace_back(name, + nathan_key.get_public_key(), + nathan_key.get_public_key(), + true); + initial_state.initial_committee_candidates.push_back({name}); + initial_state.initial_witness_candidates.push_back({name, nathan_key.get_public_key()}); + } + + initial_state.initial_accounts.emplace_back("nathan", nathan_key.get_public_key()); + initial_state.initial_balances.push_back({nathan_key.get_public_key(), + GRAPHENE_SYMBOL, + GRAPHENE_MAX_SHARE_SUPPLY}); + return initial_state; + } + class application_impl : public net::node_delegate { public: @@ -105,6 +138,7 @@ namespace detail { wsc->register_api(fc::api(login)); c->set_session_data( wsc ); }); + ilog("Configured websocket rpc to listen on ${ip}", ("ip",_options->at("rpc-endpoint").as())); _websocket_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-endpoint").as()) ); _websocket_server->start_accept(); } FC_CAPTURE_AND_RETHROW() } @@ -115,7 +149,10 @@ namespace detail { if( !_options->count("rpc-tls-endpoint") ) return; if( !_options->count("server-pem") ) + { + wlog( "Please specify a server-pem to use rpc-tls-endpoint" ); return; + } string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as() : ""; _websocket_tls_server = std::make_shared( _options->at("server-pem").as(), password ); @@ -128,6 +165,7 @@ namespace detail { wsc->register_api(fc::api(login)); c->set_session_data( wsc ); }); + ilog("Configured websocket TLS rpc to listen on ${ip}", ("ip",_options->at("rpc-tls-endpoint").as())); _websocket_tls_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-tls-endpoint").as()) ); _websocket_tls_server->start_accept(); } FC_CAPTURE_AND_RETHROW() } @@ -148,25 +186,58 @@ namespace detail { bool clean = !fc::exists(_data_dir / "blockchain/dblock"); fc::create_directories(_data_dir / "blockchain/dblock"); - auto nathan_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); - genesis_allocation initial_allocation = {{graphene::chain::public_key_type(nathan_key.get_public_key()), 1}}; - if( _options->count("genesis-json") ) - initial_allocation = fc::json::from_file(_options->at("genesis-json").as()).as(); - else - dlog("Allocating all stake to ${key}", ("key", utilities::key_to_wif(nathan_key))); + auto initial_state = [&] { + ilog("Initializing database..."); + if( _options->count("genesis-json") ) + return fc::json::from_file(_options->at("genesis-json").as()) + .as(); + else + return create_example_genesis(); + }; if( _options->count("resync-blockchain") ) _chain_db->wipe(_data_dir / "blockchain", true); + if( _options->count("checkpoint") ) + { + auto cps = _options->at("checkpoint").as>(); + flat_map loaded_checkpoints; + loaded_checkpoints.reserve( cps.size() ); + for( auto cp : cps ) + { + auto item = fc::json::from_string(cp).as >(); + loaded_checkpoints[item.first] = item.second; + } + _chain_db->add_checkpoints( loaded_checkpoints ); + } + + if( _options->count("replay-blockchain") ) { ilog("Replaying blockchain on user request."); - _chain_db->reindex(_data_dir/"blockchain", initial_allocation); + _chain_db->reindex(_data_dir/"blockchain", initial_state()); } else if( clean ) - _chain_db->open(_data_dir / "blockchain", initial_allocation); + _chain_db->open(_data_dir / "blockchain", initial_state); else { wlog("Detected unclean shutdown. Replaying blockchain..."); - _chain_db->reindex(_data_dir / "blockchain", initial_allocation); + _chain_db->reindex(_data_dir / "blockchain", initial_state()); + } + + if( _options->count("apiaccess") ) + _apiaccess = fc::json::from_file( _options->at("apiaccess").as() ) + .as(); + else + { + // TODO: Remove this generous default access policy + // when the UI logs in properly + _apiaccess = api_access(); + api_access_info wild_access; + wild_access.password_hash_b64 = "*"; + wild_access.password_salt_b64 = "*"; + wild_access.allowed_apis.push_back( "database_api" ); + wild_access.allowed_apis.push_back( "network_broadcast_api" ); + wild_access.allowed_apis.push_back( "history_api" ); + _apiaccess.permission_map["*"] = wild_access; } reset_p2p_node(_data_dir); @@ -174,20 +245,33 @@ namespace detail { reset_websocket_tls_server(); } FC_CAPTURE_AND_RETHROW() } + optional< api_access_info > get_api_access_info(const string& username)const + { + optional< api_access_info > result; + auto it = _apiaccess.permission_map.find(username); + if( it == _apiaccess.permission_map.end() ) + { + it = _apiaccess.permission_map.find("*"); + if( it == _apiaccess.permission_map.end() ) + return result; + } + return it->second; + } + /** * If delegate has the item, the network has no need to fetch it. */ - virtual bool has_item( const net::item_id& id ) override - { try { - if( id.item_type == graphene::net::block_message_type ) - { - return _chain_db->is_known_block( id.item_hash ); - } - else - { - return _chain_db->is_known_transaction( id.item_hash ); - } - } FC_CAPTURE_AND_RETHROW( (id) ) } + virtual bool has_item(const net::item_id& id) override + { + try + { + if( id.item_type == graphene::net::block_message_type ) + return _chain_db->is_known_block(id.item_hash); + else + return _chain_db->is_known_transaction(id.item_hash); + } + FC_CAPTURE_AND_RETHROW( (id) ) + } /** * @brief allows the application to validate an item prior to broadcasting to peers. @@ -197,18 +281,24 @@ namespace detail { * * @throws exception if error validating the item, otherwise the item is safe to broadcast on. */ - virtual bool handle_block( const graphene::net::block_message& blk_msg, bool sync_mode ) override + virtual bool handle_block(const graphene::net::block_message& blk_msg, bool sync_mode) override { try { ilog("Got block #${n} from network", ("n", blk_msg.block.block_num())); try { - return _chain_db->push_block( blk_msg.block, _is_block_producer? database::skip_nothing : database::skip_transaction_signatures ); + return _chain_db->push_block(blk_msg.block, _is_block_producer? database::skip_nothing : database::skip_transaction_signatures); } catch( const fc::exception& e ) { elog("Error when pushing block:\n${e}", ("e", e.to_detail_string())); throw; } + + if( !_is_finished_syncing && !sync_mode ) + { + _is_finished_syncing = true; + _self->syncing_finished(); + } } FC_CAPTURE_AND_RETHROW( (blk_msg)(sync_mode) ) } - virtual bool handle_transaction( const graphene::net::trx_message& trx_msg, bool sync_mode ) override + virtual bool handle_transaction(const graphene::net::trx_message& trx_msg, bool sync_mode) override { try { ilog("Got transaction from network"); _chain_db->push_transaction( trx_msg.trx ); @@ -227,7 +317,7 @@ namespace detail { virtual std::vector get_item_ids(uint32_t item_type, const std::vector& blockchain_synopsis, uint32_t& remaining_item_count, - uint32_t limit ) override + uint32_t limit) override { try { FC_ASSERT( item_type == graphene::net::block_message_type ); vector result; @@ -240,7 +330,7 @@ namespace detail { auto itr = blockchain_synopsis.rbegin(); while( itr != blockchain_synopsis.rend() ) { - if( _chain_db->is_known_block( *itr ) || *itr == block_id_type() ) + if( _chain_db->is_known_block(*itr) || *itr == block_id_type() ) { last_known_block_id = *itr; break; @@ -257,25 +347,24 @@ namespace detail { if( block_header::num_from_id(result.back()) < _chain_db->head_block_num() ) remaining_item_count = _chain_db->head_block_num() - block_header::num_from_id(result.back()); - idump((blockchain_synopsis)(limit)(result)(remaining_item_count)); return result; } FC_CAPTURE_AND_RETHROW( (blockchain_synopsis)(remaining_item_count)(limit) ) } /** * Given the hash of the requested data, fetch the body. */ - virtual message get_item( const item_id& id ) override + virtual message get_item(const item_id& id) override { try { ilog("Request for item ${id}", ("id", id)); if( id.item_type == graphene::net::block_message_type ) { - auto opt_block = _chain_db->fetch_block_by_id( id.item_hash ); + auto opt_block = _chain_db->fetch_block_by_id(id.item_hash); if( !opt_block ) elog("Couldn't find block ${id} -- corresponding ID in our chain is ${id2}", ("id", id.item_hash)("id2", _chain_db->get_block_id_for_num(block_header::num_from_id(id.item_hash)))); FC_ASSERT( opt_block.valid() ); ilog("Serving up block #${num}", ("num", opt_block->block_num())); - return block_message( std::move(*opt_block) ); + return block_message(std::move(*opt_block)); } return trx_message( _chain_db->get_recent_transaction( id.item_hash ) ); } FC_CAPTURE_AND_RETHROW( (id) ) } @@ -300,18 +389,18 @@ namespace detail { * &c. * the last item in the list will be the hash of the most recent block on our preferred chain */ - virtual std::vector get_blockchain_synopsis( uint32_t item_type, - const graphene::net::item_hash_t& reference_point, - uint32_t number_of_blocks_after_reference_point ) override + virtual std::vector get_blockchain_synopsis(uint32_t item_type, + const graphene::net::item_hash_t& reference_point, + uint32_t number_of_blocks_after_reference_point) override { try { std::vector result; result.reserve(30); auto head_block_num = _chain_db->head_block_num(); - result.push_back( _chain_db->head_block_id() ); + result.push_back(_chain_db->head_block_id()); auto current = 1; while( current < head_block_num ) { - result.push_back( _chain_db->get_block_id_for_num( head_block_num - current ) ); + result.push_back(_chain_db->get_block_id_for_num(head_block_num - current)); current = current*2; } std::reverse( result.begin(), result.end() ); @@ -326,7 +415,7 @@ namespace detail { * @param item_count the number of items known to the node that haven't been sent to handle_item() yet. * After `item_count` more calls to handle_item(), the node will be in sync */ - virtual void sync_status( uint32_t item_type, uint32_t item_count ) override + virtual void sync_status(uint32_t item_type, uint32_t item_count) override { // any status reports to GUI go here } @@ -334,7 +423,7 @@ namespace detail { /** * Call any time the number of connected peers changes. */ - virtual void connection_count_changed( uint32_t c ) override + virtual void connection_count_changed(uint32_t c) override { // any status reports to GUI go here } @@ -380,6 +469,7 @@ namespace detail { fc::path _data_dir; const bpo::variables_map* _options = nullptr; + api_access _apiaccess; std::shared_ptr _chain_db; std::shared_ptr _p2p_network; @@ -387,6 +477,8 @@ namespace detail { std::shared_ptr _websocket_tls_server; std::map> _plugins; + + bool _is_finished_syncing = false; }; } @@ -399,13 +491,11 @@ application::~application() { if( my->_p2p_network ) { - ilog("Closing p2p node"); my->_p2p_network->close(); my->_p2p_network.reset(); } if( my->_chain_db ) { - ilog("Closing chain database"); my->_chain_db->close(); } } @@ -416,14 +506,20 @@ void application::set_program_options(boost::program_options::options_descriptio configuration_file_options.add_options() ("p2p-endpoint", bpo::value(), "Endpoint for P2P node to listen on") ("seed-node,s", bpo::value>()->composing(), "P2P nodes to connect to on startup (may specify multiple times)") + ("checkpoint,c", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") ("rpc-endpoint", bpo::value()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on") ("rpc-tls-endpoint", bpo::value()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on") ("server-pem,p", bpo::value()->implicit_value("server.pem"), "The TLS certificate file for this server") ("server-pem-password,P", bpo::value()->implicit_value(""), "Password for this certificate") ("genesis-json", bpo::value(), "File to read Genesis State from") + ("api-access", bpo::value(), "JSON file specifying API permissions") ; command_line_options.add(configuration_file_options); command_line_options.add_options() + ("create-genesis-json", bpo::value(), + "Path to create a Genesis State at. If a well-formed JSON file exists at the path, it will be parsed and any " + "missing fields in a Genesis State will be added, and any unknown fields will be removed. If no file or an " + "invalid file is found, it will be replaced with an example Genesis State.") ("replay-blockchain", "Rebuild object graph by replaying all blocks") ("resync-blockchain", "Delete all blocks and re-sync with network from scratch") ; @@ -435,6 +531,31 @@ void application::initialize(const fc::path& data_dir, const boost::program_opti { my->_data_dir = data_dir; my->_options = &options; + + if( options.count("create-genesis-json") ) + { + fc::path genesis_out = options.at("create-genesis-json").as(); + genesis_state_type genesis_state = detail::create_example_genesis(); + if( fc::exists(genesis_out) ) + { + try { + genesis_state = fc::json::from_file(genesis_out).as(); + } catch(const fc::exception& e) { + std::cerr << "Unable to parse existing genesis file:\n" << e.to_string() + << "\nWould you like to replace it? [y/N] "; + char response = std::cin.get(); + if( toupper(response) != 'Y' ) + return; + } + + std::cerr << "Updating genesis state in file " << genesis_out.generic_string() << "\n"; + } else { + std::cerr << "Creating example genesis state in file " << genesis_out.generic_string() << "\n"; + } + fc::json::save_to_file(genesis_state, genesis_out); + + std::exit(EXIT_SUCCESS); + } } void application::startup() @@ -462,6 +583,16 @@ void application::set_block_production(bool producing_blocks) my->_is_block_producer = producing_blocks; } +optional< api_access_info > application::get_api_access_info( const string& username )const +{ + return my->get_api_access_info( username ); +} + +bool application::is_finished_syncing() const +{ + return my->_is_finished_syncing; +} + void graphene::app::application::add_plugin(const string& name, std::shared_ptr p) { my->_plugins[name] = p; diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index e34f290c..b1e96a34 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -16,19 +16,25 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include +#include #include #include #include #include -#include -#include -#include +#include +#include +#include +#include +#include #include + +#include + #include namespace graphene { namespace app { using namespace graphene::chain; + using namespace graphene::market_history; class application; @@ -37,7 +43,7 @@ namespace graphene { namespace app { * * This API exposes accessors on the database which query state tracked by a blockchain validating node. This API is * read-only; all modifications to the database must be performed via transactions. Transactions are broadcast via - * the @ref network_api. + * the @ref network_broadcast_api. */ class database_api { @@ -64,6 +70,12 @@ namespace graphene { namespace app { * @return the referenced block, or null if no matching block was found */ optional get_block(uint32_t block_num)const; + + /** + * @brief used to fetch an individual transaction. + */ + processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const; + /** * @brief Retrieve the current @ref global_property_object */ @@ -72,14 +84,6 @@ namespace graphene { namespace app { * @brief Retrieve the current @ref dynamic_global_property_object */ dynamic_global_property_object get_dynamic_global_properties()const; - /** - * @brief Get a list of keys by ID - * @param key_ids IDs of the keys to retrieve - * @return The keys corresponding to the provided IDs - * - * This function has semantics identical to @ref get_objects - */ - vector> get_keys(const vector& key_ids)const; /** * @brief Get a list of accounts by ID * @param account_ids IDs of the accounts to retrieve @@ -142,13 +146,6 @@ namespace graphene { namespace app { * @return The limit orders, ordered from least price to greatest */ vector get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const; - /** - * @brief Get short orders in a given asset - * @param a ID of asset being sold - * @param limit Maximum number of orders to retrieve - * @return The short orders, ordered from least price to greatest - */ - vector get_short_orders(asset_id_type a, uint32_t limit)const; /** * @brief Get call orders in a given asset * @param a ID of asset being called @@ -172,6 +169,58 @@ namespace graphene { namespace app { */ vector list_assets(const string& lower_bound_symbol, uint32_t limit)const; + /** + * @brief Get the committee_member owned by a given account + * @param account The ID of the account whose committee_member should be retrieved + * @return The committee_member object, or null if the account does not have a committee_member + */ + fc::optional get_committee_member_by_account(account_id_type account)const; + /** + * @brief Get the witness owned by a given account + * @param account The ID of the account whose witness should be retrieved + * @return The witness object, or null if the account does not have a witness + */ + fc::optional get_witness_by_account(account_id_type account)const; + + /** + * @brief Get the total number of witnesses registered with the blockchain + */ + uint64_t get_witness_count()const; + + /** + * @brief Get names and IDs for registered witnesses + * @param lower_bound_name Lower bound of the first name to return + * @param limit Maximum number of results to return -- must not exceed 1000 + * @return Map of witness names to corresponding IDs + */ + map lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const; + + /** + * @brief Get names and IDs for registered committee_members + * @param lower_bound_name Lower bound of the first name to return + * @param limit Maximum number of results to return -- must not exceed 1000 + * @return Map of committee_member names to corresponding IDs + */ + map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + + /** + * @brief Get a list of witnesses by ID + * @param witness_ids IDs of the witnesses to retrieve + * @return The witnesses corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_witnesses(const vector& witness_ids)const; + + /** + * @brief Get a list of committee_members by ID + * @param committee_member_ids IDs of the committee_members to retrieve + * @return The committee_members corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_committee_members(const vector& committee_member_ids)const; + /** * @group Push Notification Methods * These methods may be used to get push notifications whenever an object or market is changed @@ -217,6 +266,26 @@ namespace graphene { namespace app { /// @brief Get a hexdump of the serialized binary form of a transaction std::string get_transaction_hex(const signed_transaction& trx)const; + + /** + * @return the set of proposed transactions relevant to the specified account id. + */ + vector get_proposed_transactions( account_id_type id )const; + + /** + * @return all accounts that referr to the key or account id in their owner or active authorities. + */ + vector get_account_references( account_id_type account_id )const; + vector get_key_references( public_key_type account_id )const; + + /** + * @return all open margin positions for a given account id. + */ + vector get_margin_positions( const account_id_type& id )const; + + /** @return all unclaimed balance objects for a set of addresses */ + vector get_balance_objects( const vector
& addrs )const; + private: /** called every time a block is applied to report the objects that were changed */ void on_objects_changed(const vector& ids); @@ -237,35 +306,46 @@ namespace graphene { namespace app { */ class history_api { - public: - history_api(application& app):_app(app){} + public: + history_api(application& app):_app(app){} - /** - * @brief Get operations relevant to the specificed account - * @param account The account whose history should be queried - * @param stop ID of the earliest operation to retrieve - * @param limit Maximum number of operations to retrieve (must not exceed 100) - * @param start ID of the most recent operation to retrieve - * @return A list of operations performed by account, ordered from most recent to oldest. - */ - vector get_account_history(account_id_type account, - operation_history_id_type stop = operation_history_id_type(), - int limit = 100, - operation_history_id_type start = operation_history_id_type())const; + /** + * @brief Get operations relevant to the specificed account + * @param account The account whose history should be queried + * @param stop ID of the earliest operation to retrieve + * @param limit Maximum number of operations to retrieve (must not exceed 100) + * @param start ID of the most recent operation to retrieve + * @return A list of operations performed by account, ordered from most recent to oldest. + */ + vector get_account_history(account_id_type account, + operation_history_id_type stop = operation_history_id_type(), + unsigned limit = 100, + operation_history_id_type start = operation_history_id_type())const; - private: - application& _app; + vector get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds, + fc::time_point_sec start, fc::time_point_sec end )const; + flat_set get_market_history_buckets()const; + private: + application& _app; }; /** - * @brief The network_api class implements the RPC API for the network - * - * This API has methods to query the network status, connect to new peers, and send transactions. + * @brief The network_broadcast_api class allows broadcasting of transactions. */ - class network_api + class network_broadcast_api { public: - network_api(application& a):_app(a){} + network_broadcast_api(application& a); + + struct transaction_confirmation + { + transaction_id_type id; + uint32_t block_num; + uint32_t trx_num; + processed_transaction trx; + }; + + typedef std::function confirmation_callback; /** * @brief Broadcast a transaction to the network @@ -275,18 +355,55 @@ namespace graphene { namespace app { * apply locally, an error will be thrown and the transaction will not be broadcast. */ void broadcast_transaction(const signed_transaction& trx); + + /** this version of broadcast transaction registers a callback method that will be called when the transaction is + * included into a block. The callback method includes the transaction id, block number, and transaction number in the + * block. + */ + void broadcast_transaction_with_callback( confirmation_callback cb, const signed_transaction& trx); + + /** + * @brief Not reflected, thus not accessible to API clients. + * + * This function is registered to receive the applied_block + * signal from the chain database when a block is received. + * It then dispatches callbacks to clients who have requested + * to be notified when a particular txid is included in a block. + */ + void on_applied_block( const signed_block& b ); + private: + boost::signals2::scoped_connection _applied_block_connection; + map _callbacks; + application& _app; + }; + + /** + * @brief The network_node_api class allows maintenance of p2p connections. + */ + class network_node_api + { + public: + network_node_api(application& a); + /** * @brief add_node Connect to a new peer * @param ep The IP/Port of the peer to connect to */ void add_node(const fc::ip::endpoint& ep); + /** * @brief Get status of all current connections to peers - */ + * @brief Not reflected, thus not accessible to API clients. + * + * This function is registered to receive the applied_block + * signal from the chain database when a block is received. + * It then dispatches callbacks to clients who have requested + * to be notified when a particular txid is included in a block. + */ std::vector get_connected_peers() const; private: - application& _app; + application& _app; }; /** @@ -310,29 +427,38 @@ namespace graphene { namespace app { * has sucessfully authenticated. */ bool login(const string& user, const string& password); - /// @brief Retrieve the network API - fc::api network()const; + /// @brief Retrieve the network broadcast API + fc::api network_broadcast()const; /// @brief Retrieve the database API fc::api database()const; /// @brief Retrieve the history API fc::api history()const; + /// @brief Retrieve the network node API + fc::api network_node()const; private: + /// @brief Called to enable an API, not reflected. + void enable_api( const string& api_name ); + application& _app; optional< fc::api > _database_api; - optional< fc::api > _network_api; + optional< fc::api > _network_broadcast_api; + optional< fc::api > _network_node_api; optional< fc::api > _history_api; }; }} // graphene::app +FC_REFLECT( graphene::app::network_broadcast_api::transaction_confirmation, + (id)(block_num)(trx_num)(trx) ) + FC_API(graphene::app::database_api, (get_objects) (get_block_header) (get_block) + (get_transaction) (get_global_properties) (get_dynamic_global_properties) - (get_keys) (get_accounts) (get_assets) (lookup_account_names) @@ -342,22 +468,45 @@ FC_API(graphene::app::database_api, (get_named_account_balances) (lookup_asset_symbols) (get_limit_orders) - (get_short_orders) (get_call_orders) (get_settle_orders) (list_assets) + (get_committee_member_by_account) + (get_witnesses) + (get_committee_members) + (get_witness_by_account) + (get_witness_count) + (lookup_witness_accounts) + (lookup_committee_member_accounts) (subscribe_to_objects) (unsubscribe_from_objects) (subscribe_to_market) (unsubscribe_from_market) (cancel_all_subscriptions) (get_transaction_hex) + (get_proposed_transactions) + (get_account_references) + (get_key_references) + (get_margin_positions) + (get_balance_objects) + ) +FC_API(graphene::app::history_api, + (get_account_history) + (get_market_history) + (get_market_history_buckets) + ) +FC_API(graphene::app::network_broadcast_api, + (broadcast_transaction) + (broadcast_transaction_with_callback) + ) +FC_API(graphene::app::network_node_api, + (add_node) + (get_connected_peers) ) -FC_API(graphene::app::history_api, (get_account_history)) -FC_API(graphene::app::network_api, (broadcast_transaction)(add_node)(get_connected_peers)) FC_API(graphene::app::login_api, (login) - (network) + (network_broadcast) (database) (history) + (network_node) ) diff --git a/libraries/chain/key_object.cpp b/libraries/app/include/graphene/app/api_access.hpp similarity index 70% rename from libraries/chain/key_object.cpp rename to libraries/app/include/graphene/app/api_access.hpp index a0a79fe8..b6f4e2f3 100644 --- a/libraries/chain/key_object.cpp +++ b/libraries/app/include/graphene/app/api_access.hpp @@ -15,21 +15,36 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include +#pragma once -namespace graphene { namespace chain { - address key_object::key_address()const - { - typedef static_variant address_or_key; +#include - switch( key_data.which() ) - { - case address_or_key::tag
::value: - return key_data.get
(); - case address_or_key::tag::value: - default: - return key_data.get(); - } - } -} } +#include +#include +#include + +namespace graphene { namespace app { + +struct api_access_info +{ + std::string password_hash_b64; + std::string password_salt_b64; + std::vector< std::string > allowed_apis; +}; + +struct api_access +{ + std::map< std::string, api_access_info > permission_map; +}; + +} } // graphene::app + +FC_REFLECT( graphene::app::api_access_info, + (password_hash_b64) + (password_salt_b64) + (allowed_apis) + ) + +FC_REFLECT( graphene::app::api_access, + (permission_map) + ) diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index 6f29405a..5c76d021 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include #include #include @@ -24,7 +25,6 @@ namespace graphene { namespace app { namespace detail { class application_impl; } - namespace bpo = boost::program_options; using std::string; class abstract_plugin; @@ -35,10 +35,10 @@ namespace graphene { namespace app { application(); ~application(); - void set_program_options( bpo::options_description& command_line_options, - bpo::options_description& configuration_file_options )const; - void initialize(const fc::path& data_dir, const bpo::variables_map&options); - void initialize_plugins( const bpo::variables_map& options ); + void set_program_options( boost::program_options::options_description& command_line_options, + boost::program_options::options_description& configuration_file_options )const; + void initialize(const fc::path& data_dir, const boost::program_options::variables_map&options); + void initialize_plugins( const boost::program_options::variables_map& options ); void startup(); void shutdown(); void startup_plugins(); @@ -50,7 +50,7 @@ namespace graphene { namespace app { auto plug = std::make_shared(); plug->plugin_set_app(this); - bpo::options_description plugin_cli_options("Options for plugin " + plug->plugin_name()), plugin_cfg_options; + boost::program_options::options_description plugin_cli_options("Options for plugin " + plug->plugin_name()), plugin_cfg_options; plug->plugin_set_program_options(plugin_cli_options, plugin_cfg_options); if( !plugin_cli_options.options().empty() ) _cli_options.add(plugin_cli_options); @@ -75,13 +75,18 @@ namespace graphene { namespace app { std::shared_ptr chain_database()const; void set_block_production(bool producing_blocks); + fc::optional< api_access_info > get_api_access_info( const string& username )const; + + bool is_finished_syncing()const; + /// Emitted when syncing finishes (is_finished_syncing will return true) + boost::signals2::signal syncing_finished; private: void add_plugin( const string& name, std::shared_ptr p ); std::shared_ptr my; - bpo::options_description _cli_options; - bpo::options_description _cfg_options; + boost::program_options::options_description _cli_options; + boost::program_options::options_description _cfg_options; }; } } diff --git a/libraries/app/include/graphene/app/plugin.hpp b/libraries/app/include/graphene/app/plugin.hpp index 253492ad..53fb70b3 100644 --- a/libraries/app/include/graphene/app/plugin.hpp +++ b/libraries/app/include/graphene/app/plugin.hpp @@ -20,9 +20,9 @@ #include #include +#include namespace graphene { namespace app { -namespace bpo = boost::program_options; class abstract_plugin { @@ -42,7 +42,7 @@ class abstract_plugin * * @param options The options passed to the application, via configuration files or command line */ - virtual void plugin_initialize( const bpo::variables_map& options ) = 0; + virtual void plugin_initialize( const boost::program_options::variables_map& options ) = 0; /** * @brief Begin normal runtime operations @@ -78,8 +78,8 @@ class abstract_plugin * may simply provide an empty implementation of this method. */ virtual void plugin_set_program_options( - bpo::options_description& command_line_options, - bpo::options_description& config_file_options + boost::program_options::options_description& command_line_options, + boost::program_options::options_description& config_file_options ) = 0; }; @@ -94,18 +94,18 @@ class plugin : public abstract_plugin virtual ~plugin() override; virtual std::string plugin_name()const override; - virtual void plugin_initialize( const bpo::variables_map& options ) override; + virtual void plugin_initialize( const boost::program_options::variables_map& options ) override; virtual void plugin_startup() override; virtual void plugin_shutdown() override; virtual void plugin_set_app( application* app ) override; virtual void plugin_set_program_options( - bpo::options_description& command_line_options, - bpo::options_description& config_file_options + boost::program_options::options_description& command_line_options, + boost::program_options::options_description& config_file_options ) override; - protected: chain::database& database() { return *app().chain_database(); } application& app()const { assert(_app); return *_app; } + protected: net::node& p2p_node() { return *app().p2p_node(); } private: diff --git a/libraries/app/plugin.cpp b/libraries/app/plugin.cpp index cfff8382..4b665d6e 100644 --- a/libraries/app/plugin.cpp +++ b/libraries/app/plugin.cpp @@ -17,6 +17,7 @@ */ #include +#include namespace graphene { namespace app { @@ -36,7 +37,7 @@ std::string plugin::plugin_name()const return ""; } -void plugin::plugin_initialize( const bpo::variables_map& options ) +void plugin::plugin_initialize( const boost::program_options::variables_map& options ) { return; } @@ -58,8 +59,8 @@ void plugin::plugin_set_app( application* app ) } void plugin::plugin_set_program_options( - bpo::options_description& command_line_options, - bpo::options_description& config_file_options + boost::program_options::options_description& command_line_options, + boost::program_options::options_description& config_file_options ) { return; diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 17635897..698b5089 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -2,57 +2,71 @@ file(GLOB HEADERS "include/graphene/chain/*.hpp") ## SORT .cpp by most likely to change / break compile add_library( graphene_chain - types.cpp - address.cpp - asset.cpp + protocol/types.cpp + protocol/address.cpp + protocol/asset.cpp + protocol/assert.cpp + protocol/account.cpp + protocol/transfer.cpp + protocol/committee_member.cpp + protocol/witness.cpp + protocol/market.cpp + protocol/proposal.cpp + protocol/withdraw_permission.cpp + protocol/asset_ops.cpp + protocol/memo.cpp + protocol/worker.cpp + protocol/custom.cpp + protocol/operations.cpp + protocol/transaction.cpp + protocol/block.cpp + protocol/fee_schedule.cpp - operations.cpp + pts_address.cpp evaluator.cpp - global_parameters_evaluator.cpp + balance_evaluator.cpp account_evaluator.cpp + assert_evaluator.cpp witness_evaluator.cpp - delegate_evaluator.cpp + committee_member_evaluator.cpp asset_evaluator.cpp transfer_evaluator.cpp proposal_evaluator.cpp - short_order_evaluator.cpp - limit_order_evaluator.cpp + market_evaluator.cpp vesting_balance_evaluator.cpp withdraw_permission_evaluator.cpp worker_evaluator.cpp - key_object.cpp account_object.cpp asset_object.cpp proposal_object.cpp vesting_balance_object.cpp - worker_object.cpp - - transaction.cpp - block.cpp transaction_evaluation_state.cpp fork_database.cpp + block_database.cpp - db_balance.cpp - db_block.cpp - db_debug.cpp - db_getter.cpp - db_init.cpp - db_maint.cpp - db_management.cpp - db_market.cpp - db_update.cpp - db_witness_schedule.cpp + database.cpp + +# db_balance.cpp +# db_block.cpp +# db_debug.cpp +# db_getter.cpp +# db_init.cpp +# db_maint.cpp +# db_management.cpp +# db_market.cpp +# db_update.cpp +# db_witness_schedule.cpp ${HEADERS} ) -target_link_libraries( graphene_chain fc graphene_db leveldb ) +target_link_libraries( graphene_chain fc graphene_db ) target_include_directories( graphene_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) if(MSVC) - set_source_files_properties( db_init.cpp db_block.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) + set_source_files_properties( db_init.cpp db_block.cpp database.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 0e9aed33..7a6ac1fd 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -17,7 +17,6 @@ */ #include #include -#include #include namespace graphene { namespace chain { @@ -25,38 +24,31 @@ namespace graphene { namespace chain { void_result account_create_evaluator::do_evaluate( const account_create_operation& op ) { try { database& d = db(); - FC_ASSERT( d.find_object(op.voting_account) ); - FC_ASSERT( is_relative(op.memo_key) || d.find_object(op.memo_key) ); + FC_ASSERT( d.find_object(op.options.voting_account) ); FC_ASSERT( fee_paying_account->is_lifetime_member() ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()) ); const auto& global_props = d.get_global_properties(); - uint32_t max_vote_id = global_props.next_available_vote_id; const auto& chain_params = global_props.parameters; - FC_ASSERT( op.num_witness <= chain_params.maximum_witness_count ); - FC_ASSERT( op.num_committee <= chain_params.maximum_committee_count ); - FC_ASSERT( op.owner.auths.size() <= chain_params.maximum_authority_membership ); - FC_ASSERT( op.active.auths.size() <= chain_params.maximum_authority_membership ); - for( auto id : op.owner.auths ) - { - FC_ASSERT( is_relative(id.first) || d.find_object(id.first) ); - } - for( auto id : op.active.auths ) - { - FC_ASSERT( is_relative(id.first) || d.find_object(id.first) ); - } + + verify_authority_accounts( op.owner ); + verify_authority_accounts( op.active ); + + uint32_t max_vote_id = global_props.next_available_vote_id; + FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count ); + FC_ASSERT( op.options.num_committee <= chain_params.maximum_committee_count ); safe counts[vote_id_type::VOTE_TYPE_COUNT]; - for( auto id : op.vote ) + for( auto id : op.options.votes ) { FC_ASSERT( id < max_vote_id ); counts[id.type()]++; } - FC_ASSERT(counts[vote_id_type::witness] <= op.num_witness, + FC_ASSERT(counts[vote_id_type::witness] <= op.options.num_witness, "", - ("count", counts[vote_id_type::witness])("num", op.num_witness)); - FC_ASSERT(counts[vote_id_type::committee] <= op.num_committee, + ("count", counts[vote_id_type::witness])("num", op.options.num_witness)); + FC_ASSERT(counts[vote_id_type::committee] <= op.options.num_committee, "", - ("count", counts[vote_id_type::committee])("num", op.num_committee)); + ("count", counts[vote_id_type::committee])("num", op.options.num_committee)); auto& acnt_indx = d.get_index_type(); if( op.name.size() ) @@ -65,37 +57,11 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( current_account_itr == acnt_indx.indices().get().end() ); } - // TODO: this check can be removed after GRAPHENE_LEGACY_NAME_IMPORT_PERIOD - // legacy account check - if( d.get_dynamic_global_properties().head_block_number < GRAPHENE_LEGACY_NAME_IMPORT_PERIOD ) - { - auto legacy_account_itr = acnt_indx.indices().get().find( "bts-"+op.name ); - if( legacy_account_itr != acnt_indx.indices().get().end() ) - { - FC_ASSERT( fee_paying_account->id == legacy_account_itr->id ); - } - } - - // verify child account authority - auto pos = op.name.find( '/' ); - if( pos != string::npos ) - { - // TODO: lookup account by op.owner.auths[0] and verify the name - // this should be a constant time lookup rather than log(N) - auto parent_account_itr = acnt_indx.indices().get().find( op.name.substr(0,pos) ); - FC_ASSERT( parent_account_itr != acnt_indx.indices().get().end() ); - FC_ASSERT( verify_authority( *parent_account_itr, authority::owner ) ); - FC_ASSERT( op.owner.auths.find( parent_account_itr->id ) != op.owner.auths.end() ); - } - return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } object_id_type account_create_evaluator::do_apply( const account_create_operation& o ) { try { - auto owner = resolve_relative_ids( o.owner ); - auto active = resolve_relative_ids( o.active ); - const auto& stats_obj = db().create( [&]( account_statistics_object& ){ }); @@ -110,72 +76,63 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.referrer_rewards_percentage = o.referrer_percent; obj.name = o.name; - obj.owner = owner; - obj.active = active; + obj.owner = o.owner; + obj.active = o.active; obj.statistics = stats_obj.id; - obj.memo_key = get_relative_id(o.memo_key); - obj.voting_account = o.voting_account; - obj.votes = o.vote; - obj.num_witness = o.num_witness; - obj.num_committee = o.num_committee; + obj.options = o.options; }); + const auto& dynamic_properties = db().get_dynamic_global_properties(); + db().modify(dynamic_properties, [](dynamic_global_property_object& p) { + ++p.accounts_registered_this_interval; + }); + + /** TODO: update fee scaling for account creation... + if( dynamic_properties.accounts_registered_this_interval % + global_properties.parameters.accounts_per_fee_scale == 0 ) + db().modify(global_properties, [&dynamic_properties](global_property_object& p) { + p.parameters.current_fees.account_create_fee <<= p.parameters.account_fee_scale_bitshifts; + }); + */ + return new_acnt_object.id; } FC_CAPTURE_AND_RETHROW((o)) } void_result account_update_evaluator::do_evaluate( const account_update_operation& o ) -{ +{ try { database& d = db(); - FC_ASSERT( !o.memo_key || is_relative(*o.memo_key) || db().find_object(*o.memo_key) ); - const auto& chain_params = db().get_global_properties().parameters; - FC_ASSERT( o.num_witness <= chain_params.maximum_witness_count ); - FC_ASSERT( o.num_committee <= chain_params.maximum_committee_count ); - if( o.owner ) - { - FC_ASSERT( o.owner->auths.size() <= chain_params.maximum_authority_membership ); - for( auto id : o.owner->auths ) - { - FC_ASSERT( is_relative(id.first) || db().find(id.first) ); - } - } - if( o.active ) - { - FC_ASSERT( o.active->auths.size() <= chain_params.maximum_authority_membership ); - for( auto id : o.active->auths ) - { - FC_ASSERT( is_relative(id.first) || db().find(id.first) ); - } - } + + if( o.owner ) verify_authority_accounts( *o.owner ); + if( o.active ) verify_authority_accounts( *o.active ); acnt = &o.account(d); - if( o.vote ) + if( o.new_options ) { + FC_ASSERT( o.new_options->num_witness <= chain_params.maximum_witness_count ); + FC_ASSERT( o.new_options->num_committee <= chain_params.maximum_committee_count ); uint32_t max_vote_id = d.get_global_properties().next_available_vote_id; - for( auto id : *o.vote ) + for( auto id : o.new_options->votes ) { FC_ASSERT( id < max_vote_id ); } } return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } + void_result account_update_evaluator::do_apply( const account_update_operation& o ) -{ - db().modify( *acnt, [&]( account_object& a ){ - if( o.owner ) a.owner = *o.owner; - if( o.active ) a.active = *o.active; - if( o.voting_account ) a.voting_account = *o.voting_account; - if( o.memo_key ) a.memo_key = *o.memo_key; - if( o.vote ) a.votes = *o.vote; - a.num_witness = o.num_witness; - a.num_committee = o.num_committee; - }); +{ try { + db().modify( *acnt, [&](account_object& a){ + if( o.owner ) a.owner = *o.owner; + if( o.active ) a.active = *o.active; + if( o.new_options ) a.options = *o.new_options; + }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result account_whitelist_evaluator::do_evaluate(const account_whitelist_operation& o) { try { @@ -189,7 +146,7 @@ void_result account_whitelist_evaluator::do_evaluate(const account_whitelist_ope } FC_CAPTURE_AND_RETHROW( (o) ) } void_result account_whitelist_evaluator::do_apply(const account_whitelist_operation& o) -{ +{ try { database& d = db(); d.modify(*listed_account, [&o](account_object& a) { @@ -204,26 +161,28 @@ void_result account_whitelist_evaluator::do_apply(const account_whitelist_operat }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result account_upgrade_evaluator::do_evaluate(const account_upgrade_evaluator::operation_type& o) -{ +{ try { database& d = db(); account = &d.get(o.account_to_upgrade); FC_ASSERT(!account->is_lifetime_member()); return {}; -} +//} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_RETHROW_EXCEPTIONS( error, "Unable to upgrade account '${a}'", ("a",o.account_to_upgrade(db()).name) ) } void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator::operation_type& o) -{ +{ try { database& d = db(); d.modify(*account, [&](account_object& a) { if( o.upgrade_to_lifetime_member ) { // Upgrade to lifetime member. I don't care what the account was before. + a.statistics(d).process_fees(a, d); a.membership_expiration_date = time_point_sec::maximum(); a.referrer = a.registrar = a.lifetime_referrer = a.get_id(); a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage; @@ -234,12 +193,13 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: a.membership_expiration_date += fc::days(365); } else { // Upgrade from basic account. + a.statistics(d).process_fees(a, d); assert(a.is_basic_account(d.head_block_time())); a.membership_expiration_date = d.head_block_time() + fc::days(365); } }); return {}; -} +} FC_RETHROW_EXCEPTIONS( error, "Unable to upgrade account '${a}'", ("a",o.account_to_upgrade(db()).name) ) } } } // graphene::chain diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 59ba30aa..79c4060a 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -17,10 +17,24 @@ */ #include #include +#include #include namespace graphene { namespace chain { +share_type cut_fee(share_type a, uint16_t p) +{ + if( a == 0 || p == 0 ) + return 0; + if( p == GRAPHENE_100_PERCENT ) + return a; + + fc::uint128 r(a.value); + r *= p; + r /= GRAPHENE_100_PERCENT; + return r.to_uint64(); +} + bool account_object::is_authorized_asset(const asset_object& asset_obj) const { for( const auto id : blacklisting_accounts ) if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) return false; @@ -56,4 +70,204 @@ uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_ return bulk_discount_percent; } +void account_statistics_object::process_fees(const account_object& a, database& d) const +{ + if( pending_fees > 0 || pending_vested_fees > 0 ) + { + const auto& props = d.get_global_properties(); + + auto pay_out_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting) + { + // Check the referrer -- if he's no longer a member, pay to the lifetime referrer instead. + // No need to check the registrar; registrars are required to be lifetime members. + if( account.referrer(d).is_basic_account(d.head_block_time()) ) + d.modify(account, [](account_object& a) { + a.referrer = a.lifetime_referrer; + }); + + share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage); + assert( network_cut <= core_fee_total ); + +#ifndef NDEBUG + share_type reserveed = cut_fee(network_cut, props.parameters.reserve_percent_of_fee); + share_type accumulated = network_cut - reserveed; + assert( accumulated + reserveed == network_cut ); +#endif + share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage); + share_type referral = core_fee_total - network_cut - lifetime_cut; + + d.modify(asset_dynamic_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { + d.accumulated_fees += network_cut; + }); + + // Potential optimization: Skip some of this math and object lookups by special casing on the account type. + // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to + // it directly. + share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage); + share_type registrar_cut = referral - referrer_cut; + + d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting); + d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting); + d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting); + + assert( referrer_cut + registrar_cut + accumulated + reserveed + lifetime_cut == core_fee_total ); + }; + + share_type vesting_fee_subtotal(pending_fees); + share_type vested_fee_subtotal(pending_vested_fees); + share_type vesting_cashback, vested_cashback; + + if( lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && + a.is_member(d.head_block_time()) ) + { + auto bulk_discount_rate = calculate_bulk_discount_percent(props.parameters); + vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate); + vesting_fee_subtotal -= vesting_cashback; + + vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate); + vested_fee_subtotal -= vested_cashback; + } + + pay_out_fees(a, vesting_fee_subtotal, true); + d.deposit_cashback(a, vesting_cashback, true); + pay_out_fees(a, vested_fee_subtotal, false); + d.deposit_cashback(a, vested_cashback, false); + + d.modify(*this, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) { + s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal; + s.pending_fees = 0; + s.pending_vested_fees = 0; + }); + } +} + +void account_object::options_type::validate() const +{ + auto needed_witnesses = num_witness; + auto needed_committee = num_committee; + + for( vote_id_type id : votes ) + if( id.type() == vote_id_type::witness && needed_witnesses ) + --needed_witnesses; + else if ( id.type() == vote_id_type::committee && needed_committee ) + --needed_committee; + + FC_ASSERT( needed_witnesses == 0 && needed_committee == 0, + "May not specify fewer witnesses or committee members than the number voted for."); +} + +set account_member_index::get_account_members(const account_object& a)const +{ + set result; + for( auto auth : a.owner.account_auths ) + result.insert(auth.first); + return result; +} +set account_member_index::get_key_members(const account_object& a)const +{ + set result; + for( auto auth : a.owner.key_auths ) + result.insert(auth.first); + return result; +} + +void account_member_index::object_inserted(const object& obj) +{ + assert( dynamic_cast(&obj) ); // for debug only + const account_object& a = static_cast(obj); + + auto account_members = get_account_members(a); + for( auto item : account_members ) + account_to_account_memberships[item].insert(obj.id); + + auto key_members = get_key_members(a); + for( auto item : key_members ) + account_to_key_memberships[item].insert(obj.id); +} + +void account_member_index::object_removed(const object& obj) +{ + assert( dynamic_cast(&obj) ); // for debug only + const account_object& a = static_cast(obj); + + auto key_members = get_key_members(a); + for( auto item : key_members ) + account_to_key_memberships[item].erase( obj.id ); + auto account_members = get_account_members(a); + for( auto item : account_members ) + account_to_account_memberships[item].erase( obj.id ); +} + +void account_member_index::about_to_modify(const object& before) +{ + before_key_members.clear(); + before_account_members.clear(); + assert( dynamic_cast(&before) ); // for debug only + const account_object& a = static_cast(before); + before_key_members = get_key_members(a); + before_account_members = get_account_members(a); +} + +void account_member_index::object_modified(const object& after) +{ + assert( dynamic_cast(&after) ); // for debug only + const account_object& a = static_cast(after); + set after_account_members = get_account_members(a); + + { + vector removed; removed.reserve(before_account_members.size()); + std::set_difference(before_account_members.begin(), before_account_members.end(), + after_account_members.begin(), after_account_members.end(), + std::inserter(removed, removed.end())); + + for( auto itr = removed.begin(); itr != removed.end(); ++itr ) + account_to_account_memberships[*itr].erase(after.id); + + vector added; added.reserve(after_account_members.size()); + std::set_difference(after_account_members.begin(), after_account_members.end(), + before_account_members.begin(), before_account_members.end(), + std::inserter(added, added.end())); + + for( auto itr = added.begin(); itr != added.end(); ++itr ) + account_to_account_memberships[*itr].insert(after.id); + } + + + + + { + set after_key_members = get_key_members(a); + + vector removed; removed.reserve(before_key_members.size()); + std::set_difference(before_key_members.begin(), before_key_members.end(), + after_key_members.begin(), after_key_members.end(), + std::inserter(removed, removed.end())); + + for( auto itr = removed.begin(); itr != removed.end(); ++itr ) + account_to_key_memberships[*itr].erase(after.id); + + vector added; added.reserve(after_key_members.size()); + std::set_difference(after_key_members.begin(), after_key_members.end(), + before_key_members.begin(), before_key_members.end(), + std::inserter(added, added.end())); + + for( auto itr = added.begin(); itr != added.end(); ++itr ) + account_to_key_memberships[*itr].insert(after.id); + } + +} + +void account_referrer_index::object_inserted( const object& obj ) +{ +} +void account_referrer_index::object_removed( const object& obj ) +{ +} +void account_referrer_index::about_to_modify( const object& before ) +{ +} +void account_referrer_index::object_modified( const object& after ) +{ +} + } } // graphene::chain diff --git a/libraries/chain/global_parameters_evaluator.cpp b/libraries/chain/assert_evaluator.cpp similarity index 56% rename from libraries/chain/global_parameters_evaluator.cpp rename to libraries/chain/assert_evaluator.cpp index 49b05769..f7fe1a14 100644 --- a/libraries/chain/global_parameters_evaluator.cpp +++ b/libraries/chain/assert_evaluator.cpp @@ -15,26 +15,53 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include + +#include #include +#include + namespace graphene { namespace chain { -void_result global_parameters_update_evaluator::do_evaluate(const global_parameters_update_operation& o) +struct predicate_evaluator { - FC_ASSERT(trx_state->_is_proposed_trx); + typedef void result_type; + const database& db; + predicate_evaluator( const database& d ):db(d){} + + void operator()( const account_name_eq_lit_predicate& p )const + { + FC_ASSERT( p.account_id(db).name == p.name ); + } + void operator()( const asset_symbol_eq_lit_predicate& p )const + { + FC_ASSERT( p.asset_id(db).symbol == p.symbol ); + } +}; + +void_result assert_evaluator::do_evaluate( const assert_operation& o ) +{ try { + const database& _db = db(); + uint32_t skip = _db.get_node_properties().skip_flags; + auto max_predicate_opcode = _db.get_global_properties().parameters.max_predicate_opcode; + + if( skip & database::skip_assert_evaluation ) + return void_result(); + + for( const auto& p : o.predicates ) + { + FC_ASSERT( p.which() >= 0 ); + FC_ASSERT( unsigned(p.which()) < max_predicate_opcode ); + p.visit( predicate_evaluator( _db ) ); + } return void_result(); -} - -void_result global_parameters_update_evaluator::do_apply(const global_parameters_update_operation& o) -{ - db().modify(db().get_global_properties(), [&o](global_property_object& p) { - p.pending_parameters = o.new_parameters; - }); +} FC_CAPTURE_AND_RETHROW( (o) ) } +void_result assert_evaluator::do_apply( const assert_operation& o ) +{ try { + // assert_operation is always a no-op return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } } } // graphene::chain diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 096d05db..2c7a17fd 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -18,13 +18,14 @@ #include #include #include -#include +#include #include +#include #include namespace graphene { namespace chain { -object_id_type asset_create_evaluator::do_evaluate( const asset_create_operation& op ) +void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { database& d = db(); @@ -42,38 +43,46 @@ object_id_type asset_create_evaluator::do_evaluate( const asset_create_operation auto asset_symbol_itr = asset_indx.find( op.symbol ); FC_ASSERT( asset_symbol_itr == asset_indx.end() ); - core_fee_paid -= op.calculate_fee(d.current_fee_schedule()).value/2; - assert( core_fee_paid >= 0 ); + core_fee_paid -= core_fee_paid.value/2; - if( op.bitasset_options ) + if( op.bitasset_opts ) { - const asset_object& backing = op.bitasset_options->short_backing_asset(d); + const asset_object& backing = op.bitasset_opts->short_backing_asset(d); if( backing.is_market_issued() ) { const asset_bitasset_data_object& backing_bitasset_data = backing.bitasset_data(d); const asset_object& backing_backing = backing_bitasset_data.options.short_backing_asset(d); FC_ASSERT( !backing_backing.is_market_issued(), "May not create a bitasset backed by a bitasset backed by a bitasset." ); - } - FC_ASSERT( op.bitasset_options->feed_lifetime_sec > chain_parameters.block_interval && - op.bitasset_options->force_settlement_delay_sec > chain_parameters.block_interval ); + FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing_backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + } else + FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + FC_ASSERT( op.bitasset_opts->feed_lifetime_sec > chain_parameters.block_interval && + op.bitasset_opts->force_settlement_delay_sec > chain_parameters.block_interval ); + } + if( op.is_prediction_market ) + { + FC_ASSERT( op.bitasset_opts ); + FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision ); } - return object_id_type(); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } object_id_type asset_create_evaluator::do_apply( const asset_create_operation& op ) -{ +{ try { const asset_dynamic_data_object& dyn_asset = db().create( [&]( asset_dynamic_data_object& a ) { a.current_supply = 0; - a.fee_pool = op.calculate_fee(db().current_fee_schedule()).value / 2; + a.fee_pool = core_fee_paid; //op.calculate_fee(db().current_fee_schedule()).value / 2; }); asset_bitasset_data_id_type bit_asset_id; - if( op.bitasset_options.valid() ) + if( op.bitasset_opts.valid() ) bit_asset_id = db().create( [&]( asset_bitasset_data_object& a ) { - a.options = *op.bitasset_options; + a.options = *op.bitasset_opts; a.is_prediction_market = op.is_prediction_market; }).id; @@ -90,13 +99,13 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o else a.options.core_exchange_rate.base.asset_id = next_asset_id; a.dynamic_asset_data_id = dyn_asset.id; - if( op.bitasset_options.valid() ) + if( op.bitasset_opts.valid() ) a.bitasset_data_id = bit_asset_id; }); assert( new_asset.id == next_asset_id ); - return next_asset_id; -} + return new_asset.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) { try { @@ -120,7 +129,7 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) } FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) -{ +{ try { db().adjust_balance( o.issue_to_account, o.asset_to_issue ); db().modify( *asset_dyn_data, [&]( asset_dynamic_data_object& data ){ @@ -128,13 +137,13 @@ void_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_burn_evaluator::do_evaluate( const asset_burn_operation& o ) +void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& o ) { try { database& d = db(); - const asset_object& a = o.amount_to_burn.asset_id(d); + const asset_object& a = o.amount_to_reserve.asset_id(d); FC_ASSERT( !a.is_market_issued() ); from_account = &o.payer(d); @@ -145,21 +154,21 @@ void_result asset_burn_evaluator::do_evaluate( const asset_burn_operation& o ) } asset_dyn_data = &a.dynamic_asset_data_id(d); - FC_ASSERT( (asset_dyn_data->current_supply - o.amount_to_burn.amount) >= 0 ); + FC_ASSERT( (asset_dyn_data->current_supply - o.amount_to_reserve.amount) >= 0 ); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_burn_evaluator::do_apply( const asset_burn_operation& o ) -{ - db().adjust_balance( o.payer, -o.amount_to_burn ); +void_result asset_reserve_evaluator::do_apply( const asset_reserve_operation& o ) +{ try { + db().adjust_balance( o.payer, -o.amount_to_reserve ); db().modify( *asset_dyn_data, [&]( asset_dynamic_data_object& data ){ - data.current_supply -= o.amount_to_burn.amount; + data.current_supply -= o.amount_to_reserve.amount; }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_fund_fee_pool_evaluator::do_evaluate(const asset_fund_fee_pool_operation& o) { try { @@ -173,7 +182,7 @@ void_result asset_fund_fee_pool_evaluator::do_evaluate(const asset_fund_fee_pool } FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_operation& o) -{ +{ try { db().adjust_balance(o.from_account, -o.amount); db().modify( *asset_dyn_data, [&]( asset_dynamic_data_object& data ) { @@ -181,19 +190,34 @@ void_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_op }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { try { database& d = db(); - if( o.new_issuer ) FC_ASSERT(d.find_object(*o.new_issuer)); - const asset_object& a = o.asset_to_update(d); auto a_copy = a; a_copy.options = o.new_options; a_copy.validate(); + if( o.new_issuer ) + { + FC_ASSERT(d.find_object(*o.new_issuer)); + if( a.is_market_issued() && *o.new_issuer == GRAPHENE_COMMITTEE_ACCOUNT ) + { + const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d); + if( backing.is_market_issued() ) + { + const asset_object& backing_backing = backing.bitasset_data(d).options.short_backing_asset(d); + FC_ASSERT( backing_backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + } else + FC_ASSERT( backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + } + } + //There must be no bits set in o.permissions which are unset in a.issuer_permissions. FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions), "Cannot reinstate previously revoked issuer permissions on an asset."); @@ -214,7 +238,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) } FC_CAPTURE_AND_RETHROW((o)) } void_result asset_update_evaluator::do_apply(const asset_update_operation& o) -{ +{ try { database& d = db(); // If we are now disabling force settlements, cancel all open force settlement orders @@ -236,10 +260,10 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bitasset_operation& o) -{ +{ try { database& d = db(); const asset_object& a = o.asset_to_update(d); @@ -247,29 +271,51 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT(a.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset."); const asset_bitasset_data_object& b = a.bitasset_data(d); + FC_ASSERT( !b.has_settlement(), "Cannot update a bitasset after a settlement has executed" ); if( o.new_options.short_backing_asset != b.options.short_backing_asset ) { FC_ASSERT(a.dynamic_asset_data_id(d).current_supply == 0); FC_ASSERT(d.find_object(o.new_options.short_backing_asset)); + + if( a.issuer == GRAPHENE_COMMITTEE_ACCOUNT ) + { + const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d); + if( backing.is_market_issued() ) + { + const asset_object& backing_backing = backing.bitasset_data(d).options.short_backing_asset(d); + FC_ASSERT( backing_backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + } else + FC_ASSERT( backing.get_id() == asset_id_type(), + "May not create a blockchain-controlled market asset which is not backed by CORE."); + } } bitasset_to_update = &b; FC_ASSERT( o.issuer == a.issuer, "", ("o.issuer", o.issuer)("a.issuer", a.issuer) ); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasset_operation& o) -{ - db().modify(*bitasset_to_update, [&o](asset_bitasset_data_object& b) { +{ try { + bool should_update_feeds = false; + // If the minimum number of feeds to calculate a median has changed, we need to recalculate the median + if( o.new_options.minimum_feeds != bitasset_to_update->options.minimum_feeds ) + should_update_feeds = true; + + db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& b) { b.options = o.new_options; + + if( should_update_feeds ) + b.update_median_feeds(db().head_block_time()); }); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o) -{ +{ try { database& d = db(); FC_ASSERT( o.new_feed_producers.size() <= d.get_global_properties().parameters.maximum_asset_feed_publishers ); @@ -279,16 +325,16 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat const asset_object& a = o.asset_to_update(d); FC_ASSERT(a.is_market_issued(), "Cannot update feed producers on a non-BitAsset."); - FC_ASSERT(a.issuer != account_id_type(), "Cannot set feed producers on a genesis-issued asset."); + FC_ASSERT(a.issuer != GRAPHENE_COMMITTEE_ACCOUNT, "Cannot set feed producers on a committee-issued asset."); const asset_bitasset_data_object& b = a.bitasset_data(d); bitasset_to_update = &b; FC_ASSERT( a.issuer == o.issuer ); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o) -{ +{ try { db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& a) { //This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored. //I need to update the map such that the keys match the new publishers, but not munge the old price feeds from @@ -307,13 +353,13 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f a.feeds[*itr]; a.update_median_feeds(db().head_block_time()); }); + db().check_call_orders( o.asset_to_update(db()) ); return void_result(); -} - +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle_evaluator::operation_type& op) -{ +{ try { const database& d = db(); asset_to_settle = &op.asset_to_settle(d); FC_ASSERT(asset_to_settle->is_market_issued()); @@ -330,52 +376,82 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle "Cannot force settle at supplied price: least collateralized short lacks sufficient collateral to settle."); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (op) ) } void_result asset_global_settle_evaluator::do_apply(const asset_global_settle_evaluator::operation_type& op) -{ +{ try { database& d = db(); d.globally_settle_asset( op.asset_to_settle(db()), op.settle_price ); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::operation_type& op) -{ +void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::operation_type& op) +{ try { const database& d = db(); asset_to_settle = &op.amount.asset_id(d); FC_ASSERT(asset_to_settle->is_market_issued()); - FC_ASSERT(asset_to_settle->can_force_settle()); + const auto& bitasset = asset_to_settle->bitasset_data(d); + FC_ASSERT(asset_to_settle->can_force_settle() || bitasset.has_settlement() ); + if( bitasset.is_prediction_market ) + FC_ASSERT( bitasset.has_settlement(), "global settlement must occur before force settling a prediction market" ); + else if( bitasset.current_feed.settlement_price.is_null() ) + FC_THROW_EXCEPTION(insufficient_feeds, "Cannot force settle with no price feed."); FC_ASSERT(d.get_balance(d.get(op.account), *asset_to_settle) >= op.amount); - return d.get_index_type().get_next_id(); -} + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) -{ +operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) +{ try { database& d = db(); d.adjust_balance(op.account, -op.amount); - return d.create([&](force_settlement_object& s) { - s.owner = op.account; - s.balance = op.amount; - s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec; - }).id; -} + + const auto& bitasset = asset_to_settle->bitasset_data(d); + if( bitasset.has_settlement() ) + { + auto settled_amount = op.amount * bitasset.settlement_price; + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); + + d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + obj.settlement_fund -= settled_amount.amount; + }); + + d.adjust_balance(op.account, settled_amount); + + const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + + d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + obj.current_supply -= op.amount.amount; + }); + + return settled_amount; + } + else + { + return d.create([&](force_settlement_object& s) { + s.owner = op.account; + s.balance = op.amount; + s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec; + }).id; + } +} FC_CAPTURE_AND_RETHROW( (op) ) } void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) { try { database& d = db(); - const asset_object& quote = o.asset_id(d); + const asset_object& base = o.asset_id(d); //Verify that this feed is for a market-issued asset and that asset is backed by the base - FC_ASSERT(quote.is_market_issued()); + FC_ASSERT(base.is_market_issued()); - const asset_bitasset_data_object& bitasset = quote.bitasset_data(d); - FC_ASSERT(bitasset.options.short_backing_asset == o.feed.call_limit.base.asset_id); + const asset_bitasset_data_object& bitasset = base.bitasset_data(d); + FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" ); + FC_ASSERT(o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset); //Verify that the publisher is authoritative to publish a feed - if( quote.issuer == account_id_type() ) + if( base.issuer == account_id_type() ) { - //It's a delegate-fed asset. Verify that publisher is an active delegate or witness. - FC_ASSERT(d.get(account_id_type()).active.auths.count(o.publisher) || + //It's a committee_member-fed asset. Verify that publisher is an active committee_member or witness. + FC_ASSERT(d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher) || d.get_global_properties().witness_accounts.count(o.publisher)); } else { FC_ASSERT(bitasset.feeds.count(o.publisher)); @@ -388,14 +464,20 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope { try { database& d = db(); - const asset_object& quote = o.asset_id(d); + const asset_object& base = o.asset_id(d); + const asset_bitasset_data_object& bad = base.bitasset_data(d); + + auto old_feed = bad.current_feed; // Store medians for this asset - d.modify(quote.bitasset_data(d), [&o,&d](asset_bitasset_data_object& a) { + d.modify(bad , [&o,&d](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair(d.head_block_time(), o.feed); a.update_median_feeds(d.head_block_time()); }); + if( !(old_feed == bad.current_feed) ) + db().check_call_orders(base); + return void_result(); - } FC_CAPTURE_AND_RETHROW((o)) } +} FC_CAPTURE_AND_RETHROW((o)) } } } // graphene::chain diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index f3e5bf07..f56b107a 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -16,6 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include @@ -50,8 +51,10 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point } } - if( current_feeds.empty() ) + // If there are no valid feeds, or the number available is less than the minimum to calculate a median... + if( current_feeds.size() < options.minimum_feeds ) { + //... don't calculate a median, and set a null feed current_feed_publication_time = current_time; current_feed = price_feed(); return; @@ -79,40 +82,6 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point current_feed = median_feed; } -void asset_object::asset_options::validate()const -{ - FC_ASSERT( max_supply > 0 ); - FC_ASSERT( max_supply <= GRAPHENE_MAX_SHARE_SUPPLY ); - FC_ASSERT( market_fee_percent <= GRAPHENE_100_PERCENT ); - FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); - FC_ASSERT( min_market_fee >= 0 && min_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); - // There must be no high bits in permissions whose meaning is not known. - FC_ASSERT( !(issuer_permissions & ~ASSET_ISSUER_PERMISSION_MASK) ); - // There must be no high bits in flags which are not also high in permissions. - FC_ASSERT( !(flags & ~issuer_permissions ) ); - // The global_settle flag may never be set (this is a permission only) - FC_ASSERT( !(flags & global_settle) ); - core_exchange_rate.validate(); - FC_ASSERT( core_exchange_rate.base.asset_id.instance.value == 0 || - core_exchange_rate.quote.asset_id.instance.value == 0 ); - - if(!whitelist_authorities.empty() || !blacklist_authorities.empty()) - FC_ASSERT( flags & white_list ); - for( auto item : whitelist_markets ) - { - FC_ASSERT( blacklist_markets.find(item) == blacklist_markets.end() ); - } - for( auto item : blacklist_markets ) - { - FC_ASSERT( whitelist_markets.find(item) == whitelist_markets.end() ); - } -} - -void asset_object::bitasset_options::validate() const -{ - FC_ASSERT(force_settlement_offset_percent <= GRAPHENE_100_PERCENT); - FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT); -} asset asset_object::amount_from_string(string amount_string) const @@ -142,7 +111,7 @@ asset asset_object::amount_from_string(string amount_string) const share_type satoshis = 0; share_type scaled_precision = 1; - for( short i = 0; i < precision; ++i ) + for( uint8_t i = 0; i < precision; ++i ) scaled_precision *= 10; const auto decimal_pos = amount_string.find( '.' ); @@ -164,7 +133,7 @@ asset asset_object::amount_from_string(string amount_string) const satoshis += std::stoll( rhs ); } - FC_ASSERT( satoshis <= GRAPHENE_BLOCKCHAIN_MAX_SHARES ); + FC_ASSERT( satoshis <= GRAPHENE_MAX_SHARE_SUPPLY ); if( negative_found ) satoshis *= -1; @@ -175,7 +144,7 @@ asset asset_object::amount_from_string(string amount_string) const string asset_object::amount_to_string(share_type amount) const { share_type scaled_precision = 1; - for( short i = 0; i < precision; ++i ) + for( uint8_t i = 0; i < precision; ++i ) scaled_precision *= 10; assert(scaled_precision > 0); diff --git a/libraries/chain/balance_evaluator.cpp b/libraries/chain/balance_evaluator.cpp new file mode 100644 index 00000000..d69003f5 --- /dev/null +++ b/libraries/chain/balance_evaluator.cpp @@ -0,0 +1,69 @@ +#include + +namespace graphene { namespace chain { + +void_result balance_claim_evaluator::do_evaluate(const balance_claim_operation& op) +{ + database& d = db(); + balance = &op.balance_to_claim(d); + + GRAPHENE_ASSERT( + op.balance_owner_key == balance->owner || + pts_address(op.balance_owner_key, false, 56) == balance->owner || + pts_address(op.balance_owner_key, true, 56) == balance->owner || + pts_address(op.balance_owner_key, false, 0) == balance->owner || + pts_address(op.balance_owner_key, true, 0) == balance->owner, + balance_claim_owner_mismatch, + "Balance owner key was specified as '${op}' but balance's actual owner is '${bal}'", + ("op", op.balance_owner_key) + ("bal", balance->owner) + ); + if( !(d.get_node_properties().skip_flags & (database::skip_authority_check | + database::skip_transaction_signatures)) ) + + FC_ASSERT(op.total_claimed.asset_id == balance->asset_type()); + + if( balance->is_vesting_balance() ) + { + GRAPHENE_ASSERT( + balance->vesting_policy->is_withdraw_allowed( + { balance->balance, + d.head_block_time(), + op.total_claimed } ), + balance_claim_invalid_claim_amount, + "Attempted to claim ${c} from a vesting balance with ${a} available", + ("c", op.total_claimed)("a", balance->available(d.head_block_time())) + ); + GRAPHENE_ASSERT( + d.head_block_time() - balance->last_claim_date >= fc::days(1), + balance_claim_claimed_too_often, + "Genesis vesting balances may not be claimed more than once per day." + ); + return {}; + } + + FC_ASSERT(op.total_claimed == balance->balance); + return {}; +} + +/** + * @note the fee is always 0 for this particular operation because once the + * balance is claimed it frees up memory and it cannot be used to spam the network + */ +void_result balance_claim_evaluator::do_apply(const balance_claim_operation& op) +{ + database& d = db(); + + if( balance->is_vesting_balance() && op.total_claimed < balance->balance ) + d.modify(*balance, [&](balance_object& b) { + b.vesting_policy->on_withdraw({b.balance, d.head_block_time(), op.total_claimed}); + b.balance -= op.total_claimed; + b.last_claim_date = d.head_block_time(); + }); + else + d.remove(*balance); + + d.adjust_balance(op.deposit_to_account, op.total_claimed); + return {}; +} +} } // namespace graphene::chain diff --git a/libraries/chain/block_database.cpp b/libraries/chain/block_database.cpp new file mode 100644 index 00000000..2851ad64 --- /dev/null +++ b/libraries/chain/block_database.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include + +namespace graphene { namespace chain { + +struct index_entry +{ + uint64_t block_pos = 0; + uint32_t block_size = 0; + block_id_type block_id; +}; + }} +FC_REFLECT( graphene::chain::index_entry, (block_pos)(block_size)(block_id) ); + +namespace graphene { namespace chain { + +void block_database::open( const fc::path& dbdir ) +{ try { + fc::create_directories(dbdir); + _block_num_to_pos.exceptions(std::ios_base::failbit | std::ios_base::badbit); + _blocks.exceptions(std::ios_base::failbit | std::ios_base::badbit); + + if( !fc::exists( dbdir/"index" ) ) + { + _block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc); + _blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc); + } + else + { + _block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out ); + _blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out ); + } +} FC_CAPTURE_AND_RETHROW( (dbdir) ) } + +bool block_database::is_open()const +{ + return _blocks.is_open(); +} + +void block_database::close() +{ + _blocks.close(); + _block_num_to_pos.close(); +} + +void block_database::flush() +{ + _blocks.flush(); + _block_num_to_pos.flush(); +} + +void block_database::store( const block_id_type& id, const signed_block& b ) +{ + auto num = block_header::num_from_id(id); + _block_num_to_pos.seekp( sizeof( index_entry ) * num ); + index_entry e; + _blocks.seekp( 0, _blocks.end ); + auto vec = fc::raw::pack( b ); + e.block_pos = _blocks.tellp(); + e.block_size = vec.size(); + e.block_id = id; + _blocks.write( vec.data(), vec.size() ); + _block_num_to_pos.write( (char*)&e, sizeof(e) ); +} + +void block_database::remove( const block_id_type& id ) +{ try { + index_entry e; + auto index_pos = sizeof(e)*block_header::num_from_id(id); + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + if ( _block_num_to_pos.tellg() <= index_pos ) + FC_THROW_EXCEPTION(fc::key_not_found_exception, "Block ${id} not contained in block database", ("id", id)); + + _block_num_to_pos.seekg( index_pos ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + + if( e.block_id == id ) + { + e.block_size = 0; + _block_num_to_pos.seekp( sizeof(e)*block_header::num_from_id(id) ); + _block_num_to_pos.write( (char*)&e, sizeof(e) ); + } +} FC_CAPTURE_AND_RETHROW( (id) ) } + +bool block_database::contains( const block_id_type& id )const +{ + index_entry e; + auto index_pos = sizeof(e)*block_header::num_from_id(id); + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + if ( _block_num_to_pos.tellg() <= index_pos ) + return false; + _block_num_to_pos.seekg( index_pos ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + + return e.block_id == id; +} + +block_id_type block_database::fetch_block_id( uint32_t block_num )const +{ + index_entry e; + auto index_pos = sizeof(e)*block_num; + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + if ( _block_num_to_pos.tellg() <= index_pos ) + FC_THROW_EXCEPTION(fc::key_not_found_exception, "Block number ${block_num} not contained in block database", ("block_num", block_num)); + + _block_num_to_pos.seekg( index_pos ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + + return e.block_id; +} + +optional block_database::fetch_optional( const block_id_type& id )const +{ + try + { + index_entry e; + auto index_pos = sizeof(e)*block_header::num_from_id(id); + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + if ( _block_num_to_pos.tellg() <= index_pos ) + return {}; + + _block_num_to_pos.seekg( index_pos ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + + if( e.block_id != id ) return optional(); + + vector data( e.block_size ); + _blocks.seekg( e.block_pos ); + _blocks.read( data.data(), e.block_size ); + auto result = fc::raw::unpack(data); + FC_ASSERT( result.id() == e.block_id ); + return result; + } + catch (const fc::exception&) + { + } + catch (const std::exception&) + { + } + return optional(); +} + +optional block_database::fetch_by_number( uint32_t block_num )const +{ + try + { + index_entry e; + auto index_pos = sizeof(e)*block_num; + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + if ( _block_num_to_pos.tellg() <= index_pos ) + return {}; + + _block_num_to_pos.seekg( index_pos, _block_num_to_pos.beg ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + + vector data( e.block_size ); + _blocks.seekg( e.block_pos ); + _blocks.read( data.data(), e.block_size ); + auto result = fc::raw::unpack(data); + FC_ASSERT( result.id() == e.block_id ); + return result; + } + catch (const fc::exception&) + { + } + catch (const std::exception&) + { + } + return optional(); +} + +optional block_database::last()const +{ + try + { + index_entry e; + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + + if( _block_num_to_pos.tellp() < sizeof(index_entry) ) + return optional(); + + _block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.end ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + while( e.block_size == 0 && _blocks.tellg() > 0 ) + { + _block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.cur ); + _block_num_to_pos.read( (char*)&e, sizeof(e) ); + } + + if( e.block_size == 0 ) + return optional(); + + vector data( e.block_size ); + _blocks.seekg( e.block_pos ); + _blocks.read( data.data(), e.block_size ); + auto result = fc::raw::unpack(data); + return result; + } + catch (const fc::exception&) + { + } + catch (const std::exception&) + { + } + return optional(); +} +} } diff --git a/libraries/chain/delegate_evaluator.cpp b/libraries/chain/committee_member_evaluator.cpp similarity index 55% rename from libraries/chain/delegate_evaluator.cpp rename to libraries/chain/committee_member_evaluator.cpp index b86409f8..345b9b77 100644 --- a/libraries/chain/delegate_evaluator.cpp +++ b/libraries/chain/committee_member_evaluator.cpp @@ -15,31 +15,52 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include +#include +#include #include #include +#include +#include namespace graphene { namespace chain { -object_id_type delegate_create_evaluator::do_evaluate( const delegate_create_operation& op ) -{ - FC_ASSERT(db().get(op.delegate_account).is_lifetime_member()); - return object_id_type(); -} -object_id_type delegate_create_evaluator::do_apply( const delegate_create_operation& op ) -{ +void_result committee_member_create_evaluator::do_evaluate( const committee_member_create_operation& op ) +{ try { + FC_ASSERT(db().get(op.committee_member_account).is_lifetime_member()); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type committee_member_create_evaluator::do_apply( const committee_member_create_operation& op ) +{ try { vote_id_type vote_id; db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) { vote_id = p.get_next_vote_id(vote_id_type::committee); }); - const auto& new_del_object = db().create( [&]( delegate_object& obj ){ - obj.delegate_account = op.delegate_account; + const auto& new_del_object = db().create( [&]( committee_member_object& obj ){ + obj.committee_member_account = op.committee_member_account; obj.vote_id = vote_id; + obj.url = op.url; }); return new_del_object.id; -} +} FC_CAPTURE_AND_RETHROW( (op) ) } + + + +void_result committee_member_update_global_parameters_evaluator::do_evaluate(const committee_member_update_global_parameters_operation& o) +{ try { + FC_ASSERT(trx_state->_is_proposed_trx); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result committee_member_update_global_parameters_evaluator::do_apply(const committee_member_update_global_parameters_operation& o) +{ try { + db().modify(db().get_global_properties(), [&o](global_property_object& p) { + p.pending_parameters = o.new_parameters; + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } } } // graphene::chain diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp new file mode 100644 index 00000000..2e4ce662 --- /dev/null +++ b/libraries/chain/database.cpp @@ -0,0 +1,12 @@ +/// This file combines these sources to perform a partial unity build +#include +#include "db_balance.cpp" +#include "db_block.cpp" +#include "db_debug.cpp" +#include "db_getter.cpp" +#include "db_init.cpp" +#include "db_maint.cpp" +#include "db_management.cpp" +#include "db_market.cpp" +#include "db_update.cpp" +#include "db_witness_schedule.cpp" diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 791d8488..0434239e 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -38,10 +38,9 @@ asset database::get_balance(const account_object& owner, const asset_object& ass return get_balance(owner.get_id(), asset_obj.get_id()); } -// TODO: this method should be removed -asset database::get_balance( const account_object* owner, const asset_object* asset_obj )const +string database::to_pretty_string( const asset& a )const { - return get_balance(*owner, *asset_obj); + return a.asset_id(*this).amount_to_pretty_string(a.amount); } void database::adjust_balance(account_id_type account, asset delta ) @@ -53,14 +52,18 @@ void database::adjust_balance(account_id_type account, asset delta ) auto itr = index.find(boost::make_tuple(account, delta.asset_id)); if(itr == index.end()) { - FC_ASSERT(delta.amount > 0); + FC_ASSERT( delta.amount > 0, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", + ("a",account(*this).name) + ("b",to_pretty_string(asset(0,delta.asset_id))) + ("r",to_pretty_string(-delta))); create([account,&delta](account_balance_object& b) { b.owner = account; b.asset_type = delta.asset_id; b.balance = delta.amount.value; }); } else { - FC_ASSERT(delta.amount > 0 || itr->get_balance() >= -delta); + if( delta.amount < 0 ) + FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account(*this).name)("b",to_pretty_string(itr->get_balance()))("r",to_pretty_string(-delta))); modify(*itr, [delta](account_balance_object& b) { b.adjust_balance(delta); }); @@ -73,12 +76,6 @@ void database::adjust_balance(const account_object& account, asset delta ) adjust_balance( account.id, delta); } -// TODO: This method should be removed -void database::adjust_balance(const account_object* account, asset delta) -{ - adjust_balance(*account, delta); -} - void database::adjust_core_in_orders( const account_object& acnt, asset delta ) { if( delta.asset_id == asset_id_type(0) && delta.amount != 0 ) @@ -97,6 +94,17 @@ void database::deposit_cashback(const account_object& acct, share_type amount, b if( amount == 0 ) return; + if( acct.get_id() == GRAPHENE_COMMITTEE_ACCOUNT || acct.get_id() == GRAPHENE_WITNESS_ACCOUNT || + acct.get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT || acct.get_id() == GRAPHENE_NULL_ACCOUNT || + acct.get_id() == GRAPHENE_TEMP_ACCOUNT ) + { + // The blockchain's accounts do not get cashback; it simply goes to the reserve pool. + modify(get(asset_id_type()).dynamic_asset_data_id(*this), [amount](asset_dynamic_data_object& d) { + d.current_supply -= amount; + }); + return; + } + uint32_t global_vesting_seconds = get_global_properties().parameters.cashback_vesting_period_seconds; fc::time_point_sec now = head_block_time(); diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index cfdd63f8..9cccc9ff 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -20,17 +20,17 @@ #include #include -#include #include #include #include #include +#include namespace graphene { namespace chain { bool database::is_known_block( const block_id_type& id )const { - return _fork_db.is_known_block(id) || _block_id_to_block.find(id).valid(); + return _fork_db.is_known_block(id) || _block_id_to_block.contains(id); } /** * Only return true *if* the transaction has not expired or been invalidated. If this @@ -45,10 +45,7 @@ bool database::is_known_transaction( const transaction_id_type& id )const block_id_type database::get_block_id_for_num( uint32_t block_num )const { try { - block_id_type lb; lb._hash[0] = htonl(block_num); - auto itr = _block_id_to_block.lower_bound( lb ); - FC_ASSERT( itr.valid() && itr.key()._hash[0] == lb._hash[0] ); - return itr.key(); + return _block_id_to_block.fetch_block_id( block_num ); } FC_CAPTURE_AND_RETHROW( (block_num) ) } optional database::fetch_block_by_id( const block_id_type& id )const @@ -65,12 +62,7 @@ optional database::fetch_block_by_number( uint32_t num )const if( results.size() == 1 ) return results[0]->data; else - { - block_id_type lb; lb._hash[0] = htonl(num); - auto itr = _block_id_to_block.lower_bound( lb ); - if( itr.valid() && itr.key()._hash[0] == lb._hash[0] ) - return itr.value(); - } + return _block_id_to_block.fetch_by_number(num); return optional(); } @@ -88,29 +80,31 @@ const signed_transaction& database::get_recent_transaction(const transaction_id_ * * @return true if we switched forks as a result of this push. */ -bool database::push_block( const signed_block& new_block, uint32_t skip ) +bool database::push_block(const signed_block& new_block, uint32_t skip) +{ + + bool result; + with_skip_flags( skip, [&]() + { + result = _push_block( new_block ); + } ); + return result; +} + +bool database::_push_block(const signed_block& new_block) { try { + uint32_t skip = get_node_properties().skip_flags; if( !(skip&skip_fork_db) ) { - wdump((new_block.id())(new_block.previous)); - auto new_head = _fork_db.push_block( new_block ); + auto 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() ) { - edump((new_head->data.previous)); //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() ) { - auto branches = _fork_db.fetch_branch_from( new_head->data.id(), _pending_block.previous ); - for( auto item : branches.first ) - { - wdump( ("new")(item->id)(item->data.previous) ); - } - for( auto item : branches.second ) - { - wdump( ("old")(item->id)(item->data.previous) ); - } + auto branches = _fork_db.fetch_branch_from(new_head->data.id(), _pending_block.previous); // pop blocks until we hit the forked block while( head_block_id() != branches.second.back()->data.previous ) @@ -123,14 +117,12 @@ bool database::push_block( const signed_block& new_block, uint32_t skip ) try { auto session = _undo_db.start_undo_session(); apply_block( (*ritr)->data, skip ); - _block_id_to_block.store( new_block.id(), (*ritr)->data ); + _block_id_to_block.store( (*ritr)->id, (*ritr)->data ); session.commit(); } catch ( const fc::exception& e ) { except = e; } if( except ) { - elog( "Encountered error when switching to a longer fork at id ${id}. Going back.", - ("id", (*ritr)->id) ); // remove the rest of branches.first from the fork_db, those blocks are invalid while( ritr != branches.first.rend() ) { @@ -167,8 +159,8 @@ bool database::push_block( const signed_block& new_block, uint32_t skip ) try { auto session = _undo_db.start_undo_session(); - apply_block( new_block, skip ); - _block_id_to_block.store( new_block.id(), new_block ); + apply_block(new_block, skip); + _block_id_to_block.store(new_block.id(), new_block); session.commit(); } catch ( const fc::exception& e ) { elog("Failed to push new block:\n${e}", ("e", e.to_detail_string())); @@ -189,18 +181,29 @@ bool database::push_block( const signed_block& new_block, uint32_t skip ) * queues. */ processed_transaction database::push_transaction( const signed_transaction& trx, uint32_t skip ) +{ try { + processed_transaction result; + with_skip_flags( skip, [&]() + { + result = _push_transaction( trx ); + } ); + return result; +} FC_CAPTURE_AND_RETHROW( (trx) ) } + +processed_transaction database::_push_transaction( const signed_transaction& trx ) { - //wdump((trx.digest())(trx.id())); + uint32_t skip = get_node_properties().skip_flags; // 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_session ) _pending_block_session = _undo_db.start_undo_session(); auto session = _undo_db.start_undo_session(); - auto processed_trx = apply_transaction( trx, skip ); + auto processed_trx = _apply_transaction( trx ); _pending_block.transactions.push_back(processed_trx); FC_ASSERT( (skip & skip_block_size_check) || fc::raw::pack_size(_pending_block) <= get_global_properties().parameters.maximum_block_size ); + notify_changed_objects(); // The transaction applied successfully. Merge its changes into the pending block session. session.merge(); return processed_trx; @@ -225,9 +228,6 @@ processed_transaction database::push_proposal(const proposal_object& proposal) return std::make_pair(id, authority::owner); }); - ilog("Attempting to push proposal ${prop}", ("prop", proposal)); - idump((eval_state.approved_by)); - eval_state.operation_results.reserve(proposal.proposed_transaction.operations.size()); processed_transaction ptrx(proposal.proposed_transaction); eval_state._trx = &ptrx; @@ -248,23 +248,46 @@ signed_block database::generate_block( const fc::ecc::private_key& block_signing_private_key, uint32_t skip /* = 0 */ ) +{ + signed_block result; + with_skip_flags( skip, [&]() + { + result = _generate_block( when, witness_id, block_signing_private_key ); + } ); + return result; +} + +signed_block database::_generate_block( + fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key + ) { try { + uint32_t skip = get_node_properties().skip_flags; uint32_t slot_num = get_slot_at_time( when ); witness_id_type scheduled_witness = get_scheduled_witness( slot_num ).first; FC_ASSERT( scheduled_witness == witness_id ); const auto& witness_obj = witness_id(*this); - if( !(skip & skip_delegate_signature) ) - FC_ASSERT( witness_obj.signing_key(*this).key() == block_signing_private_key.get_public_key() ); + if( !(skip & skip_witness_signature) ) + FC_ASSERT( witness_obj.signing_key == block_signing_private_key.get_public_key() ); _pending_block.timestamp = when; - secret_hash_type::encoder last_enc; - fc::raw::pack( last_enc, block_signing_private_key ); - fc::raw::pack( last_enc, witness_obj.last_secret ); - _pending_block.previous_secret = last_enc.result(); + // Genesis witnesses start with a default initial secret + if( witness_obj.next_secret_hash == secret_hash_type::hash( secret_hash_type() ) ) + { + _pending_block.previous_secret = secret_hash_type(); + } + else + { + secret_hash_type::encoder last_enc; + fc::raw::pack( last_enc, block_signing_private_key ); + fc::raw::pack( last_enc, witness_obj.previous_secret ); + _pending_block.previous_secret = last_enc.result(); + } secret_hash_type::encoder next_enc; fc::raw::pack( next_enc, block_signing_private_key ); @@ -274,15 +297,27 @@ signed_block database::generate_block( _pending_block.transaction_merkle_root = _pending_block.calculate_merkle_root(); _pending_block.witness = witness_id; - if( !(skip & skip_delegate_signature) ) _pending_block.sign( block_signing_private_key ); + if( !(skip & skip_witness_signature) ) _pending_block.sign( block_signing_private_key ); FC_ASSERT( fc::raw::pack_size(_pending_block) <= get_global_properties().parameters.maximum_block_size ); - //This line used to std::move(_pending_block) but this is unsafe as _pending_block is later referenced without being - //reinitialized. Future optimization could be to move it, then reinitialize it with the values we need to preserve. signed_block tmp = _pending_block; tmp.transaction_merkle_root = tmp.calculate_merkle_root(); _pending_block.transactions.clear(); - push_block( tmp, skip ); + + bool failed = false; + try { push_block( tmp, skip ); } catch ( const fc::exception& e ) { failed = true; } + if( failed ) + { + for( const auto& trx : tmp.transactions ) + { + try { + push_transaction( trx, skip ); + } catch ( const fc::exception& e ) { + wlog( "Transaction is no longer valid: ${trx}", ("trx",trx) ); + } + } + return _generate_block( when, witness_id, block_signing_private_key ); + } return tmp; } FC_CAPTURE_AND_RETHROW( (witness_id) ) } @@ -330,10 +365,35 @@ const vector& database::get_applied_operations() const //////////////////// private methods //////////////////// void database::apply_block( const signed_block& next_block, uint32_t skip ) +{ + auto block_num = next_block.block_num(); + if( _checkpoints.size() ) + { + 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()) ); + + auto last = _checkpoints.rbegin(); + if( last->first >= block_num ) + { + // WE CAN SKIP ALMOST EVERYTHING + skip = ~0; + } + } + + with_skip_flags( skip, [&]() + { + _apply_block( next_block ); + } ); + return; +} + +void database::_apply_block( const signed_block& next_block ) { try { + uint32_t skip = get_node_properties().skip_flags; _applied_ops.clear(); - FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root() ); + FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root(), "", ("next_block.transaction_merkle_root",next_block.transaction_merkle_root)("calc",next_block.calculate_merkle_root())("next_block",next_block)("id",next_block.id()) ); const witness_object& signing_witness = validate_block_header(skip, next_block); const auto& global_props = get_global_properties(); @@ -376,37 +436,51 @@ void database::apply_block( const signed_block& next_block, uint32_t skip ) applied_block( next_block ); //emit _applied_ops.clear(); + notify_changed_objects(); + + update_pending_block(next_block, current_block_interval); +} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) } + +void database::notify_changed_objects() +{ const auto& head_undo = _undo_db.head(); vector changed_ids; changed_ids.reserve(head_undo.old_values.size()); for( const auto& item : head_undo.old_values ) changed_ids.push_back(item.first); changed_objects(changed_ids); +} +processed_transaction database::apply_transaction(const signed_transaction& trx, uint32_t skip) +{ + processed_transaction result; + with_skip_flags(skip, [&]() + { + result = _apply_transaction(trx); + }); + return result; +} - update_pending_block(next_block, current_block_interval); -} FC_CAPTURE_AND_RETHROW( (next_block.block_num())(skip) ) } - -processed_transaction database::apply_transaction( const signed_transaction& trx, uint32_t skip ) +processed_transaction database::_apply_transaction(const signed_transaction& trx) { try { + uint32_t skip = get_node_properties().skip_flags; trx.validate(); auto& trx_idx = get_mutable_index_type(); auto trx_id = trx.id(); FC_ASSERT( (skip & skip_transaction_dupe_check) || trx_idx.indices().get().find(trx_id) == trx_idx.indices().get().end() ); - transaction_evaluation_state eval_state(this, skip&skip_authority_check ); + transaction_evaluation_state eval_state(this); const chain_parameters& chain_parameters = get_global_properties().parameters; eval_state._trx = &trx; //This check is used only if this transaction has an absolute expiration time. if( !(skip & skip_transaction_signatures) && trx.relative_expiration == 0 ) { + eval_state._sigs.reserve(trx.signatures.size()); + for( const auto& sig : trx.signatures ) { - FC_ASSERT( sig.first(*this).key_address() == fc::ecc::public_key( sig.second, trx.digest() ), "", - ("trx",trx) - ("digest",trx.digest()) - ("sig.first",sig.first) - ("key_address",sig.first(*this).key_address()) - ("addr", address(fc::ecc::public_key( sig.second, trx.digest() ))) ); + FC_ASSERT( eval_state._sigs.insert(std::make_pair(public_key_type(fc::ecc::public_key(sig, trx.digest())), + false)).second, + "Multiple signatures by same key detected" ); } } @@ -422,22 +496,76 @@ processed_transaction database::apply_transaction( const signed_transaction& trx //Check the TaPoS reference and expiration time //Remember that the TaPoS block number is abbreviated; it contains only the lower 16 bits. //Lookup TaPoS block summary by block number (remember block summary instances are the block numbers) - const block_summary_object& tapos_block_summary - = static_cast(get_index() - .get(block_summary_id_type((head_block_num() & ~0xffff) - + trx.ref_block_num))); + + // Let N = head_block_num(), a = N & 0xFFFF, and r = trx.ref_block_num + // + // We want to solve for the largest block height x such that + // these two conditions hold: + // + // (a) 0x10000 divides x-r + // (b) x <= N + // + // Let us define: + // + // x1 = N-a+r + // x0 = x1-2^16 + // x2 = x1+2^16 + // + // It is clear that x0, x1, x2 are consecutive solutions to (a). + // + // Since r < 2^16 and a < 2^16, it follows that + // -2^16 < r-a < 2^16. From this we know that x0 < N and x2 > N. + // + // Case (1): x1 <= N. In this case, x1 must be the greatest + // integer that satisfies (a) and (b); for x2, the next + // largest integer that satisfies (a), does not satisfy (b). + // + // Case (2): x1 > N. In this case, x0 must be the greatest + // integer that satisfies (a) and (b); for x1, the next + // largest integer that satisfies (a), does not satisfy (b). + // + int64_t N = head_block_num(); + int64_t a = N & 0xFFFF; + int64_t r = trx.ref_block_num; + + int64_t x1 = N-a+r; + int64_t x0 = x1 - 0x10000; + int64_t x2 = x1 + 0x10000; + + assert( x0 < N ); + assert( x1 >= 0 ); + assert( x2 > N ); + + uint32_t ref_block_height; + if( x1 <= N ) + { + FC_ASSERT( x1 > 0 ); + ref_block_height = uint32_t( x1 ); + } + else + { + ref_block_height = uint32_t( x0 ); + } + + const block_summary_object& tapos_block_summary = + static_cast( + get_index() + .get(block_summary_id_type(ref_block_height)) + ); //This is the signature check for transactions with relative expiration. if( !(skip & skip_transaction_signatures) ) { + eval_state._sigs.reserve(trx.signatures.size()); + for( const auto& sig : trx.signatures ) { - address trx_addr = fc::ecc::public_key(sig.second, trx.digest(tapos_block_summary.block_id)); - FC_ASSERT(sig.first(*this).key_address() == trx_addr, - "", - ("sig.first",sig.first) - ("key_address",sig.first(*this).key_address()) - ("addr", trx_addr)); + FC_ASSERT(eval_state._sigs.insert(std::make_pair( + public_key_type( + fc::ecc::public_key(sig, + trx.digest(tapos_block_summary.block_id))), + false)).second, + "Multiple signatures by same key detected"); } } @@ -445,10 +573,11 @@ processed_transaction database::apply_transaction( const signed_transaction& trx FC_ASSERT( trx.ref_block_prefix == tapos_block_summary.block_id._hash[1] ); trx_expiration = tapos_block_summary.timestamp + chain_parameters.block_interval*trx.relative_expiration; } else if( trx.relative_expiration == 0 ) { - trx_expiration = fc::time_point_sec(trx.ref_block_prefix); - FC_ASSERT( trx_expiration <= _pending_block.timestamp + chain_parameters.maximum_time_until_expiration ); + trx_expiration = fc::time_point_sec() + fc::seconds(trx.ref_block_prefix); + FC_ASSERT( trx_expiration <= _pending_block.timestamp + chain_parameters.maximum_time_until_expiration, "", + ("trx_expiration",trx_expiration)("_pending_block.timestamp",_pending_block.timestamp)("max_til_exp",chain_parameters.maximum_time_until_expiration)); } - FC_ASSERT( _pending_block.timestamp <= trx_expiration ); + FC_ASSERT( _pending_block.timestamp <= trx_expiration, "", ("pending.timestamp",_pending_block.timestamp)("trx_exp",trx_expiration) ); } else if( !(skip & skip_transaction_signatures) ) { FC_ASSERT(trx.relative_expiration == 0, "May not use transactions with a reference block in block 1!"); } @@ -463,8 +592,9 @@ processed_transaction database::apply_transaction( const signed_transaction& trx }); } - eval_state.operation_results.reserve( trx.operations.size() ); + eval_state.operation_results.reserve(trx.operations.size()); + //Finally process the operations processed_transaction ptrx(trx); _current_op_in_trx = 0; for( const auto& op : ptrx.operations ) @@ -472,13 +602,27 @@ processed_transaction database::apply_transaction( const signed_transaction& trx eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); ++_current_op_in_trx; } - ptrx.operation_results = std::move( eval_state.operation_results ); + ptrx.operation_results = std::move(eval_state.operation_results); + + //Make sure the temp account has no non-zero balances + const auto& index = get_index_type().indices().get(); + auto range = index.equal_range(GRAPHENE_TEMP_ACCOUNT); + std::for_each(range.first, range.second, [](const account_balance_object& b) { FC_ASSERT(b.balance == 0); }); + + //Make sure all signatures were needed to validate the transaction + if( !(skip & (skip_transaction_signatures|skip_authority_check)) ) + { + for( const auto& item : eval_state._sigs ) + { + FC_ASSERT( item.second, "All signatures must be used", ("item",item) ); + } + } return ptrx; } FC_CAPTURE_AND_RETHROW( (trx) ) } operation_result database::apply_operation(transaction_evaluation_state& eval_state, const operation& op) -{ +{ try { int i_which = op.which(); uint64_t u_which = uint64_t( i_which ); if( i_which < 0 ) @@ -492,16 +636,16 @@ operation_result database::apply_operation(transaction_evaluation_state& eval_st auto result = eval->evaluate( eval_state, op, true ); set_applied_operation_result( op_id, result ); return result; -} +} FC_CAPTURE_AND_RETHROW( (eval_state._sigs) ) } const witness_object& database::validate_block_header( uint32_t skip, const signed_block& next_block )const { FC_ASSERT( _pending_block.previous == next_block.previous, "", ("pending.prev",_pending_block.previous)("next.prev",next_block.previous) ); FC_ASSERT( _pending_block.timestamp <= next_block.timestamp, "", ("_pending_block.timestamp",_pending_block.timestamp)("next",next_block.timestamp)("blocknum",next_block.block_num()) ); const witness_object& witness = next_block.witness(*this); - FC_ASSERT( secret_hash_type::hash(next_block.previous_secret) == witness.next_secret, "", - ("previous_secret", next_block.previous_secret)("next_secret", witness.next_secret)); - if( !(skip&skip_delegate_signature) ) FC_ASSERT( next_block.validate_signee( witness.signing_key(*this).key() ) ); + FC_ASSERT( secret_hash_type::hash( next_block.previous_secret ) == witness.next_secret_hash, "", + ("previous_secret", next_block.previous_secret)("next_secret_hash", witness.next_secret_hash)); + if( !(skip&skip_witness_signature) ) FC_ASSERT( next_block.validate_signee( witness.signing_key ) ); uint32_t slot_num = get_slot_at_time( next_block.timestamp ); FC_ASSERT( slot_num > 0 ); @@ -521,4 +665,10 @@ void database::create_block_summary(const signed_block& next_block) FC_ASSERT( sum.id.instance() == next_block.block_num(), "", ("summary.id",sum.id)("next.block_num",next_block.block_num()) ); } +void database::add_checkpoints( const flat_map& checkpts ) +{ + for( const auto& i : checkpts ) + _checkpoints[i.first] = i.second; +} + } } diff --git a/libraries/chain/db_debug.cpp b/libraries/chain/db_debug.cpp index d7d8f4e0..bfba2a0d 100644 --- a/libraries/chain/db_debug.cpp +++ b/libraries/chain/db_debug.cpp @@ -20,8 +20,7 @@ #include #include -#include -#include +#include #include #include @@ -60,13 +59,6 @@ void database::debug_dump() if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount; total_balances[for_sale.asset_id] += for_sale.amount; } - for( const short_order_object& o : db.get_index_type().indices() ) - { - idump(("short_order")(o)); - auto col = o.get_collateral(); - if( col.asset_id == asset_id_type() ) core_in_orders += col.amount; - total_balances[col.asset_id] += col.amount; - } for( const call_order_object& o : db.get_index_type().indices() ) { idump(("call_order")(o)); @@ -80,7 +72,7 @@ void database::debug_dump() total_balances[asset_obj.id] += asset_obj.dynamic_asset_data_id(db).accumulated_fees; total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool; } - for( const witness_object& witness_obj : db.get_index_type>() ) + for( const witness_object& witness_obj : db.get_index_type().indices() ) { //idump((witness_obj)); total_balances[asset_id_type()] += witness_obj.accumulated_income; @@ -89,7 +81,12 @@ void database::debug_dump() { edump( (total_balances[asset_id_type()].value)(core_asset_data.current_supply.value )); } - // TODO: Add vesting_balance_object to this method + + const auto& vbidx = db.get_index_type>(); + for( const auto& s : vbidx ) + { + idump(("vesting_balance")(s)); + } } } } diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 24b3465f..28e15602 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -38,7 +38,7 @@ const dynamic_global_property_object&database::get_dynamic_global_properties() c return get( dynamic_global_property_id_type() ); } -const fee_schedule_type& database::current_fee_schedule()const +const fee_schedule& database::current_fee_schedule()const { return get_global_properties().parameters.current_fees; } @@ -63,4 +63,14 @@ decltype( chain_parameters::block_interval ) database::block_interval( )const return get_global_properties().parameters.block_interval; } +const node_property_object& database::get_node_properties()const +{ + return _node_property_object; +} + +node_property_object& database::node_properties() +{ + return _node_property_object; +} + } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 57c3cd37..ffd928a3 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -21,70 +21,126 @@ #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 #include #include #include +#include + +#include #include - #include +#include + namespace graphene { namespace chain { +// C++ requires that static class variables declared and initialized +// in headers must also have a definition in a single source file, +// else linker errors will occur [1]. +// +// The purpose of this source file is to collect such definitions in +// a single place. +// +// [1] http://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char + +const uint8_t account_object::space_id; +const uint8_t account_object::type_id; + +const uint8_t asset_object::space_id; +const uint8_t asset_object::type_id; + +const uint8_t block_summary_object::space_id; +const uint8_t block_summary_object::type_id; + +const uint8_t call_order_object::space_id; +const uint8_t call_order_object::type_id; + +const uint8_t committee_member_object::space_id; +const uint8_t committee_member_object::type_id; + +const uint8_t force_settlement_object::space_id; +const uint8_t force_settlement_object::type_id; + +const uint8_t global_property_object::space_id; +const uint8_t global_property_object::type_id; + +const uint8_t limit_order_object::space_id; +const uint8_t limit_order_object::type_id; + +const uint8_t operation_history_object::space_id; +const uint8_t operation_history_object::type_id; + +const uint8_t proposal_object::space_id; +const uint8_t proposal_object::type_id; + +const uint8_t transaction_object::space_id; +const uint8_t transaction_object::type_id; + +const uint8_t vesting_balance_object::space_id; +const uint8_t vesting_balance_object::type_id; + +const uint8_t withdraw_permission_object::space_id; +const uint8_t withdraw_permission_object::type_id; + +const uint8_t witness_object::space_id; +const uint8_t witness_object::type_id; + +const uint8_t witness_schedule_object::space_id; +const uint8_t witness_schedule_object::type_id; + +const uint8_t worker_object::space_id; +const uint8_t worker_object::type_id; + + void database::initialize_evaluators() { _operation_evaluators.resize(255); - register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); - register_evaluator(); + register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); - register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); - register_evaluator(); - register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); - register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); @@ -94,6 +150,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -103,146 +160,130 @@ void database::initialize_indexes() //Protocol object indexes add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index >(); - add_index< primary_index> >(); - add_index< primary_index> >(); - add_index< primary_index> >(); + + auto acnt_index = add_index< primary_index >(); + acnt_index->add_secondary_index(); + acnt_index->add_secondary_index(); + + add_index< primary_index >(); + add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index >(); + + auto prop_index = add_index< primary_index >(); + prop_index->add_secondary_index(); + add_index< primary_index >(); - add_index< primary_index > >(); + //add_index< primary_index >(); + add_index< primary_index> >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index> >(); - add_index< primary_index> >(); - add_index< primary_index> >(); - add_index< primary_index> >(); - add_index< primary_index> >(); - add_index< primary_index< simple_index< witness_schedule_object > > >(); + add_index< primary_index> >(); + add_index< primary_index> >(); + add_index< primary_index> >(); + add_index< primary_index> >(); + add_index< primary_index> >(); + add_index< primary_index> >(); } -void database::init_genesis(const genesis_allocation& initial_allocation) +void database::init_genesis(const genesis_state_type& genesis_state) { try { - _undo_db.disable(); + FC_ASSERT( genesis_state.initial_timestamp != time_point_sec(), "Must initialize genesis timestamp." ); + FC_ASSERT( genesis_state.initial_timestamp.sec_since_epoch() % GRAPHENE_DEFAULT_BLOCK_INTERVAL == 0, + "Genesis timestamp must be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL." ); + FC_ASSERT(genesis_state.initial_witness_candidates.size() > 0, + "Cannot start a chain with zero witnesses."); + FC_ASSERT(genesis_state.initial_active_witnesses <= genesis_state.initial_witness_candidates.size(), + "initial_active_witnesses is larger than the number of candidate witnesses."); - fc::ecc::private_key genesis_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("genesis"))); - const key_object& genesis_key = - create( [&genesis_private_key](key_object& k) { - k.key_data = public_key_type(genesis_private_key.get_public_key()); - }); - const account_statistics_object& genesis_statistics = - create( [&](account_statistics_object& b){ - }); - create( [](account_balance_object& b) { - b.balance = GRAPHENE_INITIAL_SUPPLY; + _undo_db.disable(); + struct auth_inhibitor { + auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) + { db.node_properties().skip_flags |= skip_authority_check; } + ~auth_inhibitor() + { db.node_properties().skip_flags = old_flags; } + private: + database& db; + uint32_t old_flags; + } inhibitor(*this); + + transaction_evaluation_state genesis_eval_state(this); + + // Create blockchain accounts + fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); + create([](account_balance_object& b) { + b.balance = GRAPHENE_MAX_SHARE_SUPPLY; }); - const account_object& genesis_account = + const account_object& committee_account = create( [&](account_object& n) { n.membership_expiration_date = time_point_sec::maximum(); n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - n.name = "genesis"; - n.owner.add_authority(genesis_key.get_id(), 1); n.owner.weight_threshold = 1; - n.active = n.owner; - n.memo_key = genesis_key.id; - n.statistics = genesis_statistics.id; - }); - - vector init_delegates; - vector init_witnesses; - flat_set init_witness_set; - - auto delegates_and_witnesses = std::max(GRAPHENE_MIN_WITNESS_COUNT, GRAPHENE_MIN_DELEGATE_COUNT); - for( int i = 0; i < delegates_and_witnesses; ++i ) - { - const account_statistics_object& stats_obj = - create( [&](account_statistics_object&){ - }); - const account_object& delegate_account = - create( [&](account_object& a) { - a.active = a.owner = genesis_account.owner; - a.referrer = account_id_type(i); - a.registrar = account_id_type(i); - a.lifetime_referrer = account_id_type(i); - a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.membership_expiration_date = fc::time_point_sec::maximum(); - a.name = string("init") + fc::to_string(i); - a.statistics = stats_obj.id; - }); - const delegate_object& init_delegate = create( [&](delegate_object& d) { - d.delegate_account = delegate_account.id; - d.vote_id = i * 2; - }); - init_delegates.push_back(init_delegate.id); - - const witness_object& init_witness = create( [&](witness_object& d) { - d.witness_account = delegate_account.id; - d.vote_id = i * 2 + 1; - secret_hash_type::encoder enc; - fc::raw::pack( enc, genesis_private_key ); - fc::raw::pack( enc, d.last_secret ); - d.next_secret = secret_hash_type::hash(enc.result()); - }); - init_witnesses.push_back(init_witness.id); - init_witness_set.insert(init_witness.id); - - } - create( [&](block_summary_object& p) { - }); - - const witness_schedule_object& wso = - create( [&]( witness_schedule_object& _wso ) - { - memset( _wso.rng_seed.begin(), 0, _wso.rng_seed.size() ); - - witness_scheduler_rng rng( _wso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV ); - - _wso.scheduler = witness_scheduler(); - _wso.scheduler._min_token_count = init_witnesses.size() / 2; - _wso.scheduler.update( init_witness_set ); - - for( size_t i=0; i( [&](global_property_object& p) { - p.active_delegates = init_delegates; - for( const witness_id_type& wit : init_witnesses ) - p.active_witnesses.insert( wit ); - p.next_available_vote_id = delegates_and_witnesses * 2; - p.chain_id = fc::digest(initial_allocation); - }); - (void)properties; - - create( [&](dynamic_global_property_object& p) { - p.time = fc::time_point_sec( GRAPHENE_GENESIS_TIMESTAMP ); + n.active.weight_threshold = 1; + n.name = "committee-account"; + n.statistics = create( [&](account_statistics_object& b){}).id; }); + FC_ASSERT(committee_account.get_id() == GRAPHENE_COMMITTEE_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "witness-account"; + a.statistics = create([](account_statistics_object&){}).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_WITNESS_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_WITNESS_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "relaxed-committee-account"; + a.statistics = create([](account_statistics_object&){}).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "null-account"; + a.statistics = create([](account_statistics_object&){}).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = 0; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; + }).get_id() == GRAPHENE_NULL_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "temp-account"; + a.statistics = create([](account_statistics_object&){}).id; + a.owner.weight_threshold = 0; + a.active.weight_threshold = 0; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_TEMP_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_TEMP_ACCOUNT); + // Create core asset const asset_dynamic_data_object& dyn_asset = - create( [&]( asset_dynamic_data_object& a ) { - a.current_supply = GRAPHENE_INITIAL_SUPPLY; + create([&](asset_dynamic_data_object& a) { + a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY; }); - const asset_object& core_asset = create( [&]( asset_object& a ) { a.symbol = GRAPHENE_SYMBOL; - a.options.max_supply = GRAPHENE_INITIAL_SUPPLY; + a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; a.options.flags = 0; a.options.issuer_permissions = 0; - a.issuer = genesis_account.id; + a.issuer = committee_account.id; a.options.core_exchange_rate.base.amount = 1; a.options.core_exchange_rate.base.asset_id = 0; a.options.core_exchange_rate.quote.amount = 1; @@ -253,82 +294,248 @@ void database::init_genesis(const genesis_allocation& initial_allocation) assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) ); (void)core_asset; - if( !initial_allocation.empty() ) + // Create global properties + create([&](global_property_object& p) { + p.chain_id = fc::digest(genesis_state); + p.parameters = genesis_state.initial_parameters; + // Set fees to zero initially, so that genesis initialization needs not pay them + // We'll fix it at the end of the function + p.parameters.current_fees->zero_all_fees(); + + }); + create([&](dynamic_global_property_object& p) { + p.time = genesis_state.initial_timestamp; + p.witness_budget = 0; + }); + create([&](block_summary_object&) {}); + + // Create initial accounts + for( const auto& account : genesis_state.initial_accounts ) { - share_type total_allocation = 0; - for( const auto& handout : initial_allocation ) - total_allocation += handout.second; - - auto mangle_to_name = [](const fc::static_variant& key) { - string addr = string(key.which() == std::decay::type::tag
::value? key.get
() - : key.get()); - string result = "bts"; - string key_string = string(addr).substr(sizeof(GRAPHENE_ADDRESS_PREFIX)-1); - for( char c : key_string ) - { - if( isupper(c) ) - result += string("-") + char(tolower(c)); - else - result += c; - } - return result; - }; - - fc::time_point start_time = fc::time_point::now(); - - for( const auto& handout : initial_allocation ) + account_create_operation cop; + cop.name = account.name; + cop.registrar = GRAPHENE_TEMP_ACCOUNT; + cop.owner = authority(1, account.owner_key, 1); + if( account.active_key == public_key_type() ) { - asset amount(handout.second); - amount.amount = ((fc::uint128(amount.amount.value) * GRAPHENE_INITIAL_SUPPLY)/total_allocation.value).to_uint64(); - if( amount.amount == 0 ) - { - wlog("Skipping zero allocation to ${k}", ("k", handout.first)); - continue; - } - - signed_transaction trx; - trx.operations.emplace_back(key_create_operation({asset(), genesis_account.id, handout.first})); - relative_key_id_type key_id(0); - authority account_authority(1, key_id, 1); - account_create_operation cop; - cop.name = mangle_to_name(handout.first); - cop.registrar = account_id_type(1); - cop.active = account_authority; - cop.owner = account_authority; - cop.memo_key = key_id; - trx.operations.push_back(cop); - trx.validate(); - auto ptrx = apply_transaction(trx, ~0); - trx = signed_transaction(); - account_id_type account_id(ptrx.operation_results.back().get()); - trx.operations.emplace_back(transfer_operation({ asset(), - genesis_account.id, - account_id, - amount, - memo_data()//vector() - })); - trx.validate(); - apply_transaction(trx, ~0); + cop.active = cop.owner; + cop.options.memo_key = account.owner_key; } - - asset leftovers = get_balance(account_id_type(), asset_id_type()); - if( leftovers.amount > 0 ) + else { - modify(*get_index_type().indices().get().find(boost::make_tuple(account_id_type(), asset_id_type())), - [](account_balance_object& b) { - b.adjust_balance(-b.get_balance()); - }); - modify(core_asset.dynamic_asset_data_id(*this), [&leftovers](asset_dynamic_data_object& d) { - d.accumulated_fees += leftovers.amount; - }); + cop.active = authority(1, account.active_key, 1); + cop.options.memo_key = account.active_key; } + account_id_type account_id(apply_operation(genesis_eval_state, cop).get()); - fc::microseconds duration = fc::time_point::now() - start_time; - ilog("Finished allocating to ${n} accounts in ${t} milliseconds.", - ("n", initial_allocation.size())("t", duration.count() / 1000)); + if( account.is_lifetime_member ) + { + account_upgrade_operation op; + op.account_to_upgrade = account_id; + op.upgrade_to_lifetime_member = true; + apply_operation(genesis_eval_state, op); + } } + // Helper function to get account ID by name + const auto& accounts_by_name = get_index_type().indices().get(); + auto get_account_id = [&accounts_by_name](const string& name) { + auto itr = accounts_by_name.find(name); + FC_ASSERT(itr != accounts_by_name.end(), + "Unable to find account '${acct}'. Did you forget to add a record for it to initial_accounts?", + ("acct", name)); + return itr->get_id(); + }; + + // Helper function to get asset ID by symbol + const auto& assets_by_symbol = get_index_type().indices().get(); + auto get_asset_id = [&assets_by_symbol](const string& symbol) { + auto itr = assets_by_symbol.find(symbol); + FC_ASSERT(itr != assets_by_symbol.end(), + "Unable to find asset '${sym}'. Did you forget to add a record for it to initial_assets?", + ("sym", symbol)); + return itr->get_id(); + }; + + // Create initial assets + for( const genesis_state_type::initial_asset_type& asset : genesis_state.initial_assets ) + { + asset_dynamic_data_id_type dynamic_data_id; + optional bitasset_data_id; + if( asset.bitasset_opts.valid() ) + { + share_type total_allocated; + asset_id_type new_asset_id = get_index_type().get_next_id(); + asset_id_type collateral_asset_id = get_asset_id(asset.bitasset_opts->backing_asset_symbol); + + int collateral_holder_number = 0; + for( const auto& collateral_rec : asset.bitasset_opts->collateral_records ) + { + account_create_operation cop; + cop.name = asset.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number); + boost::algorithm::to_lower(cop.name); + cop.registrar = GRAPHENE_TEMP_ACCOUNT; + cop.owner = authority(1, collateral_rec.owner, 1); + cop.active = cop.owner; + account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get(); + + create([&](call_order_object& c) { + c.borrower = owner_account_id; + c.collateral = collateral_rec.collateral; + c.debt = collateral_rec.debt; + c.call_price = price::call_price(chain::asset(c.debt, new_asset_id), + chain::asset(c.collateral, collateral_asset_id), + asset.bitasset_opts->maintenance_collateral_ratio); + }); + + total_allocated += collateral_rec.debt; + } + + bitasset_data_id = create([&](asset_bitasset_data_object& b) { + b.options.feed_lifetime_sec = asset.bitasset_opts->feed_lifetime_sec; + b.options.minimum_feeds = asset.bitasset_opts->minimum_feeds; + b.options.force_settlement_delay_sec = asset.bitasset_opts->force_settlement_delay_sec; + b.options.force_settlement_offset_percent = asset.bitasset_opts->force_settlement_offset_percent; + b.options.maximum_force_settlement_volume = asset.bitasset_opts->maximum_force_settlement_volume; + b.options.short_backing_asset = get_asset_id(asset.bitasset_opts->backing_asset_symbol); + }).id; + + dynamic_data_id = create([&](asset_dynamic_data_object& d) { + d.current_supply = total_allocated; + d.accumulated_fees = asset.initial_accumulated_fees; + }).id; + } else + dynamic_data_id = create([&](asset_dynamic_data_object& d) { + d.accumulated_fees = asset.initial_accumulated_fees; + }).id; + + create([&](asset_object& a) { + a.symbol = asset.symbol; + a.options.description = asset.description; + a.precision = asset.precision; + a.issuer = get_account_id(asset.issuer_name); + a.options.max_supply = asset.max_supply; + a.options.market_fee_percent = asset.market_fee_percent; + a.options.max_market_fee = asset.max_market_fee; + a.options.issuer_permissions = asset.issuer_permissions; + a.options.flags = asset.flags; + + a.dynamic_asset_data_id = dynamic_data_id; + a.bitasset_data_id = bitasset_data_id; + }); + } + + // Create initial balances + share_type total_allocation; + for( const auto& handout : genesis_state.initial_balances ) + { + create([&handout,&assets_by_symbol,total_allocation](balance_object& b) { + b.balance = asset(handout.amount, assets_by_symbol.find(handout.asset_symbol)->get_id()); + b.owner = handout.owner; + }); + total_allocation += handout.amount; + } + + // Create initial vesting balances + for( const genesis_state_type::initial_vesting_balance_type& vest : genesis_state.initial_vesting_balances ) + { + create([&](balance_object& b) { + b.owner = vest.owner; + b.balance = asset(vest.amount, assets_by_symbol.find(vest.asset_symbol)->get_id()); + + linear_vesting_policy policy; + policy.begin_timestamp = vest.begin_timestamp; + policy.vesting_cliff_seconds = 0; + policy.vesting_duration_seconds = vest.vesting_duration_seconds; + policy.begin_balance = vest.begin_balance; + + b.vesting_policy = std::move(policy); + }); + total_allocation += vest.amount; + } + + // Set current supply based on allocations, if they happened + if( total_allocation > 0 ) + { + modify(dyn_asset, [total_allocation](asset_dynamic_data_object& d) { + d.current_supply = total_allocation; + }); + adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, -get_balance(GRAPHENE_COMMITTEE_ACCOUNT,{})); + } + + // Create initial witnesses + std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(), + [&](const genesis_state_type::initial_witness_type& witness) { + witness_create_operation op; + op.witness_account = get_account_id(witness.owner_name); + op.block_signing_key = witness.block_signing_key; + op.initial_secret = secret_hash_type::hash( secret_hash_type() ); + apply_operation(genesis_eval_state, op); + }); + + // Create initial committee members + std::for_each(genesis_state.initial_committee_candidates.begin(), genesis_state.initial_committee_candidates.end(), + [&](const genesis_state_type::initial_committee_member_type& member) { + committee_member_create_operation op; + op.committee_member_account = get_account_id(member.owner_name); + apply_operation(genesis_eval_state, op); + }); + + // Create initial workers + std::for_each(genesis_state.initial_worker_candidates.begin(), genesis_state.initial_worker_candidates.end(), + [&](const genesis_state_type::initial_worker_type& worker) + { + worker_create_operation op; + op.owner = get_account_id(worker.owner_name); + op.work_begin_date = genesis_state.initial_timestamp; + op.work_end_date = time_point_sec::maximum(); + op.daily_pay = worker.daily_pay; + op.name = "Genesis-Worker-" + worker.owner_name; + op.initializer = vesting_balance_worker_initializer{uint16_t(0)}; + + apply_operation(genesis_eval_state, std::move(op)); + }); + + // Set active witnesses + modify(get_global_properties(), [&](global_property_object& p) { + for( int i = 0; i < genesis_state.initial_active_witnesses; ++i ) + { + p.active_witnesses.insert(i); + p.witness_accounts.insert(get(witness_id_type(i)).witness_account); + } + }); + + // Initialize witness schedule +#ifndef NDEBUG + const witness_schedule_object& wso = +#endif + create([&](witness_schedule_object& _wso) + { + memset(_wso.rng_seed.begin(), 0, _wso.rng_seed.size()); + + witness_scheduler_rng rng(_wso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_witnesses = get_global_properties().active_witnesses; + + _wso.scheduler = witness_scheduler(); + _wso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1); + _wso.scheduler.update(init_witnesses); + + for( size_t i=0; i #include -#include +#include #include #include #include #include -#include +#include #include namespace graphene { namespace chain { -template -vector> database::sort_votable_objects(size_t count) const +template +vector> database::sort_votable_objects(size_t count) const { - const auto& all_objects = dynamic_cast&>(get_index()); + using ObjectType = typename Index::object_type; + const auto& all_objects = get_index_type().indices(); count = std::min(count, all_objects.size()); vector> refs; refs.reserve(all_objects.size()); std::transform(all_objects.begin(), all_objects.end(), std::back_inserter(refs), [](const ObjectType& o) { return std::cref(o); }); - std::partial_sort( refs.begin(), refs.begin() + count, refs.end(), - [this]( const ObjectType& a, const ObjectType& b )->bool { - return _vote_tally_buffer[a.vote_id] > _vote_tally_buffer[b.vote_id]; + std::partial_sort(refs.begin(), refs.begin() + count, refs.end(), + [this](const ObjectType& a, const ObjectType& b)->bool { + share_type oa_vote = _vote_tally_buffer[a.vote_id]; + share_type ob_vote = _vote_tally_buffer[b.vote_id]; + if( oa_vote != ob_vote ) + return oa_vote > ob_vote; + return a.vote_id < b.vote_id; }); refs.resize(count, refs.front()); return refs; } +template +void database::perform_account_maintenance(std::tuple helpers) +{ + const auto& idx = get_index_type().indices(); + for( const account_object& a : idx ) + detail::for_each(helpers, a, detail::gen_seq()); +} + +/// @brief A visitor for @ref worker_type which calls pay_worker on the worker within +struct worker_pay_visitor +{ + private: + share_type pay; + database& db; + + public: + worker_pay_visitor(share_type pay, database& db) + : pay(pay), db(db) {} + + typedef void result_type; + template + void operator()(W& worker)const + { + worker.pay_worker(pay, db); + } +}; + void database::pay_workers( share_type& budget ) { - ilog("Processing payroll! Available budget is ${b}", ("b", budget)); +// ilog("Processing payroll! Available budget is ${b}", ("b", budget)); vector> active_workers; get_index_type().inspect_all_objects([this, &active_workers](const object& o) { const worker_object& w = static_cast(o); @@ -61,8 +93,14 @@ void database::pay_workers( share_type& budget ) active_workers.emplace_back(w); }); + // worker with more votes is preferred + // if two workers exactly tie for votes, worker with lower ID is preferred std::sort(active_workers.begin(), active_workers.end(), [this](const worker_object& wa, const worker_object& wb) { - return wa.approving_stake(_vote_tally_buffer) > wb.approving_stake(_vote_tally_buffer); + share_type wa_vote = wa.approving_stake(_vote_tally_buffer); + share_type wb_vote = wb.approving_stake(_vote_tally_buffer); + if( wa_vote != wb_vote ) + return wa_vote > wb_vote; + return wa.id < wb.id; }); for( int i = 0; i < active_workers.size() && budget > 0; ++i ) @@ -78,7 +116,7 @@ void database::pay_workers( share_type& budget ) } share_type actual_pay = std::min(budget, requested_pay); - ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay)); + //ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay)); modify(active_worker, [&](worker_object& w) { w.worker.visit(worker_pay_visitor(actual_pay, *this)); }); @@ -92,24 +130,53 @@ void database::update_active_witnesses() assert( _witness_count_histogram_buffer.size() > 0 ); share_type stake_target = _total_voting_stake / 2; share_type stake_tally = _witness_count_histogram_buffer[0]; - int witness_count = 0; - while( (size_t(witness_count) < _witness_count_histogram_buffer.size()) - && (stake_tally <= stake_target) ) - stake_tally += _witness_count_histogram_buffer[++witness_count]; + size_t witness_count = 0; + if( stake_target > 0 ) + while( (witness_count < _witness_count_histogram_buffer.size() - 1) + && (stake_tally <= stake_target) ) + stake_tally += _witness_count_histogram_buffer[++witness_count]; - auto wits = sort_votable_objects(std::max(witness_count*2+1, GRAPHENE_MIN_WITNESS_COUNT)); + auto wits = sort_votable_objects(std::max(witness_count*2+1, (size_t)GRAPHENE_MIN_WITNESS_COUNT)); const global_property_object& gpo = get_global_properties(); - modify( gpo, [&]( global_property_object& gp ){ + // Update witness authority + modify( get(GRAPHENE_WITNESS_ACCOUNT), [&]( account_object& a ) { + uint64_t total_votes = 0; + map weights; + a.active.weight_threshold = 0; + a.active.clear(); + + for( const witness_object& wit : wits ) + { + weights.emplace(wit.witness_account, _vote_tally_buffer[wit.vote_id]); + total_votes += _vote_tally_buffer[wit.vote_id]; + } + + // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // then I want to keep the most significant 16 bits of what's left. + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + for( const auto& weight : weights ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); + a.active.account_auths[weight.first] += votes; + a.active.weight_threshold += votes; + } + + a.active.weight_threshold /= 2; + a.active.weight_threshold += 1; + }); + + modify(gpo, [&]( global_property_object& gp ){ gp.active_witnesses.clear(); - gp.active_witnesses.reserve( wits.size() ); + gp.active_witnesses.reserve(wits.size()); std::transform(wits.begin(), wits.end(), std::inserter(gp.active_witnesses, gp.active_witnesses.end()), [](const witness_object& w) { return w.id; }); gp.witness_accounts.clear(); - gp.witness_accounts.reserve( wits.size() ); + gp.witness_accounts.reserve(wits.size()); std::transform(wits.begin(), wits.end(), std::inserter(gp.witness_accounts, gp.witness_accounts.end()), [](const witness_object& w) { @@ -118,36 +185,37 @@ void database::update_active_witnesses() }); const witness_schedule_object& wso = witness_schedule_id_type()(*this); - modify( wso, [&]( witness_schedule_object& _wso ) + modify(wso, [&](witness_schedule_object& _wso) { - _wso.scheduler.update( gpo.active_witnesses ); - } ); - + _wso.scheduler.update(gpo.active_witnesses); + }); } FC_CAPTURE_AND_RETHROW() } -void database::update_active_delegates() +void database::update_active_committee_members() { try { assert( _committee_count_histogram_buffer.size() > 0 ); uint64_t stake_target = _total_voting_stake / 2; uint64_t stake_tally = _committee_count_histogram_buffer[0]; - int delegate_count = 0; - while( (size_t(delegate_count) < _committee_count_histogram_buffer.size()) - && (stake_tally <= stake_target) ) - stake_tally += _committee_count_histogram_buffer[++delegate_count]; + size_t committee_member_count = 0; + if( stake_target > 0 ) + while( (committee_member_count < _committee_count_histogram_buffer.size() - 1) + && (stake_tally <= stake_target) ) + stake_tally += _committee_count_histogram_buffer[++committee_member_count]; - auto delegates = sort_votable_objects(std::max(delegate_count*2+1, GRAPHENE_MIN_DELEGATE_COUNT)); + auto committee_members = sort_votable_objects(std::max(committee_member_count*2+1, (size_t)GRAPHENE_MIN_COMMITTEE_MEMBER_COUNT)); - // Update genesis authorities - if( !delegates.empty() ) - modify( get(account_id_type()), [&]( account_object& a ) { + // Update committee authorities + if( !committee_members.empty() ) + { + modify(get(GRAPHENE_COMMITTEE_ACCOUNT), [&](account_object& a) { uint64_t total_votes = 0; map weights; - a.owner.weight_threshold = 0; - a.owner.auths.clear(); + a.active.weight_threshold = 0; + a.active.clear(); - for( const delegate_object& del : delegates ) + for( const committee_member_object& del : committee_members ) { - weights.emplace(del.delegate_account, _vote_tally_buffer[del.vote_id]); + weights.emplace(del.committee_member_account, _vote_tally_buffer[del.vote_id]); total_votes += _vote_tally_buffer[del.vote_id]; } @@ -158,19 +226,22 @@ void database::update_active_delegates() { // Ensure that everyone has at least one vote. Zero weights aren't allowed. uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); - a.owner.auths[weight.first] += votes; - a.owner.weight_threshold += votes; + a.active.account_auths[weight.first] += votes; + a.active.weight_threshold += votes; } - a.owner.weight_threshold /= 2; - a.owner.weight_threshold += 1; - a.active = a.owner; + a.active.weight_threshold /= 2; + a.active.weight_threshold += 1; }); - modify( get_global_properties(), [&]( global_property_object& gp ) { - gp.active_delegates.clear(); - std::transform(delegates.begin(), delegates.end(), - std::back_inserter(gp.active_delegates), - [](const delegate_object& d) { return d.id; }); + modify(get(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT), [&](account_object& a) { + a.active = get(GRAPHENE_COMMITTEE_ACCOUNT).active; + }); + } + modify(get_global_properties(), [&](global_property_object& gp) { + gp.active_committee_members.clear(); + std::transform(committee_members.begin(), committee_members.end(), + std::inserter(gp.active_committee_members, gp.active_committee_members.begin()), + [](const committee_member_object& d) { return d.id; }); }); } FC_CAPTURE_AND_RETHROW() } @@ -186,13 +257,16 @@ share_type database::get_max_budget( fc::time_point_sec now )const int64_t dt = (now - dpo.last_budget_time).to_seconds(); - // We'll consider accumulated_fees to be burned at the BEGINNING + // We'll consider accumulated_fees to be reserved at the BEGINNING // of the maintenance interval. However, for speed we only // call modify() on the asset_dynamic_data_object once at the // end of the maintenance interval. Thus the accumulated_fees // are available for the budget at this point, but not included - // in core.burned(). - share_type reserve = core.burned(*this) + core_dd.accumulated_fees; + // in core.reserved(). + share_type reserve = core.reserved(*this) + core_dd.accumulated_fees; + // Similarly, we consider leftover witness_budget to be burned + // at the BEGINNING of the maintenance interval. + reserve += dpo.witness_budget; fc::uint128_t budget_u128 = reserve.value; budget_u128 *= uint64_t(dt); @@ -263,16 +337,23 @@ void database::process_budget() pay_workers(leftover_worker_funds); available_funds += leftover_worker_funds; + share_type unused_prev_witness_budget = dpo.witness_budget; modify(core, [&]( asset_dynamic_data_object& _core ) { - _core.current_supply = (_core.current_supply + witness_budget + - worker_budget - leftover_worker_funds - - _core.accumulated_fees); + _core.current_supply = (_core.current_supply + + witness_budget + + worker_budget + - leftover_worker_funds + - _core.accumulated_fees + - unused_prev_witness_budget + ); _core.accumulated_fees = 0; }); modify(dpo, [&]( dynamic_global_property_object& _dpo ) { - // Should this be +=? + // Since initial witness_budget was rolled into + // available_funds, we replace it with witness_budget + // instead of adding it. _dpo.witness_budget = witness_budget; _dpo.last_budget_time = now; }); @@ -307,25 +388,26 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // Usually they're the same, but if the stake account has specified a voting_account, that account is the one // specifying the opinions. const account_object& opinion_account = - (stake_account.voting_account == account_id_type())? stake_account - : d.get(stake_account.voting_account); + (stake_account.options.voting_account == + account_id_type())? stake_account + : d.get(stake_account.options.voting_account); const auto& stats = stake_account.statistics(d); uint64_t voting_stake = stats.total_core_in_orders.value + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0) + d.get_balance(stake_account.get_id(), asset_id_type()).amount.value; - for( vote_id_type id : opinion_account.votes ) + for( vote_id_type id : opinion_account.options.votes ) { uint32_t offset = id.instance(); // if they somehow managed to specify an illegal offset, ignore it. if( offset < d._vote_tally_buffer.size() ) - d._vote_tally_buffer[ offset ] += voting_stake; + d._vote_tally_buffer[offset] += voting_stake; } - if( opinion_account.num_witness <= props.parameters.maximum_witness_count ) + if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count ) { - uint16_t offset = std::min(size_t(opinion_account.num_witness/2), + uint16_t offset = std::min(size_t(opinion_account.options.num_witness/2), d._witness_count_histogram_buffer.size() - 1); // votes for a number greater than maximum_witness_count // are turned into votes for maximum_witness_count. @@ -333,17 +415,17 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // in particular, this takes care of the case where a // member was voting for a high number, then the // parameter was lowered. - d._witness_count_histogram_buffer[ offset ] += voting_stake; + d._witness_count_histogram_buffer[offset] += voting_stake; } - if( opinion_account.num_committee <= props.parameters.maximum_committee_count ) + if( opinion_account.options.num_committee <= props.parameters.maximum_committee_count ) { - uint16_t offset = std::min(size_t(opinion_account.num_committee/2), + uint16_t offset = std::min(size_t(opinion_account.options.num_committee/2), d._committee_count_histogram_buffer.size() - 1); // votes for a number greater than maximum_committee_count // are turned into votes for maximum_committee_count. // // same rationale as for witnesses - d._committee_count_histogram_buffer[ offset ] += voting_stake; + d._committee_count_histogram_buffer[offset] += voting_stake; } d._total_voting_stake += voting_stake; @@ -357,77 +439,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g process_fees_helper(database& d, const global_property_object& gpo) : d(d), props(gpo) {} - share_type cut_fee(share_type a, uint16_t p)const - { - if( a == 0 || p == 0 ) - return 0; - if( p == GRAPHENE_100_PERCENT ) - return a; - - fc::uint128 r(a.value); - r *= p; - r /= GRAPHENE_100_PERCENT; - return r.to_uint64(); - } - - void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting) - { - share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage); - assert( network_cut <= core_fee_total ); - share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); - share_type accumulated = network_cut - burned; - assert( accumulated + burned == network_cut ); - share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage); - share_type referral = core_fee_total - network_cut - lifetime_cut; - - d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { - d.accumulated_fees += network_cut; - }); - - // Potential optimization: Skip some of this math and object lookups by special casing on the account type. - // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to - // it directly. - share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage); - share_type registrar_cut = referral - referrer_cut; - - d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting); - d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting); - d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting); - - assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total ); - } - void operator()(const account_object& a) { - const account_statistics_object& stats = a.statistics(d); - - if( stats.pending_fees > 0 ) - { - share_type vesting_fee_subtotal(stats.pending_fees); - share_type vested_fee_subtotal(stats.pending_vested_fees); - share_type vesting_cashback, vested_cashback; - - if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && - a.is_member(d.head_block_time()) ) - { - auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters); - vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate); - vesting_fee_subtotal -= vesting_cashback; - - vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate); - vested_fee_subtotal -= vested_cashback; - } - - pay_out_fees(a, vesting_fee_subtotal, true); - d.deposit_cashback(a, vesting_cashback, true); - pay_out_fees(a, vested_fee_subtotal, false); - d.deposit_cashback(a, vested_cashback, false); - - d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) { - s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal; - s.pending_fees = 0; - s.pending_vested_fees = 0; - }); - } + a.statistics(d).process_fees(a, d); } } fee_helper(*this, gpo); @@ -444,13 +457,23 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g c(_vote_tally_buffer); update_active_witnesses(); - update_active_delegates(); + update_active_committee_members(); - if( gpo.pending_parameters ) - modify(gpo, [](global_property_object& p) { + modify(gpo, [this](global_property_object& p) { + // Remove scaling of account registration fee + /* + /// TODO reimplement this + const auto& dgpo = get_dynamic_global_properties(); + p.parameters.current_fees.account_create_fee >>= p.parameters.account_fee_scale_bitshifts * + (dgpo.accounts_registered_this_interval / p.parameters.accounts_per_fee_scale); + */ + + if( p.pending_parameters ) + { p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); - }); + } + }); auto new_block_interval = global_props.parameters.block_interval; @@ -462,7 +485,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( !r ) { _pending_block.timestamp -= r; - assert( (_pending_block.timestamp.sec_since_epoch() % new_block_interval) == 0 ); + assert( (_pending_block.timestamp.sec_since_epoch() % new_block_interval) == 0 ); } auto next_maintenance_time = get(dynamic_global_property_id_type()).next_maintenance_time; @@ -474,12 +497,15 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g next_maintenance_time = time_point_sec() + (((next_block.timestamp.sec_since_epoch() / maintenance_interval) + 1) * maintenance_interval); else - next_maintenance_time += maintenance_interval; - assert( next_maintenance_time > next_block.timestamp ); + // It's possible we have missed blocks for at least a maintenance interval. + // In this case, we'll need to bump the next maintenance time more than once. + do next_maintenance_time += maintenance_interval; + while( next_maintenance_time < head_block_time() ); } modify(get_dynamic_global_properties(), [next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; + d.accounts_registered_this_interval = 0; }); // Reset all BitAsset force settlement volumes to zero diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index de45ecda..3dab21c3 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -19,6 +19,9 @@ #include #include +#include + +#include namespace graphene { namespace chain { @@ -33,44 +36,28 @@ database::~database(){ _pending_block_session->commit(); } -void database::open( const fc::path& data_dir, const genesis_allocation& initial_allocation ) -{ try { - ilog("Open database in ${d}", ("d", data_dir)); - object_database::open( data_dir ); - - _block_id_to_block.open( data_dir / "database" / "block_num_to_block" ); - - if( !find(global_property_id_type()) ) - init_genesis(initial_allocation); - - _pending_block.previous = head_block_id(); - _pending_block.timestamp = head_block_time(); - - auto last_block_itr = _block_id_to_block.last(); - if( last_block_itr.valid() ) - _fork_db.start_block( last_block_itr.value() ); - -} FC_CAPTURE_AND_RETHROW( (data_dir) ) } - -void database::reindex(fc::path data_dir, const genesis_allocation& initial_allocation) +void database::reindex(fc::path data_dir, const genesis_state_type& initial_allocation) { try { wipe(data_dir, false); - open(data_dir, initial_allocation); + open(data_dir, [&initial_allocation]{return initial_allocation;}); auto start = fc::time_point::now(); - auto itr = _block_id_to_block.begin(); + auto last_block = _block_id_to_block.last(); + if( !last_block ) return; + + const auto last_block_num = last_block->block_num(); + // TODO: disable undo tracking durring reindex, this currently causes crashes in the benchmark test //_undo_db.disable(); - while( itr.valid() ) + for( uint32_t i = 1; i <= last_block_num; ++i ) { - apply_block( itr.value(), skip_delegate_signature | + apply_block(*_block_id_to_block.fetch_by_number(i), skip_witness_signature | skip_transaction_signatures | skip_undo_block | skip_undo_transaction | skip_transaction_dupe_check | skip_tapos_check | - skip_authority_check ); - ++itr; + skip_authority_check); } //_undo_db.enable(); auto end = fc::time_point::now(); @@ -93,6 +80,7 @@ void database::close(uint32_t blocks_to_rewind) for(uint32_t i = 0; i < blocks_to_rewind && head_block_num() > 0; ++i) pop_block(); + object_database::flush(); object_database::close(); if( _block_id_to_block.is_open() ) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 22fb778d..8b20c3c9 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -20,31 +20,31 @@ #include #include -#include -#include +#include #include namespace graphene { namespace chain { /** - for each short order, fill it at settlement price and place funds received into a total - calculate the USD->CORE price and convert all USD balances to CORE at that price and subtract CORE from total - - any fees accumulated by the issuer in the bitasset are forfeit / not redeemed - - cancel all open orders with bitasset in it - - any bitassets that use this bitasset as collateral are immediately settled at their feed price - - convert all balances in bitasset to CORE and subtract from total - - any prediction markets with usd as the backing get converted to CORE as the backing - any CORE left over due to rounding errors is paid to accumulated fees + * All margin positions are force closed at the swan price + * Collateral received goes into a force-settlement fund + * No new margin positions can be created for this asset + * No more price feed updates + * Force settlement happens without delay at the swan price, deducting from force-settlement fund + * No more asset updates may be issued. */ void database::globally_settle_asset( const asset_object& mia, const price& settlement_price ) { try { + /* elog( "BLACK SWAN!" ); debug_dump(); - edump( (mia.symbol)(settlement_price) ); + */ const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); + FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); + const asset_object& backing_asset = bitasset.options.short_backing_asset(*this); asset collateral_gathered = backing_asset.amount(0); @@ -54,92 +54,37 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); - auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) ); - auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); - while( call_itr != call_end ) - { - auto pays = call_itr->get_debt() * settlement_price; - wdump( (call_itr->get_debt() ) ); - collateral_gathered += pays; - const auto& order = *call_itr; - ++call_itr; - FC_ASSERT( fill_order( order, pays, order.get_debt() ) ); - } - - const limit_order_index& limit_index = get_index_type(); - const auto& limit_price_index = limit_index.indices().get(); - - // cancel all orders selling the market issued asset - auto limit_itr = limit_price_index.lower_bound(price::max(mia.id, bitasset.options.short_backing_asset)); - auto limit_end = limit_price_index.upper_bound(~bitasset.current_feed.call_limit); - while( limit_itr != limit_end ) - { - const auto& order = *limit_itr; - ilog( "CANCEL LIMIT ORDER" ); - idump((order)); - ++limit_itr; - cancel_order( order ); - } - - limit_itr = limit_price_index.begin(); - while( limit_itr != limit_end ) - { - if( limit_itr->amount_for_sale().asset_id == mia.id ) - { - const auto& order = *limit_itr; - ilog( "CANCEL_AGAIN" ); - edump((order)); - ++limit_itr; - cancel_order( order ); - } - } - - limit_itr = limit_price_index.begin(); - while( limit_itr != limit_end ) + // cancel all call orders and accumulate it into collateral_gathered + auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) ); + auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); + while( call_itr != call_end ) { - if( limit_itr->amount_for_sale().asset_id == mia.id ) - { - const auto& order = *limit_itr; - edump((order)); - ++limit_itr; - cancel_order( order ); - } + auto pays = call_itr->get_debt() * settlement_price; + + if( pays > call_itr->get_collateral() ) + pays = call_itr->get_collateral(); + + collateral_gathered += pays; + const auto& order = *call_itr; + ++call_itr; + FC_ASSERT( fill_order( order, pays, order.get_debt() ) ); } - // settle all balances - asset total_mia_settled = mia.amount(0); + modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + assert( collateral_gathered.asset_id == settlement_price.quote.asset_id ); + obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; //settlement_price; + obj.settlement_fund = collateral_gathered.amount; + }); - const auto& index = get_index_type().indices().get(); - auto range = index.equal_range(mia.get_id()); - for( auto itr = range.first; itr != range.second; ++itr ) - { - auto mia_balance = itr->get_balance(); - if( mia_balance.amount > 0 ) - { - adjust_balance(itr->owner, -mia_balance); - auto settled_amount = mia_balance * settlement_price; - idump( (mia_balance)(settled_amount)(settlement_price) ); - adjust_balance(itr->owner, settled_amount); - total_mia_settled += mia_balance; - collateral_gathered -= settled_amount; - } - } + /// After all margin positions are closed, the current supply will be reported as 0, but + /// that is a lie, the supply didn't change. We need to capture the current supply before + /// filling all call orders and then restore it afterward. Then in the force settlement + /// evaluator reduce the supply + modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + obj.current_supply = original_mia_supply; + }); - // TODO: convert payments held in escrow - - modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ - total_mia_settled.amount += obj.accumulated_fees; - obj.accumulated_fees = 0; - }); - - wlog( "====================== AFTER SETTLE BLACK SWAN UNCLAIMED SETTLEMENT FUNDS ==============\n" ); - wdump((collateral_gathered)(total_mia_settled)(original_mia_supply)(mia_dyn.current_supply)); - modify( bitasset.options.short_backing_asset(*this).dynamic_asset_data_id(*this), [&]( asset_dynamic_data_object& obj ){ - obj.accumulated_fees += collateral_gathered.amount; - }); - - FC_ASSERT( total_mia_settled.amount == original_mia_supply, "", ("total_settled",total_mia_settled)("original",original_mia_supply) ); - } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } +} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } void database::cancel_order(const force_settlement_object& order, bool create_virtual_op) { @@ -167,7 +112,49 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua // TODO: create a virtual cancel operation } - remove( order ); + remove(order); +} + +bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) +{ + auto order_id = new_order_object.id; + const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id); + const asset_object& receive_asset = get(new_order_object.amount_to_receive().asset_id); + + // Possible optimization: We only need to check calls if both are true: + // - The new order is at the front of the book + // - The new order is below the call limit price + bool called_some = check_call_orders(sell_asset, allow_black_swan); + called_some |= check_call_orders(receive_asset, allow_black_swan); + if( called_some && !find_object(order_id) ) // then we were filled by call order + return true; + + const auto& limit_price_idx = get_index_type().indices().get(); + + // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to + // determine whether or not this order has "changed the book" in a way that requires us to + // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs + // constant time check. Potential optimization. + + auto max_price = ~new_order_object.sell_price; + auto limit_itr = limit_price_idx.lower_bound(max_price.max()); + auto limit_end = limit_price_idx.upper_bound(max_price); + + bool finished = false; + while( !finished && limit_itr != limit_end ) + { + auto old_limit_itr = limit_itr; + ++limit_itr; + // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + finished = (match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) != 2); + } + + //Possible optimization: only check calls if the new order completely filled some old order + //Do I need to check both assets? + check_call_orders(sell_asset, allow_black_swan); + check_call_orders(receive_asset, allow_black_swan); + + return find_object(order_id) == nullptr; } /** @@ -225,10 +212,6 @@ int database::match( const limit_order_object& bid, const limit_order_object& as return match( bid, ask, match_price ); } -int database::match( const limit_order_object& bid, const short_order_object& ask, const price& match_price ) -{ - return match( bid, ask, match_price ); -} asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, asset max_settlement ) @@ -263,7 +246,8 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c auto issuer_fees = pay_market_fees( recv_asset, receives ); pay_order( seller, receives - issuer_fees, pays ); - push_applied_operation( fill_order_operation{ order.id, order.seller, pays, receives, issuer_fees } ); + assert( pays.asset_id != receives.asset_id ); + push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees ) ); if( pays == order.amount_for_sale() ) { @@ -290,111 +274,10 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c } } -bool database::fill_order( const short_order_object& order, const asset& pays, const asset& receives ) -{ try { - assert( order.amount_for_sale().asset_id == pays.asset_id ); - assert( pays.asset_id != receives.asset_id ); - - const call_order_index& call_index = get_index_type(); - - const account_object& seller = order.seller(*this); - const asset_object& recv_asset = receives.asset_id(*this); - const asset_object& pays_asset = pays.asset_id(*this); - assert( pays_asset.is_market_issued() ); - - auto issuer_fees = pay_market_fees( recv_asset, receives ); - - bool filled = pays == order.amount_for_sale(); - asset seller_to_collateral; - if( (*pays_asset.bitasset_data_id)(*this).is_prediction_market ) - { - assert( pays.amount >= receives.amount ); - seller_to_collateral = pays.amount - receives.amount; - } - else - { - seller_to_collateral = filled ? order.get_collateral() : pays * order.sell_price; - } - auto buyer_to_collateral = receives - issuer_fees; - - if( receives.asset_id == asset_id_type() ) - { - const auto& statistics = seller.statistics(*this); - modify( statistics, [&]( account_statistics_object& b ){ - b.total_core_in_orders += buyer_to_collateral.amount; - }); - } - - modify( pays_asset.dynamic_asset_data_id(*this), [&]( asset_dynamic_data_object& obj ){ - obj.current_supply += pays.amount; - }); - - const auto& call_account_index = call_index.indices().get(); - auto call_itr = call_account_index.find( boost::make_tuple(order.seller, pays.asset_id) ); - if( call_itr == call_account_index.end() ) - { - create( [&]( call_order_object& c ){ - c.borrower = seller.id; - c.collateral = seller_to_collateral.amount + buyer_to_collateral.amount; - c.debt = pays.amount; - c.maintenance_collateral_ratio = order.maintenance_collateral_ratio; - c.call_price = price::max(seller_to_collateral.asset_id, pays.asset_id); - c.update_call_price(); - }); - } - else - { - modify( *call_itr, [&]( call_order_object& c ){ - c.debt += pays.amount; - c.collateral += seller_to_collateral.amount + buyer_to_collateral.amount; - c.maintenance_collateral_ratio = order.maintenance_collateral_ratio; - c.update_call_price(); - }); - } - - if( filled ) - { - remove( order ); - } - else - { - modify( order, [&]( short_order_object& b ) { - b.for_sale -= pays.amount; - b.available_collateral -= seller_to_collateral.amount; - assert( b.available_collateral > 0 ); - assert( b.for_sale > 0 ); - }); - - /** - * There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we - * have hit the limit where the seller is asking for nothing in return. When this - * happens we must refund any balance back to the seller, it is too small to be - * sold at the sale price. - */ - if( order.amount_to_receive().amount == 0 ) - { - adjust_balance(seller.get_id(), order.get_collateral()); - if( order.get_collateral().asset_id == asset_id_type() ) - { - const auto& statistics = seller.statistics(*this); - modify( statistics, [&]( account_statistics_object& b ){ - b.total_core_in_orders -= order.available_collateral; - }); - } - - remove( order ); - filled = true; - } - } - - push_applied_operation( fill_order_operation{ order.id, order.seller, pays, receives, issuer_fees } ); - - return filled; -} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } bool database::fill_order( const call_order_object& order, const asset& pays, const asset& receives ) { try { - idump((pays)(receives)(order)); + //idump((pays)(receives)(order)); assert( order.get_debt().asset_id == receives.asset_id ); assert( order.get_collateral().asset_id == pays.asset_id ); assert( order.get_collateral() >= pays ); @@ -415,7 +298,7 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); modify( mia_ddo, [&]( asset_dynamic_data_object& ao ){ - idump((receives)); + //idump((receives)); ao.current_supply -= receives.amount; }); @@ -439,6 +322,7 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co remove( order ); } + assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation{ order.id, order.borrower, pays, receives, asset(0, pays.asset_id) } ); return collateral_freed.valid(); @@ -462,20 +346,30 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa } adjust_balance(settle.owner, receives - issuer_fees); + assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation{ settle.id, settle.owner, pays, receives, issuer_fees } ); return filled; } FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) } /** + * Starting with the least collateralized orders, fill them if their + * call price is above the max(lowest bid,call_limit). * + * This method will return true if it filled a short or limit + * + * @param mia - the market issued asset that should be called. + * @param enable_black_swan - when adjusting collateral, triggering a black swan is invalid and will throw + * if enable_black_swan is not set to true. + * + * @return true if a margin call was executed. */ -bool database::check_call_orders( const asset_object& mia ) +bool database::check_call_orders(const asset_object& mia, bool enable_black_swan) { try { if( !mia.is_market_issued() ) return false; const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); - if( bitasset.current_feed.call_limit.is_null() ) return false; if( bitasset.is_prediction_market ) return false; + if( bitasset.current_feed.settlement_price.is_null() ) return false; const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); @@ -483,65 +377,52 @@ bool database::check_call_orders( const asset_object& mia ) const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); - const short_order_index& short_index = get_index_type(); - const auto& short_price_index = short_index.indices().get(); + // looking for limit orders selling the most USD for the least CORE + auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); + // stop when limit orders are selling too little USD for too much CORE + auto min_price = bitasset.current_feed.max_short_squeeze_price(); - auto short_itr = short_price_index.lower_bound( price::max( mia.id, bitasset.options.short_backing_asset ) ); - auto short_end = short_price_index.upper_bound( ~bitasset.current_feed.call_limit ); + assert( max_price.base.asset_id == min_price.base.asset_id ); + // NOTE limit_price_index is sorted from greatest to least + auto limit_itr = limit_price_index.lower_bound( max_price ); + auto limit_end = limit_price_index.upper_bound( min_price ); - auto limit_itr = limit_price_index.lower_bound( price::max( mia.id, bitasset.options.short_backing_asset ) ); - auto limit_end = limit_price_index.upper_bound( ~bitasset.current_feed.call_limit ); + if( limit_itr == limit_end ) { + return false; + } auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) ); auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); - bool filled_short_or_limit = false; + bool filled_limit = false; while( call_itr != call_end ) { - bool current_is_limit = true; bool filled_call = false; price match_price; asset usd_for_sale; if( limit_itr != limit_end ) { assert( limit_itr != limit_price_index.end() ); - if( short_itr != short_end && limit_itr->sell_price < short_itr->sell_price ) - { - assert( short_itr != short_price_index.end() ); - current_is_limit = false; - match_price = short_itr->sell_price; - usd_for_sale = short_itr->amount_for_sale(); - } - else - { - current_is_limit = true; - match_price = limit_itr->sell_price; - usd_for_sale = limit_itr->amount_for_sale(); - } + match_price = limit_itr->sell_price; + usd_for_sale = limit_itr->amount_for_sale(); } - else if( short_itr != short_end ) - { - assert( short_itr != short_price_index.end() ); - current_is_limit = false; - match_price = short_itr->sell_price; - usd_for_sale = short_itr->amount_for_sale(); - } - else return filled_short_or_limit; + else return filled_limit; match_price.validate(); if( match_price > ~call_itr->call_price ) { - return filled_short_or_limit; + return filled_limit; } auto usd_to_buy = call_itr->get_debt(); if( usd_to_buy * match_price > call_itr->get_collateral() ) { - elog( "black swan, we do not have enough collateral to cover at this price" ); - globally_settle_asset( mia, call_itr->get_debt() / call_itr->get_collateral() ); + FC_ASSERT( enable_black_swan ); + //globally_settle_asset(mia, call_itr->get_debt() / call_itr->get_collateral()); + globally_settle_asset(mia, bitasset.current_feed.settlement_price );// call_itr->get_debt() / call_itr->get_collateral()); return true; } @@ -553,11 +434,9 @@ bool database::check_call_orders( const asset_object& mia ) call_pays = order_receives; order_pays = usd_for_sale; - filled_short_or_limit = true; + filled_limit = true; filled_call = (usd_to_buy == usd_for_sale); - } - else // fill call - { + } else { // fill call call_receives = usd_to_buy; order_receives = usd_to_buy * match_price; call_pays = order_receives; @@ -568,20 +447,13 @@ bool database::check_call_orders( const asset_object& mia ) auto old_call_itr = call_itr; if( filled_call ) ++call_itr; - fill_order( *old_call_itr, call_pays, call_receives ); - if( current_is_limit ) - { - auto old_limit_itr = !filled_call ? limit_itr++ : limit_itr; - fill_order( *old_limit_itr, order_pays, order_receives ); - } - else - { - auto old_short_itr = !filled_call ? short_itr++ : short_itr; - fill_order( *old_short_itr, order_pays, order_receives ); - } + fill_order(*old_call_itr, call_pays, call_receives); + + auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr; + fill_order(*old_limit_itr, order_pays, order_receives); } // whlie call_itr != call_end - return filled_short_or_limit; + return filled_limit; } FC_CAPTURE_AND_RETHROW() } void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) @@ -594,16 +466,6 @@ void database::pay_order( const account_object& receiver, const asset& receives, adjust_balance(receiver.get_id(), receives); } -/** - * For Market Issued assets Managed by Delegates, any fees collected in the MIA need - * to be sold and converted into CORE by accepting the best offer on the table. - */ -bool database::convert_fees( const asset_object& mia ) -{ - if( mia.issuer != account_id_type() ) return false; - return false; -} - asset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount ) { assert( trade_asset.id == trade_amount.asset_id ); @@ -611,7 +473,7 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass if( !trade_asset.charges_market_fees() ) return trade_asset.amount(0); if( trade_asset.options.market_fee_percent == 0 ) - return trade_asset.amount(trade_asset.options.min_market_fee); + return trade_asset.amount(0); fc::uint128 a(trade_amount.amount.value); a *= trade_asset.options.market_fee_percent; @@ -620,8 +482,6 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass if( percent_fee.amount > trade_asset.options.max_market_fee ) percent_fee.amount = trade_asset.options.max_market_fee; - else if( percent_fee.amount < trade_asset.options.min_market_fee ) - percent_fee.amount = trade_asset.options.min_market_fee; return percent_fee; } @@ -636,7 +496,7 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re { const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this); modify( recv_dyn_data, [&]( asset_dynamic_data_object& obj ){ - idump((issuer_fees)); + //idump((issuer_fees)); obj.accumulated_fees += issuer_fees.amount; }); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 1ba5b95e..78ca531b 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -20,12 +20,12 @@ #include #include -#include #include -#include #include +#include #include #include +#include #include @@ -65,8 +65,8 @@ void database::update_signing_witness(const witness_object& signing_witness, con modify( signing_witness, [&]( witness_object& _wit ) { - _wit.last_secret = new_block.previous_secret; - _wit.next_secret = new_block.next_secret_hash; + _wit.previous_secret = new_block.previous_secret; + _wit.next_secret_hash = new_block.next_secret_hash; _wit.accumulated_income += witness_pay; } ); } @@ -102,7 +102,7 @@ void database::clear_expired_proposals() const proposal_object& proposal = *proposal_expiration_index.begin(); processed_transaction result; try { - if( proposal.is_authorized_to_execute(this) ) + if( proposal.is_authorized_to_execute(*this) ) { result = push_proposal(proposal); //TODO: Do something with result so plugins can process it. @@ -118,29 +118,22 @@ void database::clear_expired_proposals() void database::clear_expired_orders() { - transaction_evaluation_state cancel_context(this, true); + with_skip_flags( + get_node_properties().skip_flags | skip_authority_check, [&](){ + transaction_evaluation_state cancel_context(this); - //Cancel expired limit orders - auto& limit_index = get_index_type().indices().get(); - while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() ) - { - limit_order_cancel_operation canceler; - const limit_order_object& order = *limit_index.begin(); - canceler.fee_paying_account = order.seller; - canceler.order = order.id; - apply_operation(cancel_context, canceler); - } + //Cancel expired limit orders + auto& limit_index = get_index_type().indices().get(); + while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() ) + { + limit_order_cancel_operation canceler; + const limit_order_object& order = *limit_index.begin(); + canceler.fee_paying_account = order.seller; + canceler.order = order.id; + apply_operation(cancel_context, canceler); + } + }); - //Cancel expired short orders - auto& short_index = get_index_type().indices().get(); - while( !short_index.empty() && short_index.begin()->expiration <= head_block_time() ) - { - const short_order_object& order = *short_index.begin(); - short_order_cancel_operation canceler; - canceler.fee_paying_account = order.seller; - canceler.order = order.id; - apply_operation(cancel_context, canceler); - } //Process expired force settlement orders auto& settlement_index = get_index_type().indices().get(); @@ -187,9 +180,11 @@ void database::clear_expired_orders() max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply)); if( mia.force_settled_volume >= max_settlement_volume.amount ) { + /* ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); + */ if( next_asset() ) continue; break; @@ -224,12 +219,26 @@ void database::clear_expired_orders() void database::update_expired_feeds() { - auto& asset_idx = get_index_type(); - for( const asset_bitasset_data_object* b : asset_idx ) - if( b->feed_is_expired(head_block_time()) ) - modify(*b, [this](asset_bitasset_data_object& a) { + auto& asset_idx = get_index_type().indices(); + for( const asset_object& a : asset_idx ) + { + if( !a.is_market_issued() ) + continue; + + const asset_bitasset_data_object& b = a.bitasset_data(*this); + if( b.feed_is_expired(head_block_time()) ) + { + modify(b, [this](asset_bitasset_data_object& a) { a.update_median_feeds(head_block_time()); }); + check_call_orders(b.current_feed.settlement_price.base.asset_id(*this)); + } + if( !b.current_feed.core_exchange_rate.is_null() && + a.options.core_exchange_rate != b.current_feed.core_exchange_rate ) + modify(a, [&b](asset_object& a) { + a.options.core_exchange_rate = b.current_feed.core_exchange_rate; + }); + } } void database::update_withdraw_permissions() diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index 92e75afb..d233c8bf 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -23,9 +23,6 @@ namespace graphene { namespace chain { -static_assert((GRAPHENE_GENESIS_TIMESTAMP % GRAPHENE_DEFAULT_BLOCK_INTERVAL) == 0, - "GRAPHENE_GENESIS_TIMESTAMP must be aligned to a block interval."); - pair database::get_scheduled_witness(uint32_t slot_num)const { if( slot_num == 0 ) @@ -93,7 +90,7 @@ vector database::get_near_witness_schedule()const return result; } -void database::update_witness_schedule(signed_block next_block) +void database::update_witness_schedule(const signed_block& next_block) { const global_property_object& gpo = get_global_properties(); const witness_schedule_object& wso = get(witness_schedule_id_type()); @@ -106,10 +103,13 @@ void database::update_witness_schedule(signed_block next_block) // triggering FC_ASSERT elsewhere assert( schedule_slot > 0 ); + witness_id_type first_witness; + bool slot_is_near = wso.scheduler.get_slot( schedule_slot-1, first_witness ); witness_id_type wit; const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + assert( dpo.random.data_size() == witness_scheduler_rng::seed_length ); assert( witness_scheduler_rng::seed_length == wso.rng_seed.size() ); @@ -118,14 +118,22 @@ void database::update_witness_schedule(signed_block next_block) _wso.slots_since_genesis += schedule_slot; witness_scheduler_rng rng(wso.rng_seed.data, _wso.slots_since_genesis); - _wso.scheduler._min_token_count = gpo.active_witnesses.size() / 2; - uint32_t drain = schedule_slot; - while( drain > 0 ) + _wso.scheduler._min_token_count = std::max(int(gpo.active_witnesses.size()) / 2, 1); + + if( slot_is_near ) { - if( _wso.scheduler.size() == 0 ) - break; - _wso.scheduler.consume_schedule(); - --drain; + uint32_t drain = schedule_slot; + while( drain > 0 ) + { + if( _wso.scheduler.size() == 0 ) + break; + _wso.scheduler.consume_schedule(); + --drain; + } + } + else + { + _wso.scheduler.reset_schedule( first_witness ); } while( !_wso.scheduler.get_slot(schedule_needs_filled, wit) ) { @@ -133,6 +141,16 @@ void database::update_witness_schedule(signed_block next_block) memcpy(_wso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); } _wso.last_scheduling_block = next_block.block_num(); + _wso.recent_slots_filled = ( + (_wso.recent_slots_filled << 1) + + 1) << (schedule_slot - 1); }); } + +uint32_t database::witness_participation_rate()const +{ + const witness_schedule_object& wso = get(witness_schedule_id_type()); + return uint64_t(GRAPHENE_100_PERCENT) * wso.recent_slots_filled.popcount() / 128; +} + } } diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 46117211..13d78646 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -17,27 +17,29 @@ */ #include #include +#include #include -#include + #include #include -#include -#include -#include +#include +#include +#include #include namespace graphene { namespace chain { - database& generic_evaluator::db()const { return trx_state->db(); } +database& generic_evaluator::db()const { return trx_state->db(); } + operation_result generic_evaluator::start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ) - { + { try { trx_state = &eval_state; check_required_authorities(op); auto result = evaluate( op ); if( apply ) result = this->apply( op ); return result; - } + } FC_CAPTURE_AND_RETHROW() } void generic_evaluator::prepare_fee(account_id_type account_id, asset fee) { @@ -55,7 +57,8 @@ namespace graphene { namespace chain { asset fee_from_pool = fee_from_account * fee_asset->options.core_exchange_rate; FC_ASSERT( fee_from_pool.asset_id == asset_id_type() ); core_fee_paid = fee_from_pool.amount; - FC_ASSERT( core_fee_paid <= fee_asset_dyn_data->fee_pool ); + FC_ASSERT( core_fee_paid <= fee_asset_dyn_data->fee_pool, "Fee pool balance of '${b}' is less than the ${r} required to convert ${c}", + ("r", db().to_pretty_string( fee_from_pool))("b",db().to_pretty_string(fee_asset_dyn_data->fee_pool))("c",db().to_pretty_string(fee)) ); } } @@ -75,60 +78,52 @@ namespace graphene { namespace chain { } FC_CAPTURE_AND_RETHROW() } bool generic_evaluator::verify_authority( const account_object& a, authority::classification c ) - { + { try { return trx_state->check_authority( a, c ); - } + } FC_CAPTURE_AND_RETHROW( (a)(c) ) } + void generic_evaluator::check_required_authorities(const operation& op) - { + { try { flat_set active_auths; flat_set owner_auths; - op.visit(operation_get_required_auths(active_auths, owner_auths)); + vector other_auths; + + operation_get_required_authorities( op, active_auths, owner_auths, other_auths ); for( auto id : active_auths ) { - FC_ASSERT(verify_authority(id(db()), authority::active) || - verify_authority(id(db()), authority::owner), "", ("id", id)); + GRAPHENE_ASSERT( + verify_authority(id(db()), authority::active) || + verify_authority(id(db()), authority::owner), + tx_missing_active_auth, + "missing required active authority ${id}", ("id", id)); } + for( auto id : owner_auths ) { - FC_ASSERT(verify_authority(id(db()), authority::owner), "", ("id", id)); + GRAPHENE_ASSERT( + verify_authority(id(db()), authority::owner), + tx_missing_owner_auth, + "missing required owner authority ${id}", ("id", id)); } - } - /* - bool generic_evaluator::verify_signature( const key_object& k ) - { - return trx_state->_skip_signature_check || trx_state->signed_by( k.id ); - } - */ - - object_id_type generic_evaluator::get_relative_id( object_id_type rel_id )const - { - if( rel_id.space() == relative_protocol_ids ) + for( const auto& auth : other_auths ) { - FC_ASSERT( rel_id.instance() < trx_state->operation_results.size() ); - // fetch the object just to make sure it exists. - auto r = trx_state->operation_results[rel_id.instance()].get(); - db().get_object( r ); // make sure it exists. - return r; + GRAPHENE_ASSERT( + trx_state->check_authority(auth), + tx_missing_other_auth, + "missing required authority ${auth}", + ("auth",auth)("sigs",trx_state->_sigs)); } - return rel_id; - } - authority generic_evaluator::resolve_relative_ids( const authority& a )const + } FC_CAPTURE_AND_RETHROW( (op) ) } + + void generic_evaluator::verify_authority_accounts( const authority& a )const { - authority result; - result.auths.reserve( a.auths.size() ); - result.weight_threshold = a.weight_threshold; - - for( const auto& item : a.auths ) - { - auto id = get_relative_id( item.first ); - FC_ASSERT( id.type() == key_object_type || id.type() == account_object_type ); - result.auths[id] = item.second; - } - - return result; + const auto& chain_params = db().get_global_properties().parameters; + FC_ASSERT( a.num_auths() <= chain_params.maximum_authority_membership ); + for( const auto& acnt : a.account_auths ) + acnt.first(db()); } } } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index c81e1350..11c91c5c 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -40,10 +40,9 @@ void fork_database::start_block( signed_block b ) _head = item; } -shared_ptr fork_database::push_block( signed_block b ) +shared_ptr fork_database::push_block( const signed_block& b ) { - auto item = std::make_shared( std::move(b) ); - //wdump((item->num)(_head?_head->num:0)); + auto item = std::make_shared( b ); if( _head && b.previous != block_id_type() ) { diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index c41a15fc..56e3f96a 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -16,12 +16,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include #include #include namespace graphene { namespace chain { + class database; /** * @class account_statistics_object @@ -64,8 +64,8 @@ namespace graphene { namespace chain { * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid * doing massive amounts of uint128 arithmetic on each and every operation. * - *These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance - *interval. + * These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance + * interval. */ share_type pending_fees; /** @@ -76,6 +76,8 @@ namespace graphene { namespace chain { /// @brief Calculate the percentage discount this user receives on his fees uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const; + /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees + void process_fees(const account_object& a, database& d) const; }; /** @@ -106,7 +108,7 @@ namespace graphene { namespace chain { * @ingroup protocol * * Accounts are the primary unit of authority on the graphene system. Users must have an account in order to use - * assets, trade in the markets, vote for delegates, etc. + * assets, trade in the markets, vote for committee_members, etc. */ class account_object : public graphene::db::annotated_object { @@ -141,6 +143,7 @@ namespace graphene { namespace chain { /// The account's name. This name must be unique among all account names on the graph. May not be empty. string name; + /** * The owner authority represents absolute control over the account. Usually the keys in this authority will * be kept in cold storage, as they should not be needed very often and compromise of these keys constitutes @@ -151,19 +154,9 @@ namespace graphene { namespace chain { /// The owner authority contains the hot keys of the account. This authority has control over nearly all /// operations the account may perform. authority active; - /// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non- - /// validated account activities. This field is here to prevent confusion if the active authority has zero or - /// multiple keys in it. - key_id_type memo_key; - /// If this field is set to an account ID other than 0, this account's votes will be ignored and its stake - /// will be counted as voting for the referenced account's selected votes instead. - account_id_type voting_account; - uint16_t num_witness = 0; - uint16_t num_committee = 0; - /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this - /// account's balance of core asset. - flat_set votes; + typedef account_options options_type; + account_options options; /// The reference implementation records the account's statistics in a separate object. This field contains the /// ID of that object. @@ -189,6 +182,12 @@ namespace graphene { namespace chain { * Vesting balance which receives cashback_reward deposits. */ optional cashback_vb; + template + const vesting_balance_object& cashback_balance(const DB& db)const + { + FC_ASSERT(cashback_vb); + return db.get(*cashback_vb); + } /// @return true if this is a lifetime member account; false otherwise. bool is_lifetime_member()const @@ -231,8 +230,50 @@ namespace graphene { namespace chain { static const uint8_t space_id = implementation_ids; static const uint8_t type_id = meta_account_object_type; - key_id_type memo_key; - delegate_id_type delegate_id; // optional + public_key_type memo_key; + committee_member_id_type committee_member_id; // optional + }; + + /** + * @brief This secondary index will allow a reverse lookup of all accounts that a particular key or account + * is an potential signing authority. + */ + class account_member_index : public secondary_index + { + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override; + virtual void object_modified( const object& after ) override; + + + /** given an account or key, map it to the set of accounts that reference it in an active or owner authority */ + map< account_id_type, set > account_to_account_memberships; + map< public_key_type, set > account_to_key_memberships; + + + protected: + set get_account_members( const account_object& a )const; + set get_key_members( const account_object& a )const; + + set before_account_members; + set before_key_members; + }; + + /** + * @brief This secondary index will allow a reverse lookup of all accounts that have been referred by + * a particular account. + */ + class account_referrer_index : public secondary_index + { + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override; + virtual void object_modified( const object& after ) override; + + /** maps the referrer to the set of accounts that they have referred */ + map< account_id_type, set > referred_by; }; struct by_asset; @@ -269,14 +310,14 @@ namespace graphene { namespace chain { account_object, indexed_by< hashed_unique< tag, member< object, object_id_type, &object::id > >, - ordered_non_unique< tag, member > + ordered_unique< tag, member > > - > account_object_multi_index_type; + > account_multi_index_type; /** * @ingroup object_index */ - typedef generic_index account_index; + typedef generic_index account_index; }} @@ -284,8 +325,8 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (graphene::db::annotated_object), (membership_expiration_date)(registrar)(referrer)(lifetime_referrer) (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage) - (name)(owner)(active)(memo_key)(voting_account)(num_witness)(num_committee)(votes) - (statistics)(whitelisting_accounts)(blacklisting_accounts)(cashback_vb) ) + (name)(owner)(active)(options)(statistics)(whitelisting_accounts)(blacklisting_accounts) + (cashback_vb) ) FC_REFLECT_DERIVED( graphene::chain::account_balance_object, (graphene::db::object), @@ -293,7 +334,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_balance_object, FC_REFLECT_DERIVED( graphene::chain::meta_account_object, (graphene::db::object), - (memo_key)(delegate_id) ) + (memo_key)(committee_member_id) ) FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain::object), (most_recent_op) diff --git a/libraries/chain/include/graphene/chain/delegate_evaluator.hpp b/libraries/chain/include/graphene/chain/assert_evaluator.hpp similarity index 81% rename from libraries/chain/include/graphene/chain/delegate_evaluator.hpp rename to libraries/chain/include/graphene/chain/assert_evaluator.hpp index 6fd04a19..74cb5aee 100644 --- a/libraries/chain/include/graphene/chain/delegate_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/assert_evaluator.hpp @@ -16,18 +16,19 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include -#include +#include namespace graphene { namespace chain { - class delegate_create_evaluator : public evaluator + class assert_evaluator : public evaluator { public: - typedef delegate_create_operation operation_type; + typedef assert_operation operation_type; - object_id_type do_evaluate( const delegate_create_operation& o ); - object_id_type do_apply( const delegate_create_operation& o ); + void_result do_evaluate( const assert_operation& o ); + void_result do_apply( const assert_operation& o ); }; } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 212bc9af..e9966fbc 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -16,8 +16,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include -#include #include namespace graphene { namespace chain { @@ -27,7 +27,7 @@ namespace graphene { namespace chain { public: typedef asset_create_operation operation_type; - object_id_type do_evaluate( const asset_create_operation& o ); + void_result do_evaluate( const asset_create_operation& o ); object_id_type do_apply( const asset_create_operation& o ); }; @@ -42,12 +42,12 @@ namespace graphene { namespace chain { const account_object* to_account = nullptr; }; - class asset_burn_evaluator : public evaluator + class asset_reserve_evaluator : public evaluator { public: - typedef asset_burn_operation operation_type; - void_result do_evaluate( const asset_burn_operation& o ); - void_result do_apply( const asset_burn_operation& o ); + typedef asset_reserve_operation operation_type; + void_result do_evaluate( const asset_reserve_operation& o ); + void_result do_apply( const asset_reserve_operation& o ); const asset_dynamic_data_object* asset_dyn_data = nullptr; const account_object* from_account = nullptr; @@ -113,8 +113,8 @@ namespace graphene { namespace chain { public: typedef asset_settle_operation operation_type; - object_id_type do_evaluate(const operation_type& op); - object_id_type do_apply(const operation_type& op); + void_result do_evaluate(const operation_type& op); + operation_result do_apply(const operation_type& op); const asset_object* asset_to_settle = nullptr; }; diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 95d78d4d..01376bc5 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -16,8 +16,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include -#include #include #include @@ -33,6 +33,7 @@ namespace graphene { namespace chain { class account_object; + class database; using namespace graphene::db; /** @@ -89,6 +90,7 @@ namespace graphene { namespace chain { bool charges_market_fees()const { return options.flags & charge_market_fee; } /// @return true if this asset may only be transferred to/from the issuer or market orders bool is_transfer_restricted()const { return options.flags & transfer_restricted; } + bool can_override()const { return options.flags & override_authority; } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -108,87 +110,17 @@ namespace graphene { namespace chain { { FC_ASSERT(amount.asset_id == id); return amount_to_pretty_string(amount.amount); } /// Ticker symbol for this asset, i.e. "USD" - string symbol; - /// Maximum number of digits after the decimal point - uint64_t precision; + string symbol; + /// Maximum number of digits after the decimal point (must be <= 12) + uint8_t precision; /// ID of the account which issued this asset. - account_id_type issuer; + account_id_type issuer; - /** - * @brief The asset_options struct contains options available on all assets in the network - * - * @note Changes to this struct will break protocol compatibility - */ - struct asset_options { - /// The maximum supply of this asset which may exist at any given time. This can be as large as - /// GRAPHENE_MAX_SHARE_SUPPLY - share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY; - /// When this asset is traded on the markets, this percentage of the total traded will be exacted and paid - /// to the issuer. This is a fixed point value, representing hundredths of a percent, i.e. a value of 100 - /// in this field means a 1% fee is charged on market trades of this asset. - uint16_t market_fee_percent = 0; - share_type max_market_fee = GRAPHENE_MAX_SHARE_SUPPLY; - share_type min_market_fee; + asset_options options; - /// The flags which the issuer has permission to update. See @ref asset_issuer_permission_flags - uint16_t issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - /// The currently active flags on this permission. See @ref asset_issuer_permission_flags - uint16_t flags = 0; - - /// When a non-core asset is used to pay a fee, the blockchain must convert that asset to core asset in - /// order to accept the fee. If this asset's fee pool is funded, the chain will automatically deposite fees - /// in this asset to its accumulated fees, and withdraw from the fee pool the same amount as converted at - /// the core exchange rate. - price core_exchange_rate; - - /// A set of accounts which maintain whitelists to consult for this asset. If enforce_white_list() returns - /// true, an account may only send, receive, trade, etc. in this asset if one of these accounts appears in - /// its account_object::whitelisting_accounts field. - flat_set whitelist_authorities; - /// A set of accounts which maintain blacklists to consult for this asset. If enforce_white_list() returns - /// true, an account may only send, receive, trade, etc. in this asset if none of these accounts appears in - /// its account_object::blacklisting_accounts field. If the account is blacklisted, it may not transact in - /// this asset even if it is also whitelisted. - flat_set blacklist_authorities; - - /** defines the assets that this asset may be traded against in the market */ - flat_set whitelist_markets; - /** defines the assets that this asset may not be traded against in the market, must not overlap whitelist */ - flat_set blacklist_markets; - - /// Perform internal consistency checks. - /// @throws fc::exception if any check fails - void validate()const; - } options; - /** - * @brief The bitasset_options struct contains configurable options available only to BitAssets. - * - * @note Changes to this struct will break protocol compatibility - */ - struct bitasset_options { - /// Time before a price feed expires - uint32_t feed_lifetime_sec = GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME; - /// This is the delay between the time a long requests settlement and the chain evaluates the settlement - uint32_t force_settlement_delay_sec = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY; - /// This is the percent to adjust the feed price in the short's favor in the event of a forced settlement - uint16_t force_settlement_offset_percent = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET; - /// Force settlement volume can be limited such that only a certain percentage of the total existing supply - /// of the asset may be force-settled within any given chain maintenance interval. This field stores the - /// percentage of the current supply which may be force settled within the current maintenance interval. If - /// force settlements come due in an interval in which the maximum volume has already been settled, the new - /// settlements will be enqueued and processed at the beginning of the next maintenance interval. - uint16_t maximum_force_settlement_volume = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME; - /// This speicifies which asset type is used to collateralize short sales - /// This field may only be updated if the current supply of the asset is zero. - asset_id_type short_backing_asset; - - /// Perform internal consistency checks. - /// @throws fc::exception if any check fails - void validate()const; - }; /// Current supply, fee pool, and collected fees are stored in a separate object as they change frequently. - dynamic_asset_data_id_type dynamic_asset_data_id; + asset_dynamic_data_id_type dynamic_asset_data_id; /// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true optional bitasset_data_id; @@ -212,8 +144,11 @@ namespace graphene { namespace chain { const asset_dynamic_data_object& dynamic_data(const DB& db)const { return db.get(dynamic_asset_data_id); } + /** + * The total amount of an asset that is reserved for future issuance. + */ template - share_type burned( const DB& db )const + share_type reserved( const DB& db )const { return options.max_supply - dynamic_data(db).current_supply; } }; @@ -230,10 +165,10 @@ namespace graphene { namespace chain { static const uint8_t type_id = impl_asset_bitasset_data_type; /// The tunable options for BitAssets are stored in this field. - asset_object::bitasset_options options; + bitasset_options options; - /// Feeds published for this asset. If issuer is not genesis, the keys in this map are the feed publishing - /// accounts; otherwise, the feed publishers are the currently active delegates and witnesses and this map + /// Feeds published for this asset. If issuer is not committee, the keys in this map are the feed publishing + /// accounts; otherwise, the feed publishers are the currently active committee_members and witnesses and this map /// should be treated as an implementation detail. The timestamp on each feed is the time it was published. flat_map> feeds; /// This is the currently active price feed, calculated as the median of values from the currently active @@ -250,6 +185,22 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; + /** return true if there has been a black swan, false otherwise */ + bool has_settlement()const { return !settlement_price.is_null(); } + + /** + * In the event of a black swan, the swan price is saved in the settlement price, and all margin positions + * are settled at the same price with the siezed collateral being moved into the settlement fund. From this + * point on no further updates to the asset are permitted (no feeds, etc) and forced settlement occurs + * immediately when requested, using the settlement price and fund. + */ + ///@{ + /// Price at which force settlements of a black swanned asset will occur + price settlement_price; + /// Amount of collateral which is available for force settlement + share_type settlement_fund; + ///@} + time_point_sec feed_expiration_time()const { return current_feed_publication_time + options.feed_lifetime_sec; } bool feed_is_expired(time_point_sec current_time)const @@ -292,29 +243,10 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db:: (options) (force_settled_volume) (is_prediction_market) + (settlement_price) + (settlement_fund) ) -FC_REFLECT( graphene::chain::asset_object::asset_options, - (max_supply) - (market_fee_percent) - (max_market_fee) - (min_market_fee) - (issuer_permissions) - (flags) - (core_exchange_rate) - (whitelist_authorities) - (blacklist_authorities) - (whitelist_markets) - (blacklist_markets) - ) -FC_REFLECT( graphene::chain::asset_object::bitasset_options, - (feed_lifetime_sec) - (force_settlement_delay_sec) - (force_settlement_offset_percent) - (maximum_force_settlement_volume) - (short_backing_asset) - ) - FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::annotated_object), (symbol) diff --git a/libraries/chain/include/graphene/chain/balance_evaluator.hpp b/libraries/chain/include/graphene/chain/balance_evaluator.hpp new file mode 100644 index 00000000..acd2e1f1 --- /dev/null +++ b/libraries/chain/include/graphene/chain/balance_evaluator.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +class balance_claim_evaluator : public evaluator +{ +public: + typedef balance_claim_operation operation_type; + + const balance_object* balance = nullptr; + + void_result do_evaluate(const balance_claim_operation& op); + + /** + * @note the fee is always 0 for this particular operation because once the + * balance is claimed it frees up memory and it cannot be used to spam the network + */ + void_result do_apply(const balance_claim_operation& op); +}; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/balance_object.hpp b/libraries/chain/include/graphene/chain/balance_object.hpp new file mode 100644 index 00000000..82828f7c --- /dev/null +++ b/libraries/chain/include/graphene/chain/balance_object.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include + +namespace graphene { namespace chain { + + class balance_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = balance_object_type; + + bool is_vesting_balance()const + { return vesting_policy.valid(); } + asset available(fc::time_point_sec now)const + { + return is_vesting_balance()? vesting_policy->get_allowed_withdraw({balance, now, {}}) + : balance; + } + + address owner; + asset balance; + optional vesting_policy; + time_point_sec last_claim_date; + asset_id_type asset_type()const { return balance.asset_id; } + }; + + struct by_owner; + + /** + * @ingroup object_index + */ + using balance_multi_index_type = multi_index_container< + balance_object, + indexed_by< + hashed_unique< tag, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, composite_key< + balance_object, + member, + const_mem_fun + > > + > + >; + + /** + * @ingroup object_index + */ + using balance_index = generic_index; +} } + +FC_REFLECT_DERIVED( graphene::chain::balance_object, (graphene::db::object), + (owner)(balance)(vesting_policy)(last_claim_date) ) diff --git a/libraries/chain/include/graphene/chain/key_object.hpp b/libraries/chain/include/graphene/chain/block_database.hpp similarity index 64% rename from libraries/chain/include/graphene/chain/key_object.hpp rename to libraries/chain/include/graphene/chain/block_database.hpp index 3a9d07da..1e8a97a6 100644 --- a/libraries/chain/include/graphene/chain/key_object.hpp +++ b/libraries/chain/include/graphene/chain/block_database.hpp @@ -16,30 +16,28 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include -#include -#include +#include +#include namespace graphene { namespace chain { - /** - * @class key_object - * @brief maps an ID to a public key or address - * @ingroup object - * @ingroup protocol - */ - class key_object : public graphene::db::abstract_object + class block_database { public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = key_object_type; + void open( const fc::path& dbdir ); + bool is_open()const; + void flush(); + void close(); - key_id_type get_id()const { return key_id_type( id.instance() ); } - address key_address()const; - const public_key_type& key()const { return key_data.get(); } + void store( const block_id_type& id, const signed_block& b ); + void remove( const block_id_type& id ); - static_variant key_data; + bool contains( const block_id_type& id )const; + block_id_type fetch_block_id( uint32_t block_num )const; + optional fetch_optional( const block_id_type& id )const; + optional fetch_by_number( uint32_t block_num )const; + optional last()const; + private: + mutable std::fstream _blocks; + mutable std::fstream _block_num_to_pos; }; } } - -FC_REFLECT_DERIVED( graphene::chain::key_object, (graphene::db::object), (key_data) ) diff --git a/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp b/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp new file mode 100644 index 00000000..1728e77e --- /dev/null +++ b/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + class committee_member_create_evaluator : public evaluator + { + public: + typedef committee_member_create_operation operation_type; + + void_result do_evaluate( const committee_member_create_operation& o ); + object_id_type do_apply( const committee_member_create_operation& o ); + }; + + class committee_member_update_global_parameters_evaluator : public evaluator + { + public: + typedef committee_member_update_global_parameters_operation operation_type; + + void_result do_evaluate( const committee_member_update_global_parameters_operation& o ); + void_result do_apply( const committee_member_update_global_parameters_operation& o ); + }; + + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/delegate_object.hpp b/libraries/chain/include/graphene/chain/committee_member_object.hpp similarity index 53% rename from libraries/chain/include/graphene/chain/delegate_object.hpp rename to libraries/chain/include/graphene/chain/committee_member_object.hpp index 29dcbfd5..447e51bf 100644 --- a/libraries/chain/include/graphene/chain/delegate_object.hpp +++ b/libraries/chain/include/graphene/chain/committee_member_object.hpp @@ -16,8 +16,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include +#include #include +#include namespace graphene { namespace chain { using namespace graphene::db; @@ -25,28 +26,41 @@ namespace graphene { namespace chain { class account_object; /** - * @brief tracks information about a delegate account. + * @brief tracks information about a committee_member account. * @ingroup object * - * A delegate is responsible for setting blockchain parameters and has - * dynamic multi-sig control over the genesis account. The current set of - * active delegates has control. + * A committee_member is responsible for setting blockchain parameters and has + * dynamic multi-sig control over the committee account. The current set of + * active committee_members has control. * - * Delegates were separated into a separate object to make iterating over - * the set of delegate easy. + * committee_members were separated into a separate object to make iterating over + * the set of committee_member easy. */ - class delegate_object : public abstract_object + class committee_member_object : public abstract_object { public: static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = delegate_object_type; + static const uint8_t type_id = committee_member_object_type; - account_id_type delegate_account; - vote_id_type vote_id; + account_id_type committee_member_account; + vote_id_type vote_id; + string url; }; + struct by_account; + using committee_member_multi_index_type = multi_index_container< + committee_member_object, + indexed_by< + ordered_unique< tag, + member + >, + hashed_unique< tag, + member + > + > + >; + using committee_member_index = generic_index; } } // graphene::chain -FC_REFLECT_DERIVED( graphene::chain::delegate_object, (graphene::db::object), - (delegate_account) - (vote_id) ) +FC_REFLECT_DERIVED( graphene::chain::committee_member_object, (graphene::db::object), + (committee_member_account)(vote_id)(url) ) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 507b5641..61add015 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -19,18 +19,26 @@ #define GRAPHENE_SYMBOL "CORE" #define GRAPHENE_ADDRESS_PREFIX "GPH" -#define GRAPHENE_MAX_SYMBOL_NAME_LENGTH 16 + +#define GRAPHENE_MIN_ACCOUNT_NAME_LENGTH 1 +#define GRAPHENE_MAX_ACCOUNT_NAME_LENGTH 63 + +#define GRAPHENE_MIN_ASSET_SYMBOL_LENGTH 3 +#define GRAPHENE_MAX_ASSET_SYMBOL_LENGTH 16 + #define GRAPHENE_MAX_ASSET_NAME_LENGTH 127 -#define GRAPHENE_MAX_SHARE_SUPPLY int64_t(1000000000000ll) + +#define GRAPHENE_MAX_SHARE_SUPPLY int64_t(1000000000000000ll) #define GRAPHENE_MAX_PAY_RATE 10000 /* 100% */ #define GRAPHENE_MAX_SIG_CHECK_DEPTH 2 #define GRAPHENE_MIN_WITNESS_COUNT 10 -#define GRAPHENE_MIN_DELEGATE_COUNT 10 +#define GRAPHENE_MIN_COMMITTEE_MEMBER_COUNT 10 /** - * Don't allow the delegates to publish a limit that would + * Don't allow the committee_members to publish a limit that would * make the network unable to operate. */ #define GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT 1024 +#define GRAPHENE_MIN_BLOCK_INTERVAL 1 /* seconds */ #define GRAPHENE_MAX_BLOCK_INTERVAL 30 /* seconds */ #define GRAPHENE_DEFAULT_BLOCK_INTERVAL 5 /* seconds */ @@ -42,29 +50,38 @@ #define GRAPHENE_MIN_BLOCK_SIZE_LIMIT (GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT*5) // 5 transactions per block #define GRAPHENE_MIN_TRANSACTION_EXPIRATION_LIMIT (GRAPHENE_MAX_BLOCK_INTERVAL * 5) // 5 transactions per block -#define GRAPHENE_BLOCKCHAIN_MAX_SHARES (1000*1000*int64_t(1000)*1000*int64_t(1000)) -#define GRAPHENE_BLOCKCHAIN_PRECISION 100000 +#define GRAPHENE_BLOCKCHAIN_PRECISION uint64_t( 100000 ) +#define CORE GRAPHENE_BLOCKCHAIN_PRECISION + #define GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS 5 -#define GRAPHENE_INITIAL_SUPPLY GRAPHENE_BLOCKCHAIN_MAX_SHARES #define GRAPHENE_DEFAULT_TRANSFER_FEE (1*GRAPHENE_BLOCKCHAIN_PRECISION) #define GRAPHENE_MAX_INSTANCE_ID (uint64_t(-1)>>16) +/** percentage fields are fixed point with a denominator of 10,000 */ #define GRAPHENE_100_PERCENT 10000 #define GRAPHENE_1_PERCENT (GRAPHENE_100_PERCENT/100) /** NOTE: making this a power of 2 (say 2^15) would greatly accelerate fee calcs */ #define GRAPHENE_MAX_MARKET_FEE_PERCENT GRAPHENE_100_PERCENT #define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY (60*60*24) ///< 1 day #define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET 0 ///< 1% -#define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME 2000 ///< 20% +#define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME (20* GRAPHENE_1_PERCENT) ///< 20% #define GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME (60*60*24) ///< 1 day #define GRAPHENE_MAX_FEED_PRODUCERS 200 #define GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP 10 #define GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES 10 #define GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS 10 -#define GRAPHENE_MIN_COLLATERAL_RATIO 1001 // lower than this could result in divide by 0 -#define GRAPHENE_MAX_COLLATERAL_RATIO 32000 // higher than this is unnecessary and may exceed int16 storage -#define GRAPHENE_DEFAULT_INITIAL_COLLATERAL_RATIO 2000 -#define GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO 1750 +/** + * These ratios are fixed point numbers with a denominator of GRAPHENE_COLLATERAL_RATIO_DENOM, the + * minimum maitenance collateral is therefore 1.001x and the default + * maintenance ratio is 1.75x + */ +///@{ +#define GRAPHENE_COLLATERAL_RATIO_DENOM 1000 +#define GRAPHENE_MIN_COLLATERAL_RATIO 1001 ///< lower than this could result in divide by 0 +#define GRAPHENE_MAX_COLLATERAL_RATIO 32000 ///< higher than this is unnecessary and may exceed int16 storage +#define GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO 1750 ///< Call when collateral only pays off 175% the debt +#define GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO 1500 ///< Stop calling when collateral only pays off 150% of the debt +///@} #define GRAPHENE_DEFAULT_MARGIN_PERIOD_SEC (30*60*60*24) #define GRAPHENE_DEFAULT_NUM_WITNESSES (101) @@ -72,7 +89,7 @@ #define GRAPHENE_DEFAULT_MAX_WITNESSES (1001) // SHOULD BE ODD #define GRAPHENE_DEFAULT_MAX_COMMITTEE (1001) // SHOULD BE ODD #define GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC (60*60*24*7*4) // Four weeks -#define GRAPHENE_DEFAULT_GENESIS_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks +#define GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks #define GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) #define GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE (30*GRAPHENE_1_PERCENT) #define GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT (50*GRAPHENE_1_PERCENT) @@ -82,7 +99,14 @@ #define GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD (GRAPHENE_BLOCKCHAIN_PRECISION*int64_t(100)) #define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) #define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000) -#define GRAPHENE_GENESIS_TIMESTAMP (1431700000) /// Should be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL +#define GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE 1 +#define GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD GRAPHENE_BLOCKCHAIN_PRECISION * 100; +#define GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE 1000 +#define GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS 4 + +#define GRAPHENE_MAX_WORKER_NAME_LENGTH 63 + +#define GRAPHENE_MAX_URL_LENGTH 127 // counter initialization values used to derive near and far future seeds for shuffling witnesses // we use the fractional bits of sqrt(2) in hex @@ -99,7 +123,7 @@ // counter used to determine bits of entropy // must be less than or equal to secret_hash_type::data_length() -#define GRAPHENE_RNG_SEED_LENGTH (224 / 8) +#define GRAPHENE_RNG_SEED_LENGTH (160 / 8) /** * every second, the fraction of burned core asset which cycles is @@ -112,15 +136,19 @@ #define GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(500) * 1000 ) #define GRAPHENE_MAX_INTEREST_APR uint16_t( 10000 ) -#define GRAPHENE_LEGACY_NAME_IMPORT_PERIOD 3000000 /** 3 million blocks */ /** * Reserved Account IDs with special meaning */ ///@{ -#define GRAPHENE_GENESIS_ACCOUNT (graphene::chain::account_id_type(0)) +/// Represents the current committee members, two-week review period +#define GRAPHENE_COMMITTEE_ACCOUNT (graphene::chain::account_id_type(0)) +/// Represents the current witnesses #define GRAPHENE_WITNESS_ACCOUNT (graphene::chain::account_id_type(1)) -#define GRAPHENE_DELEGATE_ACCOUNT (graphene::chain::account_id_type(2)) +/// Represents the current committee members +#define GRAPHENE_RELAXED_COMMITTEE_ACCOUNT (graphene::chain::account_id_type(2)) +/// Represents the canonical account with NO authority (nobody can access funds in null account) #define GRAPHENE_NULL_ACCOUNT (graphene::chain::account_id_type(3)) +/// Represents the canonical account with WILDCARD authority (anybody can access funds in temp account) #define GRAPHENE_TEMP_ACCOUNT (graphene::chain::account_id_type(4)) ///@} diff --git a/libraries/chain/include/graphene/chain/custom_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_evaluator.hpp index 3b315c1f..afa87dfb 100644 --- a/libraries/chain/include/graphene/chain/custom_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/custom_evaluator.hpp @@ -16,8 +16,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include -#include #include namespace graphene { namespace chain { diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 469a78ad..0500f834 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -17,20 +17,21 @@ */ #pragma once #include -#include -#include #include +#include #include #include #include +#include +#include #include #include -#include -#include #include #include +#include + #include #include @@ -39,13 +40,36 @@ namespace graphene { namespace chain { using graphene::db::abstract_object; using graphene::db::object; - typedef vector, share_type >> genesis_allocation; + namespace detail + { + /** + * Class used to help the with_skip_flags implementation. + * It must be defined in this header because it must be + * available to the with_skip_flags implementation, + * which is a template and therefore must also be defined + * in this header. + */ + struct skip_flags_restorer + { + skip_flags_restorer( node_property_object& npo, uint32_t old_skip_flags ) + : _npo( npo ), _old_skip_flags( old_skip_flags ) + {} + + ~skip_flags_restorer() + { + _npo.skip_flags = _old_skip_flags; + } + + node_property_object& _npo; + uint32_t _old_skip_flags; + }; + } /** * @class database * @brief tracks the blockchain state in an extensible manner */ - class database : public object_database + class database : public db::object_database { public: //////////////////// db_management.cpp //////////////////// @@ -55,27 +79,40 @@ namespace graphene { namespace chain { enum validation_steps { - skip_nothing = 0x00, - skip_delegate_signature = 0x01, ///< used while reindexing - skip_transaction_signatures = 0x02, ///< used by non delegate nodes - skip_undo_block = 0x04, ///< used while reindexing - skip_undo_transaction = 0x08, ///< used while applying block - skip_transaction_dupe_check = 0x10, ///< used while reindexing - skip_fork_db = 0x20, ///< used while reindexing - skip_block_size_check = 0x40, ///< used when applying locally generated transactions - skip_tapos_check = 0x80, ///< used while reindexing -- note this skips expiration check as well - skip_authority_check = 0x100, ///< used while reindexing -- disables any checking of authority on transactions - skip_merkle_check = 0x200 ///< used while reindexing + skip_nothing = 0, + skip_witness_signature = 1 << 0, ///< used while reindexing + skip_transaction_signatures = 1 << 1, ///< used by non-witness nodes + skip_undo_block = 1 << 2, ///< used while reindexing + skip_undo_transaction = 1 << 3, ///< used while applying block + skip_transaction_dupe_check = 1 << 4, ///< used while reindexing + skip_fork_db = 1 << 5, ///< used while reindexing + skip_block_size_check = 1 << 6, ///< used when applying locally generated transactions + skip_tapos_check = 1 << 7, ///< used while reindexing -- note this skips expiration check as well + skip_authority_check = 1 << 8, ///< used while reindexing -- disables any checking of authority on transactions + skip_merkle_check = 1 << 9, ///< used while reindexing + skip_assert_evaluation = 1 << 10 ///< used while reindexing }; - void open(const fc::path& data_dir, const genesis_allocation& initial_allocation = genesis_allocation()); + /** + * @brief Open a database, creating a new one if necessary + * + * Opens a database in the specified directory. If no initialized database is found, genesis_loader is called + * and its return value is used as the genesis state when initializing the new database + * + * genesis_loader will not be called if an existing database is found. + * + * @param data_dir Path to open or create database in + * @param genesis_loader A callable object which returns the genesis state to initialize new databases on + */ + template + void open(const fc::path& data_dir, F&& genesis_loader); /** * @brief Rebuild object graph from block history and open detabase * * This method may be called after or instead of @ref database::open, and will rebuild the object graph by * replaying blockchain history. When this method exits successfully, the database will be open. */ - void reindex(fc::path data_dir, const genesis_allocation& initial_allocation = genesis_allocation()); + void reindex(fc::path data_dir, const genesis_state_type& initial_allocation = genesis_state_type()); /** * @brief wipe Delete database from disk, and potentially the raw chain as well. @@ -99,8 +136,20 @@ namespace graphene { namespace chain { optional fetch_block_by_number( uint32_t num )const; const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; + /** + * Calculate the percent of block production slots that were missed in the + * past 128 blocks, not including the current block. + */ + uint32_t witness_participation_rate()const; + + void add_checkpoints( const flat_map& checkpts ); + const flat_map get_checkpoints()const { return _checkpoints; } + bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); processed_transaction push_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing ); + bool _push_block( const signed_block& b ); + processed_transaction _push_transaction( const signed_transaction& trx ); + ///@throws fc::exception if the proposed transaction fails to apply. processed_transaction push_proposal( const proposal_object& proposal ); @@ -108,7 +157,12 @@ namespace graphene { namespace chain { const fc::time_point_sec when, witness_id_type witness_id, const fc::ecc::private_key& block_signing_private_key, - uint32_t skip = 0 + uint32_t skip + ); + signed_block _generate_block( + const fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key ); void pop_block(); @@ -127,6 +181,8 @@ namespace graphene { namespace chain { void set_applied_operation_result( uint32_t op_id, const operation_result& r ); const vector& get_applied_operations()const; + string to_pretty_string( const asset& a )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. @@ -138,7 +194,7 @@ namespace graphene { namespace chain { fc::signal applied_block; /** - * After a block has been applied and committed. The callback + * Emitted After a block has been applied and committed. The callback * should not yield and should execute quickly. */ fc::signal&)> changed_objects; @@ -193,20 +249,41 @@ namespace graphene { namespace chain { const asset_object& get_core_asset()const; const global_property_object& get_global_properties()const; const dynamic_global_property_object& get_dynamic_global_properties()const; - const fee_schedule_type& current_fee_schedule()const; + const node_property_object& get_node_properties()const; + const fee_schedule& current_fee_schedule()const; - time_point_sec head_block_time()const; - uint32_t head_block_num()const; - block_id_type head_block_id()const; + time_point_sec head_block_time()const; + uint32_t head_block_num()const; + block_id_type head_block_id()const; + witness_id_type head_block_witness()const; decltype( chain_parameters::block_interval ) block_interval( )const; + node_property_object& node_properties(); + + /** + * Set the skip_flags to the given value, call callback, + * then reset skip_flags to their previous value after + * callback is done. + */ + template< typename Lambda > + void with_skip_flags( + uint32_t skip_flags, + Lambda callback ) + { + node_property_object& npo = node_properties(); + detail::skip_flags_restorer restorer( npo, npo.skip_flags ); + npo.skip_flags = skip_flags; + callback(); + return; + } + //////////////////// db_init.cpp //////////////////// void initialize_evaluators(); /// Reset the object graph in-memory void initialize_indexes(); - void init_genesis(const genesis_allocation& initial_allocation = genesis_allocation()); + void init_genesis(const genesis_state_type& genesis_state = genesis_state_type()); template void register_evaluator() @@ -234,8 +311,6 @@ namespace graphene { namespace chain { asset get_balance(account_id_type owner, asset_id_type asset_id)const; /// This is an overloaded method. asset get_balance(const account_object& owner, const asset_object& asset_obj)const; - /// This is an overloaded method. - asset get_balance(const account_object* owner, const asset_object* asset_obj)const; /** * @brief Adjust a particular account's balance in a given asset by a delta @@ -245,8 +320,6 @@ namespace graphene { namespace chain { void adjust_balance(account_id_type account, asset delta); /// This is an overloaded method. void adjust_balance(const account_object& account, asset delta); - /// This is an overloaded method. - void adjust_balance(const account_object* account, asset delta); /** * If delta.asset_id is a core asset, adjusts account statistics @@ -267,6 +340,16 @@ namespace graphene { namespace chain { void cancel_order(const force_settlement_object& order, bool create_virtual_op = true); void cancel_order(const limit_order_object& order, bool create_virtual_op = true); + /** + * @brief Process a new limit order through the markets + * @param order The new order to process + * @return true if order was completely filled; false otherwise + * + * This function takes a new limit order, and runs the markets attempting to match it with existing orders + * already on the books. + */ + bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); + /** * Matches the two orders, * @@ -281,7 +364,6 @@ namespace graphene { namespace chain { template int match( const limit_order_object& bid, const OrderType& ask, const price& match_price ); int match( const limit_order_object& bid, const limit_order_object& ask, const price& trade_price ); - int match( const limit_order_object& bid, const short_order_object& ask, const price& trade_price ); /// @return the amount of asset settled asset match(const call_order_object& call, const force_settlement_object& settle, @@ -293,16 +375,14 @@ namespace graphene { namespace chain { * @return true if the order was completely filled and thus freed. */ bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives ); - bool fill_order( const short_order_object& order, const asset& pays, const asset& receives ); bool fill_order( const call_order_object& order, const asset& pays, const asset& receives ); bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives ); - bool check_call_orders( const asset_object& mia ); + bool check_call_orders( const asset_object& mia, bool enable_black_swan = true ); // helpers to fill_order void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); - bool convert_fees( const asset_object& mia ); asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); @@ -314,24 +394,28 @@ namespace graphene { namespace chain { protected: //Mark pop_undo() as protected -- we do not want outside calling pop_undo(); it should call pop_block() instead void pop_undo() { object_database::pop_undo(); } + void notify_changed_objects(); private: optional _pending_block_session; vector< unique_ptr > _operation_evaluators; - template - vector> sort_votable_objects(size_t count)const; + template + vector> sort_votable_objects(size_t count)const; //////////////////// db_block.cpp //////////////////// void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing ); processed_transaction apply_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing ); + void _apply_block( const signed_block& next_block ); + processed_transaction _apply_transaction( const signed_transaction& trx ); operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op ); ///Steps involved in applying a new block ///@{ const witness_object& validate_block_header( uint32_t skip, const signed_block& next_block )const; + const witness_object& _validate_block_header( const signed_block& next_block )const; void create_block_summary(const signed_block& next_block); //////////////////// db_update.cpp //////////////////// @@ -345,7 +429,7 @@ namespace graphene { namespace chain { void update_withdraw_permissions(); //////////////////// db_witness_schedule.cpp //////////////////// - void update_witness_schedule(signed_block next_block); /// no-op except for scheduling blocks + void update_witness_schedule(const signed_block& next_block); /// no-op except for scheduling blocks ///Steps performed only at maintenance intervals ///@{ @@ -357,7 +441,7 @@ namespace graphene { namespace chain { void pay_workers( share_type& budget ); void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); void update_active_witnesses(); - void update_active_delegates(); + void update_active_committee_members(); template void perform_account_maintenance(std::tuple helpers); @@ -376,7 +460,7 @@ namespace graphene { namespace chain { * until the fork is resolved. This should make maintaining * the fork tree relatively simple. */ - graphene::db::level_map _block_id_to_block; + block_database _block_id_to_block; /** * Contains the set of ops that are in the process of being applied from @@ -395,6 +479,10 @@ namespace graphene { namespace chain { vector _witness_count_histogram_buffer; vector _committee_count_histogram_buffer; uint64_t _total_voting_stake; + + flat_map _checkpoints; + + node_property_object _node_property_object; }; namespace detail @@ -415,12 +503,24 @@ namespace graphene { namespace chain { (void)l; } } - template - void database::perform_account_maintenance(std::tuple helpers) - { - const auto& idx = get_index_type().indices(); - for( const account_object& a : idx ) - detail::for_each(helpers, a, detail::gen_seq()); - } + + template + void database::open(const fc::path& data_dir, F&& genesis_loader) + { try { + object_database::open(data_dir); + + _block_id_to_block.open(data_dir / "database" / "block_num_to_block"); + + if( !find(global_property_id_type()) ) + init_genesis(genesis_loader()); + + _pending_block.previous = head_block_id(); + _pending_block.timestamp = head_block_time(); + + auto last_block= _block_id_to_block.last(); + if( last_block.valid() ) + _fork_db.start_block( *last_block ); + + } FC_CAPTURE_AND_RETHROW( (data_dir) ) } } } diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index 922a056d..74d31378 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -16,12 +16,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include namespace graphene { namespace chain { class database; + struct signed_transaction; class generic_evaluator; class transaction_evaluation_state; @@ -42,174 +42,175 @@ namespace graphene { namespace chain { */ class evaluation_observer { - public: - virtual ~evaluation_observer(){} + public: + virtual ~evaluation_observer(){} - virtual void pre_evaluate( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge ) {} + virtual void pre_evaluate(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge) + {} - virtual void post_evaluate( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge, - const operation_result& result ) {} + virtual void post_evaluate(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge, + const operation_result& result) + {} - virtual void evaluation_failed( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge, - const operation_result& result ) {} + virtual void evaluation_failed(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge, + const operation_result& result) + {} }; class generic_evaluator { - public: - virtual ~generic_evaluator(){} + public: + virtual ~generic_evaluator(){} - virtual int get_type()const = 0; - virtual operation_result start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ); + virtual int get_type()const = 0; + virtual operation_result start_evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply); - /** @note derived classes should ASSUME that the default validation that is - * indepenent of chain state should be performed by op.validate() and should - * not perform these extra checks. - */ - virtual operation_result evaluate( const operation& op ) = 0; - virtual operation_result apply( const operation& op ) = 0; + /** + * @note derived classes should ASSUME that the default validation that is + * indepenent of chain state should be performed by op.validate() and should + * not perform these extra checks. + */ + virtual operation_result evaluate(const operation& op) = 0; + virtual operation_result apply(const operation& op) = 0; - database& db()const; + database& db()const; - void check_required_authorities(const operation& op); + void check_required_authorities(const operation& op); protected: - /** - * @brief Fetch objects relevant to fee payer and set pointer members - * @param account_id Account which is paying the fee - * @param fee The fee being paid. May be in assets other than core. - * - * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should - * be called during do_evaluate. - */ - void prepare_fee(account_id_type account_id, asset fee); - /// Pays the fee and returns the number of CORE asset that were paid. - void pay_fee(); + /** + * @brief Fetch objects relevant to fee payer and set pointer members + * @param account_id Account which is paying the fee + * @param fee The fee being paid. May be in assets other than core. + * + * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should + * be called during do_evaluate. + */ + void prepare_fee(account_id_type account_id, asset fee); + /// Pays the fee and returns the number of CORE asset that were paid. + void pay_fee(); - bool verify_authority( const account_object&, authority::classification ); - //bool verify_signature( const key_object& ); + bool verify_authority(const account_object&, authority::classification); + object_id_type get_relative_id( object_id_type rel_id )const; - object_id_type get_relative_id( object_id_type rel_id )const; + void verify_authority_accounts( const authority& a )const; - authority resolve_relative_ids( const authority& a )const; - - asset fee_from_account; - share_type core_fee_paid; - const account_object* fee_paying_account = nullptr; - const account_statistics_object* fee_paying_account_statistics = nullptr; - const asset_object* fee_asset = nullptr; - const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; - transaction_evaluation_state* trx_state; + asset fee_from_account; + share_type core_fee_paid; + const account_object* fee_paying_account = nullptr; + const account_statistics_object* fee_paying_account_statistics = nullptr; + const asset_object* fee_asset = nullptr; + const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; + transaction_evaluation_state* trx_state; }; class op_evaluator { - public: - virtual ~op_evaluator(){} - virtual operation_result evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ) = 0; + public: + virtual ~op_evaluator(){} + virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply) = 0; - vector< evaluation_observer* > eval_observers; + vector eval_observers; }; template class op_evaluator_impl : public op_evaluator { - public: - virtual operation_result evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply = true ) override + public: + virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply = true) override + { + // fc::exception from observers are suppressed. + // fc::exception from evaluation is deferred (re-thrown + // after all observers receive evaluation_failed) + + T eval; + shared_ptr evaluation_exception; + size_t observer_count = 0; + operation_result result; + + for( const auto& obs : eval_observers ) { - // fc::exception from observers are suppressed. - // fc::exception from evaluation is deferred (re-thrown - // after all observers receive evaluation_failed) - - T eval; - optional< fc::exception > evaluation_exception; - size_t observer_count = 0; - operation_result result; - - for( const auto& obs : eval_observers ) - { - try - { - obs->pre_evaluate( eval_state, op, apply, &eval ); - } - catch( const fc::exception& e ) - { - elog( "suppressed exception in observer pre method:\n${e}", ( "e", e.to_detail_string() ) ); - } - observer_count++; - } - - try - { - result = eval.start_evaluate( eval_state, op, apply ); - } - catch( const fc::exception& e ) - { - evaluation_exception = e; - } - - while( observer_count > 0 ) - { - --observer_count; - const auto& obs = eval_observers[ observer_count ]; - try - { - if( !evaluation_exception.valid() ) - obs->post_evaluate( eval_state, op, apply, &eval, result ); - else - obs->evaluation_failed( eval_state, op, apply, &eval, result ); - } - catch( const fc::exception& e ) - { - elog( "suppressed exception in observer post method:\n${e}", ( "e", e.to_detail_string() ) ); - } - } - - if( evaluation_exception.valid() ) - throw *evaluation_exception; - return result; + try + { + obs->pre_evaluate(eval_state, op, apply, &eval); + } + catch( const fc::exception& e ) + { + elog( "suppressed exception in observer pre method:\n${e}", ( "e", e.to_detail_string() ) ); + } + observer_count++; } + + try + { + result = eval.start_evaluate(eval_state, op, apply); + } + catch( const fc::exception& e ) + { + evaluation_exception = e.dynamic_copy_exception(); + } + + while( observer_count > 0 ) + { + --observer_count; + const auto& obs = eval_observers[observer_count]; + try + { + if( evaluation_exception ) + obs->post_evaluate(eval_state, op, apply, &eval, result); + else + obs->evaluation_failed(eval_state, op, apply, &eval, result); + } + catch( const fc::exception& e ) + { + elog( "suppressed exception in observer post method:\n${e}", ( "e", e.to_detail_string() ) ); + } + } + + if( evaluation_exception ) + evaluation_exception->dynamic_rethrow_exception(); + return result; + } }; template class evaluator : public generic_evaluator { - public: - virtual int get_type()const override { return operation::tag::value; } + public: + virtual int get_type()const override { return operation::tag::value; } - virtual operation_result evaluate( const operation& o ) final override - { - auto* eval = static_cast(this); - const auto& op = o.get(); + virtual operation_result evaluate(const operation& o) final override + { + auto* eval = static_cast(this); + const auto& op = o.get(); - prepare_fee(op.fee_payer(), op.fee); - FC_ASSERT( core_fee_paid >= op.calculate_fee(db().current_fee_schedule()) ); + prepare_fee(op.fee_payer(), op.fee); + FC_ASSERT( core_fee_paid >= db().current_fee_schedule().calculate_fee( op ).amount, + "Insufficient Fee Paid", + ("core_fee_paid",core_fee_paid)("required",db().current_fee_schedule().calculate_fee( op ).amount) ); - return eval->do_evaluate( op ); - } - virtual operation_result apply( const operation& o ) final override - { - auto* eval = static_cast(this); - const auto& op = o.get(); + return eval->do_evaluate(op); + } + virtual operation_result apply(const operation& o) final override + { + auto* eval = static_cast(this); + const auto& op = o.get(); - pay_fee(); + pay_fee(); - auto result = eval->do_apply( op ); + auto result = eval->do_apply(op); - db().adjust_balance(op.fee_payer(), -fee_from_account); + db().adjust_balance(op.fee_payer(), -fee_from_account); - return result; - } + return result; + } }; } } diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index 3fb75c4d..b18859a2 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -18,96 +18,207 @@ #pragma once #include +#include + +#define GRAPHENE_ASSERT( expr, exc_type, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( !(expr) ) \ + FC_THROW_EXCEPTION( exc_type, FORMAT, __VA_ARGS__ ); \ + FC_MULTILINE_MACRO_END + + +#define GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( op_name ) \ + FC_DECLARE_DERIVED_EXCEPTION( \ + op_name ## _validate_exception, \ + graphene::chain::operation_validate_exception, \ + 3040000 + 100 * operation::tag< op_name ## _operation >::value, \ + #op_name "_operation validation exception" \ + ) \ + FC_DECLARE_DERIVED_EXCEPTION( \ + op_name ## _evaluate_exception, \ + graphene::chain::operation_evaluate_exception, \ + 3050000 + 100 * operation::tag< op_name ## _operation >::value, \ + #op_name "_operation evaluation exception" \ + ) + +#define GRAPHENE_DECLARE_OP_VALIDATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \ + FC_DECLARE_DERIVED_EXCEPTION( \ + op_name ## _ ## exc_name, \ + graphene::chain::op_name ## _validate_exception, \ + 3040000 + 100 * operation::tag< op_name ## _operation >::value \ + + seqnum, \ + msg \ + ) + +#define GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \ + FC_DECLARE_DERIVED_EXCEPTION( \ + op_name ## _ ## exc_name, \ + graphene::chain::op_name ## _evaluate_exception, \ + 3050000 + 100 * operation::tag< op_name ## _operation >::value \ + + seqnum, \ + msg \ + ) namespace graphene { namespace chain { - // registered in chain_database.cpp - FC_DECLARE_EXCEPTION( chain_exception, 30000, "Blockchain Exception" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, graphene::chain::chain_exception, 30001, "invalid pts address" ); - FC_DECLARE_DERIVED_EXCEPTION( addition_overflow, graphene::chain::chain_exception, 30002, "addition overflow" ); - FC_DECLARE_DERIVED_EXCEPTION( subtraction_overflow, graphene::chain::chain_exception, 30003, "subtraction overflow" ); - FC_DECLARE_DERIVED_EXCEPTION( asset_type_mismatch, graphene::chain::chain_exception, 30004, "asset/price mismatch" ); - FC_DECLARE_DERIVED_EXCEPTION( unsupported_chain_operation, graphene::chain::chain_exception, 30005, "unsupported chain operation" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction, graphene::chain::chain_exception, 30006, "unknown transaction" ); - FC_DECLARE_DERIVED_EXCEPTION( duplicate_transaction, graphene::chain::chain_exception, 30007, "duplicate transaction" ); - FC_DECLARE_DERIVED_EXCEPTION( zero_amount, graphene::chain::chain_exception, 30008, "zero amount" ); - FC_DECLARE_DERIVED_EXCEPTION( zero_price, graphene::chain::chain_exception, 30009, "zero price" ); - FC_DECLARE_DERIVED_EXCEPTION( asset_divide_by_self, graphene::chain::chain_exception, 30010, "asset divide by self" ); - FC_DECLARE_DERIVED_EXCEPTION( asset_divide_by_zero, graphene::chain::chain_exception, 30011, "asset divide by zero" ); - FC_DECLARE_DERIVED_EXCEPTION( new_database_version, graphene::chain::chain_exception, 30012, "new database version" ); - FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block, graphene::chain::chain_exception, 30013, "unlinkable block" ); - FC_DECLARE_DERIVED_EXCEPTION( price_out_of_range, graphene::chain::chain_exception, 30014, "price out of range" ); + FC_DECLARE_EXCEPTION( chain_exception, 3000000, "blockchain exception" ) + FC_DECLARE_DERIVED_EXCEPTION( database_query_exception, graphene::chain::chain_exception, 3010000, "database query exception" ) + FC_DECLARE_DERIVED_EXCEPTION( block_validate_exception, graphene::chain::chain_exception, 3020000, "block validation exception" ) + FC_DECLARE_DERIVED_EXCEPTION( transaction_exception, graphene::chain::chain_exception, 3030000, "transaction validation exception" ) + FC_DECLARE_DERIVED_EXCEPTION( operation_validate_exception, graphene::chain::chain_exception, 3040000, "operation validation exception" ) + FC_DECLARE_DERIVED_EXCEPTION( operation_evaluate_exception, graphene::chain::chain_exception, 3050000, "operation evaluation exception" ) + FC_DECLARE_DERIVED_EXCEPTION( utility_exception, graphene::chain::chain_exception, 3060000, "utility method exception" ) - FC_DECLARE_DERIVED_EXCEPTION( block_numbers_not_sequential, graphene::chain::chain_exception, 30015, "block numbers not sequential" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_previous_block_id, graphene::chain::chain_exception, 30016, "invalid previous block" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_block_time, graphene::chain::chain_exception, 30017, "invalid block time" ); - FC_DECLARE_DERIVED_EXCEPTION( time_in_past, graphene::chain::chain_exception, 30018, "time is in the past" ); - FC_DECLARE_DERIVED_EXCEPTION( time_in_future, graphene::chain::chain_exception, 30019, "time is in the future" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_block_digest, graphene::chain::chain_exception, 30020, "invalid block digest" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_delegate_signee, graphene::chain::chain_exception, 30021, "invalid delegate signee" ); - FC_DECLARE_DERIVED_EXCEPTION( failed_checkpoint_verification, graphene::chain::chain_exception, 30022, "failed checkpoint verification" ); - FC_DECLARE_DERIVED_EXCEPTION( wrong_chain_id, graphene::chain::chain_exception, 30023, "wrong chain id" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_block, graphene::chain::chain_exception, 30024, "unknown block" ); - FC_DECLARE_DERIVED_EXCEPTION( block_older_than_undo_history, graphene::chain::chain_exception, 30025, "block is older than our undo history allows us to process" ); + FC_DECLARE_DERIVED_EXCEPTION( tx_missing_active_auth, graphene::chain::transaction_exception, 3030001, "missing required active authority" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_missing_owner_auth, graphene::chain::transaction_exception, 3030002, "missing required owner authority" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_missing_other_auth, graphene::chain::transaction_exception, 3030003, "missing required other authority" ) + //FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_authority, graphene::chain::transaction_exception, 3030004, "irrelevant authority" ) - FC_DECLARE_EXCEPTION( evaluation_error, 31000, "Evaluation Error" ); - FC_DECLARE_DERIVED_EXCEPTION( negative_deposit, graphene::chain::evaluation_error, 31001, "negative deposit" ); - FC_DECLARE_DERIVED_EXCEPTION( not_a_delegate, graphene::chain::evaluation_error, 31002, "not a delegate" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_balance_record, graphene::chain::evaluation_error, 31003, "unknown balance record" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_funds, graphene::chain::evaluation_error, 31004, "insufficient funds" ); - FC_DECLARE_DERIVED_EXCEPTION( missing_signature, graphene::chain::evaluation_error, 31005, "missing signature" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_claim_password, graphene::chain::evaluation_error, 31006, "invalid claim password" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_withdraw_condition, graphene::chain::evaluation_error, 31007, "invalid withdraw condition" ); - FC_DECLARE_DERIVED_EXCEPTION( negative_withdraw, graphene::chain::evaluation_error, 31008, "negative withdraw" ); - FC_DECLARE_DERIVED_EXCEPTION( not_an_active_delegate, graphene::chain::evaluation_error, 31009, "not an active delegate" ); - FC_DECLARE_DERIVED_EXCEPTION( expired_transaction, graphene::chain::evaluation_error, 31010, "expired transaction" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_transaction_expiration, graphene::chain::evaluation_error, 31011, "invalid transaction expiration" ); - FC_DECLARE_DERIVED_EXCEPTION( oversized_transaction, graphene::chain::evaluation_error, 31012, "transaction exceeded the maximum transaction size" ); + FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, graphene::chain::utility_exception, 3060001, "invalid pts address" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, graphene::chain::chain_exception, 37006, "insufficient feeds" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_account_name, graphene::chain::evaluation_error, 32001, "invalid account name" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_account_id, graphene::chain::evaluation_error, 32002, "unknown account id" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_account_name, graphene::chain::evaluation_error, 32003, "unknown account name" ); - FC_DECLARE_DERIVED_EXCEPTION( missing_parent_account_signature, graphene::chain::evaluation_error, 32004, "missing parent account signature" ); - FC_DECLARE_DERIVED_EXCEPTION( parent_account_retracted, graphene::chain::evaluation_error, 32005, "parent account retracted" ); - FC_DECLARE_DERIVED_EXCEPTION( account_expired, graphene::chain::evaluation_error, 32006, "account expired" ); - FC_DECLARE_DERIVED_EXCEPTION( account_already_registered, graphene::chain::evaluation_error, 32007, "account already registered" ); - FC_DECLARE_DERIVED_EXCEPTION( account_key_in_use, graphene::chain::evaluation_error, 32008, "account key already in use" ); - FC_DECLARE_DERIVED_EXCEPTION( account_retracted, graphene::chain::evaluation_error, 32009, "account retracted" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_parent_account_name, graphene::chain::evaluation_error, 32010, "unknown parent account name" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_delegate_slate, graphene::chain::evaluation_error, 32011, "unknown delegate slate" ); - FC_DECLARE_DERIVED_EXCEPTION( too_may_delegates_in_slate, graphene::chain::evaluation_error, 32012, "too many delegates in slate" ); - FC_DECLARE_DERIVED_EXCEPTION( pay_balance_remaining, graphene::chain::evaluation_error, 32013, "pay balance remaining" ); + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( transfer ); - FC_DECLARE_DERIVED_EXCEPTION( not_a_delegate_signature, graphene::chain::evaluation_error, 33002, "not delegates signature" ); + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( from_account_not_whitelisted, transfer, 1, "owner mismatch" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( to_account_not_whitelisted, transfer, 2, "owner mismatch" ) - FC_DECLARE_DERIVED_EXCEPTION( invalid_precision, graphene::chain::evaluation_error, 35001, "invalid precision" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_symbol, graphene::chain::evaluation_error, 35002, "invalid asset symbol" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_asset_id, graphene::chain::evaluation_error, 35003, "unknown asset id" ); - FC_DECLARE_DERIVED_EXCEPTION( asset_symbol_in_use, graphene::chain::evaluation_error, 35004, "asset symbol in use" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_amount, graphene::chain::evaluation_error, 35005, "invalid asset amount" ); - FC_DECLARE_DERIVED_EXCEPTION( negative_issue, graphene::chain::evaluation_error, 35006, "negative issue" ); - FC_DECLARE_DERIVED_EXCEPTION( over_issue, graphene::chain::evaluation_error, 35007, "over issue" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_asset_symbol, graphene::chain::evaluation_error, 35008, "unknown asset symbol" ); - FC_DECLARE_DERIVED_EXCEPTION( asset_id_in_use, graphene::chain::evaluation_error, 35009, "asset id in use" ); - FC_DECLARE_DERIVED_EXCEPTION( not_user_issued, graphene::chain::evaluation_error, 35010, "not user issued" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_name, graphene::chain::evaluation_error, 35011, "invalid asset name" ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_cancel ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( call_order_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_whitelist ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_upgrade ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_transfer ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update_bitasset ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update_feed_producers ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_issue ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_reserve ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_fund_fee_pool ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_settle ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_global_settle ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_publish_feed ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( committee_member_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( witness_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( witness_withdraw_pay ); - FC_DECLARE_DERIVED_EXCEPTION( delegate_vote_limit, graphene::chain::evaluation_error, 36001, "delegate_vote_limit" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_fee, graphene::chain::evaluation_error, 36002, "insufficient fee" ); - FC_DECLARE_DERIVED_EXCEPTION( negative_fee, graphene::chain::evaluation_error, 36003, "negative fee" ); - FC_DECLARE_DERIVED_EXCEPTION( missing_deposit, graphene::chain::evaluation_error, 36004, "missing deposit" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_relay_fee, graphene::chain::evaluation_error, 36005, "insufficient relay fee" ); + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_create ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_market, graphene::chain::evaluation_error, 37001, "invalid market" ); - FC_DECLARE_DERIVED_EXCEPTION( unknown_market_order, graphene::chain::evaluation_error, 37002, "unknown market order" ); - FC_DECLARE_DERIVED_EXCEPTION( shorting_base_shares, graphene::chain::evaluation_error, 37003, "shorting base shares" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_collateral, graphene::chain::evaluation_error, 37004, "insufficient collateral" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_depth, graphene::chain::evaluation_error, 37005, "insufficient depth" ); - FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, graphene::chain::evaluation_error, 37006, "insufficient feeds" ); - FC_DECLARE_DERIVED_EXCEPTION( invalid_feed_price, graphene::chain::evaluation_error, 37007, "invalid feed price" ); + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( review_period_required, proposal_create, 1, "review_period required" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( review_period_insufficient, proposal_create, 2, "review_period insufficient" ) - FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_overflow, graphene::chain::evaluation_error, 38001, "price multiplication overflow" ); - FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_underflow, graphene::chain::evaluation_error, 38002, "price multiplication underflow" ); - FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_undefined, graphene::chain::evaluation_error, 38003, "price multiplication undefined product 0*inf" ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_delete ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_claim ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_delete ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( fill_order ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( global_parameters_update ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( vesting_balance_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( vesting_balance_withdraw ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( worker_create ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( custom ); + //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( assert ); + + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( balance_claim ); + + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( claimed_too_often, balance_claim, 1, "balance claimed too often" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( invalid_claim_amount, balance_claim, 2, "invalid claim amount" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, balance_claim, 3, "owner mismatch" ) + + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( override_transfer ); + + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( not_permitted, override_transfer, 1, "not permitted" ) + + /* + FC_DECLARE_DERIVED_EXCEPTION( addition_overflow, graphene::chain::chain_exception, 30002, "addition overflow" ) + FC_DECLARE_DERIVED_EXCEPTION( subtraction_overflow, graphene::chain::chain_exception, 30003, "subtraction overflow" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_type_mismatch, graphene::chain::chain_exception, 30004, "asset/price mismatch" ) + FC_DECLARE_DERIVED_EXCEPTION( unsupported_chain_operation, graphene::chain::chain_exception, 30005, "unsupported chain operation" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction, graphene::chain::chain_exception, 30006, "unknown transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( duplicate_transaction, graphene::chain::chain_exception, 30007, "duplicate transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( zero_amount, graphene::chain::chain_exception, 30008, "zero amount" ) + FC_DECLARE_DERIVED_EXCEPTION( zero_price, graphene::chain::chain_exception, 30009, "zero price" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_divide_by_self, graphene::chain::chain_exception, 30010, "asset divide by self" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_divide_by_zero, graphene::chain::chain_exception, 30011, "asset divide by zero" ) + FC_DECLARE_DERIVED_EXCEPTION( new_database_version, graphene::chain::chain_exception, 30012, "new database version" ) + FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block, graphene::chain::chain_exception, 30013, "unlinkable block" ) + FC_DECLARE_DERIVED_EXCEPTION( price_out_of_range, graphene::chain::chain_exception, 30014, "price out of range" ) + + FC_DECLARE_DERIVED_EXCEPTION( block_numbers_not_sequential, graphene::chain::chain_exception, 30015, "block numbers not sequential" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_previous_block_id, graphene::chain::chain_exception, 30016, "invalid previous block" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_block_time, graphene::chain::chain_exception, 30017, "invalid block time" ) + FC_DECLARE_DERIVED_EXCEPTION( time_in_past, graphene::chain::chain_exception, 30018, "time is in the past" ) + FC_DECLARE_DERIVED_EXCEPTION( time_in_future, graphene::chain::chain_exception, 30019, "time is in the future" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_block_digest, graphene::chain::chain_exception, 30020, "invalid block digest" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_committee_member_signee, graphene::chain::chain_exception, 30021, "invalid committee_member signee" ) + FC_DECLARE_DERIVED_EXCEPTION( failed_checkpoint_verification, graphene::chain::chain_exception, 30022, "failed checkpoint verification" ) + FC_DECLARE_DERIVED_EXCEPTION( wrong_chain_id, graphene::chain::chain_exception, 30023, "wrong chain id" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_block, graphene::chain::chain_exception, 30024, "unknown block" ) + FC_DECLARE_DERIVED_EXCEPTION( block_older_than_undo_history, graphene::chain::chain_exception, 30025, "block is older than our undo history allows us to process" ) + + FC_DECLARE_EXCEPTION( evaluation_error, 31000, "Evaluation Error" ) + FC_DECLARE_DERIVED_EXCEPTION( negative_deposit, graphene::chain::evaluation_error, 31001, "negative deposit" ) + FC_DECLARE_DERIVED_EXCEPTION( not_a_committee_member, graphene::chain::evaluation_error, 31002, "not a committee_member" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_balance_record, graphene::chain::evaluation_error, 31003, "unknown balance record" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_funds, graphene::chain::evaluation_error, 31004, "insufficient funds" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_signature, graphene::chain::evaluation_error, 31005, "missing signature" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_claim_password, graphene::chain::evaluation_error, 31006, "invalid claim password" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_withdraw_condition, graphene::chain::evaluation_error, 31007, "invalid withdraw condition" ) + FC_DECLARE_DERIVED_EXCEPTION( negative_withdraw, graphene::chain::evaluation_error, 31008, "negative withdraw" ) + FC_DECLARE_DERIVED_EXCEPTION( not_an_active_committee_member, graphene::chain::evaluation_error, 31009, "not an active committee_member" ) + FC_DECLARE_DERIVED_EXCEPTION( expired_transaction, graphene::chain::evaluation_error, 31010, "expired transaction" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_transaction_expiration, graphene::chain::evaluation_error, 31011, "invalid transaction expiration" ) + FC_DECLARE_DERIVED_EXCEPTION( oversized_transaction, graphene::chain::evaluation_error, 31012, "transaction exceeded the maximum transaction size" ) + + FC_DECLARE_DERIVED_EXCEPTION( invalid_account_name, graphene::chain::evaluation_error, 32001, "invalid account name" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_account_id, graphene::chain::evaluation_error, 32002, "unknown account id" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_account_name, graphene::chain::evaluation_error, 32003, "unknown account name" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_parent_account_signature, graphene::chain::evaluation_error, 32004, "missing parent account signature" ) + FC_DECLARE_DERIVED_EXCEPTION( parent_account_retracted, graphene::chain::evaluation_error, 32005, "parent account retracted" ) + FC_DECLARE_DERIVED_EXCEPTION( account_expired, graphene::chain::evaluation_error, 32006, "account expired" ) + FC_DECLARE_DERIVED_EXCEPTION( account_already_registered, graphene::chain::evaluation_error, 32007, "account already registered" ) + FC_DECLARE_DERIVED_EXCEPTION( account_key_in_use, graphene::chain::evaluation_error, 32008, "account key already in use" ) + FC_DECLARE_DERIVED_EXCEPTION( account_retracted, graphene::chain::evaluation_error, 32009, "account retracted" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_parent_account_name, graphene::chain::evaluation_error, 32010, "unknown parent account name" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_committee_member_slate, graphene::chain::evaluation_error, 32011, "unknown committee_member slate" ) + FC_DECLARE_DERIVED_EXCEPTION( too_may_committee_members_in_slate, graphene::chain::evaluation_error, 32012, "too many committee_members in slate" ) + FC_DECLARE_DERIVED_EXCEPTION( pay_balance_remaining, graphene::chain::evaluation_error, 32013, "pay balance remaining" ) + + FC_DECLARE_DERIVED_EXCEPTION( not_a_committee_member_signature, graphene::chain::evaluation_error, 33002, "not committee_members signature" ) + + FC_DECLARE_DERIVED_EXCEPTION( invalid_precision, graphene::chain::evaluation_error, 35001, "invalid precision" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_symbol, graphene::chain::evaluation_error, 35002, "invalid asset symbol" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_asset_id, graphene::chain::evaluation_error, 35003, "unknown asset id" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_symbol_in_use, graphene::chain::evaluation_error, 35004, "asset symbol in use" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_amount, graphene::chain::evaluation_error, 35005, "invalid asset amount" ) + FC_DECLARE_DERIVED_EXCEPTION( negative_issue, graphene::chain::evaluation_error, 35006, "negative issue" ) + FC_DECLARE_DERIVED_EXCEPTION( over_issue, graphene::chain::evaluation_error, 35007, "over issue" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_asset_symbol, graphene::chain::evaluation_error, 35008, "unknown asset symbol" ) + FC_DECLARE_DERIVED_EXCEPTION( asset_id_in_use, graphene::chain::evaluation_error, 35009, "asset id in use" ) + FC_DECLARE_DERIVED_EXCEPTION( not_user_issued, graphene::chain::evaluation_error, 35010, "not user issued" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_asset_name, graphene::chain::evaluation_error, 35011, "invalid asset name" ) + + FC_DECLARE_DERIVED_EXCEPTION( committee_member_vote_limit, graphene::chain::evaluation_error, 36001, "committee_member_vote_limit" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_fee, graphene::chain::evaluation_error, 36002, "insufficient fee" ) + FC_DECLARE_DERIVED_EXCEPTION( negative_fee, graphene::chain::evaluation_error, 36003, "negative fee" ) + FC_DECLARE_DERIVED_EXCEPTION( missing_deposit, graphene::chain::evaluation_error, 36004, "missing deposit" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_relay_fee, graphene::chain::evaluation_error, 36005, "insufficient relay fee" ) + + FC_DECLARE_DERIVED_EXCEPTION( invalid_market, graphene::chain::evaluation_error, 37001, "invalid market" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_market_order, graphene::chain::evaluation_error, 37002, "unknown market order" ) + FC_DECLARE_DERIVED_EXCEPTION( shorting_base_shares, graphene::chain::evaluation_error, 37003, "shorting base shares" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_collateral, graphene::chain::evaluation_error, 37004, "insufficient collateral" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_depth, graphene::chain::evaluation_error, 37005, "insufficient depth" ) + FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, graphene::chain::evaluation_error, 37006, "insufficient feeds" ) + FC_DECLARE_DERIVED_EXCEPTION( invalid_feed_price, graphene::chain::evaluation_error, 37007, "invalid feed price" ) + + FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_overflow, graphene::chain::evaluation_error, 38001, "price multiplication overflow" ) + FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_underflow, graphene::chain::evaluation_error, 38002, "price multiplication underflow" ) + FC_DECLARE_DERIVED_EXCEPTION( price_multiplication_undefined, graphene::chain::evaluation_error, 38003, "price multiplication undefined product 0*inf" ) + */ } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/fork_database.hpp b/libraries/chain/include/graphene/chain/fork_database.hpp index 3df28683..cf79f955 100644 --- a/libraries/chain/include/graphene/chain/fork_database.hpp +++ b/libraries/chain/include/graphene/chain/fork_database.hpp @@ -16,8 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include #include #include @@ -58,7 +57,7 @@ namespace graphene { namespace chain { class fork_database { public: - typedef vector branch_type; + typedef vector branch_type; fork_database(); void reset(); @@ -69,7 +68,7 @@ namespace graphene { namespace chain { 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; - shared_ptr push_block( signed_block b ); + shared_ptr push_block(const signed_block& b ); shared_ptr head()const { return _head; } void pop_block(); @@ -81,8 +80,8 @@ namespace graphene { namespace chain { pair< branch_type, branch_type > fetch_branch_from( block_id_type first, block_id_type second )const; - struct block_id{}; - struct block_num{}; + struct block_id; + struct block_num; typedef multi_index_container< item_ptr, indexed_by< diff --git a/libraries/chain/include/graphene/chain/genesis_state.hpp b/libraries/chain/include/graphene/chain/genesis_state.hpp new file mode 100644 index 00000000..40cef3d6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/genesis_state.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include + +#include +#include + +namespace graphene { namespace chain { +using std::string; +using std::vector; + +struct genesis_state_type { + struct initial_account_type { + initial_account_type(const string& name = string(), + const public_key_type& owner_key = public_key_type(), + const public_key_type& active_key = public_key_type(), + bool is_lifetime_member = false) + : name(name), + owner_key(owner_key), + active_key(active_key == public_key_type()? owner_key : active_key), + is_lifetime_member(is_lifetime_member) + {} + string name; + public_key_type owner_key; + public_key_type active_key; + bool is_lifetime_member = false; + }; + struct initial_asset_type { + string symbol; + string description; + uint8_t precision; + string issuer_name; + share_type max_supply; + uint16_t market_fee_percent; + share_type max_market_fee; + uint16_t issuer_permissions; + uint16_t flags; + + struct initial_bitasset_options { + uint32_t feed_lifetime_sec; + uint8_t minimum_feeds; + uint32_t force_settlement_delay_sec; + uint16_t force_settlement_offset_percent; + uint16_t maximum_force_settlement_volume; + string backing_asset_symbol; + + struct initial_collateral_position { + address owner; + share_type collateral; + share_type debt; + }; + + uint16_t maintenance_collateral_ratio; + vector collateral_records; + }; + optional bitasset_opts; + + share_type initial_accumulated_fees; + }; + struct initial_balance_type { + address owner; + string asset_symbol; + share_type amount; + }; + struct initial_vesting_balance_type { + address owner; + string asset_symbol; + share_type amount; + time_point_sec begin_timestamp; + uint32_t vesting_duration_seconds = 0; + share_type begin_balance; + }; + struct initial_witness_type { + /// Must correspond to one of the initial accounts + string owner_name; + public_key_type block_signing_key; + }; + struct initial_committee_member_type { + /// Must correspond to one of the initial accounts + string owner_name; + }; + struct initial_worker_type { + /// Must correspond to one of the initial accounts + string owner_name; + share_type daily_pay; + }; + + time_point_sec initial_timestamp; + chain_parameters initial_parameters; + vector initial_accounts; + vector initial_assets; + vector initial_balances; + vector initial_vesting_balances; + int initial_active_witnesses = GRAPHENE_DEFAULT_NUM_WITNESSES; + vector initial_witness_candidates; + vector initial_committee_candidates; + vector initial_worker_candidates; +}; +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::genesis_state_type::initial_account_type, (name)(owner_key)(active_key)(is_lifetime_member)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_asset_type, + (symbol)(description)(precision)(issuer_name)(max_supply)(market_fee_percent) + (issuer_permissions)(flags)(bitasset_opts)(initial_accumulated_fees)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_asset_type::initial_bitasset_options, + (feed_lifetime_sec)(minimum_feeds)(force_settlement_delay_sec)(force_settlement_offset_percent) + (maximum_force_settlement_volume)(backing_asset_symbol)(maintenance_collateral_ratio)(collateral_records)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_asset_type::initial_bitasset_options::initial_collateral_position, + (collateral)(debt)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_balance_type, + (owner)(asset_symbol)(amount)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_vesting_balance_type, + (owner)(asset_symbol)(amount)(begin_timestamp)(vesting_duration_seconds)(begin_balance)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_witness_type, (owner_name)(block_signing_key)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_committee_member_type, (owner_name)) + +FC_REFLECT(graphene::chain::genesis_state_type::initial_worker_type, (owner_name)(daily_pay)) + +FC_REFLECT(graphene::chain::genesis_state_type, + (initial_timestamp)(initial_parameters)(initial_accounts)(initial_assets)(initial_balances) + (initial_vesting_balances)(initial_active_witnesses)(initial_witness_candidates) + (initial_committee_candidates)(initial_worker_candidates)) diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index c69a9169..4b83b993 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -16,20 +16,21 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include + +#include #include -#include -#include #include namespace graphene { namespace chain { /** * @class global_property_object - * @brief Maintains global state information (delegate list, current fees) + * @brief Maintains global state information (committee_member list, current fees) * @ingroup object * @ingroup implementation * - * This is an implementation detail. The values here are set by delegates to tune the blockchain parameters. + * This is an implementation detail. The values here are set by committee_members to tune the blockchain parameters. */ class global_property_object : public graphene::db::abstract_object { @@ -41,7 +42,7 @@ namespace graphene { namespace chain { optional pending_parameters; uint32_t next_available_vote_id = 0; - vector active_delegates; // updated once per maintenance interval + vector active_committee_members; // updated once per maintenance interval flat_set active_witnesses; // updated once per maintenance interval // n.b. witness scheduling is done by witness_schedule object flat_set witness_accounts; // updated once per maintenance interval @@ -55,7 +56,7 @@ namespace graphene { namespace chain { /** * @class dynamic_global_property_object - * @brief Maintains global state information (delegate list, current fees) + * @brief Maintains global state information (committee_member list, current fees) * @ingroup object * @ingroup implementation * @@ -76,10 +77,17 @@ namespace graphene { namespace chain { time_point_sec next_maintenance_time; time_point_sec last_budget_time; share_type witness_budget; + uint32_t accounts_registered_this_interval; + /** if the interval changes then how we calculate witness participation will + * also change. Normally witness participation is defined as % of blocks + * produced in the last round which is calculated by dividing the delta + * time between block N and N-NUM_WITNESSES by the block interval to calculate + * the number of blocks produced. + */ + uint32_t first_maintenance_block_with_current_interval = 0; }; }} - FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::db::object), (random) (head_block_number) @@ -88,13 +96,15 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene:: (current_witness) (next_maintenance_time) (witness_budget) + (accounts_registered_this_interval) + (first_maintenance_block_with_current_interval) ) FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::object), (parameters) (pending_parameters) (next_available_vote_id) - (active_delegates) + (active_committee_members) (active_witnesses) (chain_id) ) diff --git a/libraries/chain/include/graphene/chain/limit_order_evaluator.hpp b/libraries/chain/include/graphene/chain/limit_order_evaluator.hpp deleted file mode 100644 index 2560b58c..00000000 --- a/libraries/chain/include/graphene/chain/limit_order_evaluator.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once -#include -#include -#include - -namespace graphene { namespace chain { - - class limit_order_create_evaluator : public evaluator - { - public: - typedef limit_order_create_operation operation_type; - - object_id_type do_evaluate( const limit_order_create_operation& o ); - object_id_type do_apply( const limit_order_create_operation& o ); - - asset calculate_market_fee( const asset_object* aobj, const asset& trade_amount ); - - const limit_order_create_operation* _op = nullptr; - const account_object* _seller = nullptr; - const asset_object* _sell_asset = nullptr; - const asset_object* _receive_asset = nullptr; - }; - - class limit_order_cancel_evaluator : public evaluator - { - public: - typedef limit_order_cancel_operation operation_type; - - asset do_evaluate( const limit_order_cancel_operation& o ); - asset do_apply( const limit_order_cancel_operation& o ); - - const limit_order_object* _order; - }; - -} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/limit_order_object.hpp b/libraries/chain/include/graphene/chain/limit_order_object.hpp deleted file mode 100644 index f45bb3f6..00000000 --- a/libraries/chain/include/graphene/chain/limit_order_object.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once -#include -#include -#include -#include -#include - -namespace graphene { namespace chain { - using namespace graphene::db; - - /** - * @brief an offer to sell a amount of a asset at a specified exchange rate by a certain time - * @ingroup object - * @ingroup protocol - * @ingroup market - * - * This limit_order_objects are indexed by @ref expiration and is automatically deleted on the first block after expiration. - */ - class limit_order_object : public abstract_object - { - public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = limit_order_object_type; - - time_point_sec expiration; - account_id_type seller; - share_type for_sale; ///< asset id is sell_price.base.asset_id - price sell_price; - - asset amount_for_sale()const { return asset( for_sale, sell_price.base.asset_id ); } - asset amount_to_receive()const { return amount_for_sale() * sell_price; } - }; - - struct by_id; - struct by_price; - struct by_expiration; - typedef multi_index_container< - limit_order_object, - indexed_by< - hashed_unique< tag, - member< object, object_id_type, &object::id > >, - ordered_non_unique< tag, member< limit_order_object, time_point_sec, &limit_order_object::expiration> >, - ordered_unique< tag, - composite_key< limit_order_object, - member< limit_order_object, price, &limit_order_object::sell_price>, - member< object, object_id_type, &object::id> - >, - composite_key_compare< std::greater, std::less > - > - > - > limit_order_multi_index_type; - - typedef generic_index limit_order_index; - -} } - -FC_REFLECT_DERIVED( graphene::chain::limit_order_object, - (graphene::db::object), - (expiration)(seller)(for_sale)(sell_price) - ) - diff --git a/libraries/chain/include/graphene/chain/short_order_object.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp similarity index 52% rename from libraries/chain/include/graphene/chain/short_order_object.hpp rename to libraries/chain/include/graphene/chain/market_evaluator.hpp index acbc59d8..7e5b7258 100644 --- a/libraries/chain/include/graphene/chain/short_order_object.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -1,72 +1,57 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Copyright (c) 2015, Cryptonomex, Inc. All rights reserved. */ #pragma once -#include -#include -#include -#include -#include -#include +#include +#include +#include namespace graphene { namespace chain { - using namespace graphene::db; + + using namespace graphene::db; /** - * @class short_order_object - * @brief maintains state about requests to short an asset + * @brief an offer to sell a amount of a asset at a specified exchange rate by a certain time + * @ingroup object + * @ingroup protocol + * @ingroup market * - * Short orders are only valid if their sell price is above the - * fair market value of the asset at the feed price. Users can - * place shorts at any price but their order will be ignored - * beyond the feed. - * - * All shorts have a minimial initial collateral ratio requirement that is - * defined by the network, but individuals may choose to have a higher - * initial collateral to avoid the risk of being margin called. - * - * All shorts have a maintenance collateral ratio that must be kept or - * the network will automatically cover the short order. Users can - * specify a higher maintenance collateral ratio as a form of "stop loss" - * and to potentially get ahead of a short squeeze. + * This limit_order_objects are indexed by @ref expiration and is automatically deleted on the first block after expiration. */ - class short_order_object : public abstract_object + class limit_order_object : public abstract_object { public: static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = short_order_object_type; + static const uint8_t type_id = limit_order_object_type; time_point_sec expiration; account_id_type seller; - share_type for_sale; - share_type available_collateral; ///< asset_id == sell_price.quote.asset_id - price sell_price; ///< the price the short is currently at = min(limit_price,feed) - price call_price; ///< the price that will be used to trigger margin calls after match, must be 1:1 if prediction market - uint16_t initial_collateral_ratio = 0; ///< may be higher than the network requires - uint16_t maintenance_collateral_ratio = 0; ///< may optionally be higher than the network requires + share_type for_sale; ///< asset id is sell_price.base.asset_id + price sell_price; - asset get_collateral()const { return asset( available_collateral, sell_price.quote.asset_id ); } - /** if the initial_collateral_ratio is 0, then this is a prediction market order which means the - * amount for sale depends upon price and available collateral. - */ asset amount_for_sale()const { return asset( for_sale, sell_price.base.asset_id ); } asset amount_to_receive()const { return amount_for_sale() * sell_price; } }; + struct by_id; + struct by_price; + struct by_expiration; + typedef multi_index_container< + limit_order_object, + indexed_by< + hashed_unique< tag, + member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, member< limit_order_object, time_point_sec, &limit_order_object::expiration> >, + ordered_unique< tag, + composite_key< limit_order_object, + member< limit_order_object, price, &limit_order_object::sell_price>, + member< object, object_id_type, &object::id> + >, + composite_key_compare< std::greater, std::less > + > + > + > limit_order_multi_index_type; + + typedef generic_index limit_order_index; + /** * @class call_order_object * @brief tracks debt and call price information @@ -86,13 +71,10 @@ namespace graphene { namespace chain { asset_id_type debt_type()const { return call_price.quote.asset_id; } price collateralization()const { return get_collateral() / get_debt(); } - void update_call_price() { call_price = price::call_price(get_debt(), get_collateral(), maintenance_collateral_ratio); } - account_id_type borrower; share_type collateral; ///< call_price.base.asset_id, access via get_collateral share_type debt; ///< call_price.quote.asset_id, access via get_collateral - price call_price; - uint16_t maintenance_collateral_ratio; + price call_price; ///< Debt / Collateral }; /** @@ -115,27 +97,9 @@ namespace graphene { namespace chain { { return balance.asset_id; } }; - struct by_id; - struct by_price; - struct by_account; - struct by_expiration; - struct by_collateral; - typedef multi_index_container< - short_order_object, - indexed_by< - hashed_unique< tag, - member< object, object_id_type, &object::id > >, - ordered_non_unique< tag, member< short_order_object, time_point_sec, &short_order_object::expiration> >, - ordered_unique< tag, - composite_key< short_order_object, - member< short_order_object, price, &short_order_object::sell_price>, - member< object, object_id_type, &object::id> - >, - composite_key_compare< std::greater, std::less > - > - > - > short_order_multi_index_type; - + struct by_collateral; + struct by_account; + struct by_price; typedef multi_index_container< call_order_object, indexed_by< @@ -163,7 +127,6 @@ namespace graphene { namespace chain { > > call_order_multi_index_type; - struct by_account; struct by_expiration; typedef multi_index_container< force_settlement_object, @@ -182,17 +145,62 @@ namespace graphene { namespace chain { > force_settlement_object_multi_index_type; - typedef generic_index short_order_index; typedef generic_index call_order_index; typedef generic_index force_settlement_index; + + + + + class limit_order_create_evaluator : public evaluator + { + public: + typedef limit_order_create_operation operation_type; + + void_result do_evaluate( const limit_order_create_operation& o ); + object_id_type do_apply( const limit_order_create_operation& o ); + + asset calculate_market_fee( const asset_object* aobj, const asset& trade_amount ); + + const limit_order_create_operation* _op = nullptr; + const account_object* _seller = nullptr; + const asset_object* _sell_asset = nullptr; + const asset_object* _receive_asset = nullptr; + }; + + class limit_order_cancel_evaluator : public evaluator + { + public: + typedef limit_order_cancel_operation operation_type; + + void_result do_evaluate( const limit_order_cancel_operation& o ); + asset do_apply( const limit_order_cancel_operation& o ); + + const limit_order_object* _order; + }; + + class call_order_update_evaluator : public evaluator + { + public: + typedef call_order_update_operation operation_type; + + void_result do_evaluate( const call_order_update_operation& o ); + void_result do_apply( const call_order_update_operation& o ); + + bool _closing_order = false; + const asset_object* _debt_asset = nullptr; + const account_object* _paying_account = nullptr; + const call_order_object* _order = nullptr; + const asset_bitasset_data_object* _bitasset_data = nullptr; + }; + } } // graphene::chain -FC_REFLECT_DERIVED( graphene::chain::short_order_object, (graphene::db::object), - (expiration)(seller)(for_sale)(available_collateral)(sell_price) - (call_price)(initial_collateral_ratio)(maintenance_collateral_ratio) +FC_REFLECT_DERIVED( graphene::chain::limit_order_object, + (graphene::db::object), + (expiration)(seller)(for_sale)(sell_price) ) FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object), - (borrower)(collateral)(debt)(call_price)(maintenance_collateral_ratio) ) + (borrower)(collateral)(debt)(call_price) ) FC_REFLECT( graphene::chain::force_settlement_object, (owner)(balance)(settlement_date) ) diff --git a/libraries/chain/include/graphene/chain/key_evaluator.hpp b/libraries/chain/include/graphene/chain/node_property_object.hpp similarity index 68% rename from libraries/chain/include/graphene/chain/key_evaluator.hpp rename to libraries/chain/include/graphene/chain/node_property_object.hpp index 79ecb2f4..92addff9 100644 --- a/libraries/chain/include/graphene/chain/key_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/node_property_object.hpp @@ -16,30 +16,26 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include +#include namespace graphene { namespace chain { - class key_create_evaluator : public evaluator + /** + * @brief Contains per-node database configuration. + * + * Transactions are evaluated differently based on per-node state. + * Settings here may change based on whether the node is syncing or up-to-date. + * Or whether the node is a witness node. Or if we're processing a + * transaction in a witness-signed block vs. a fresh transaction + * from the p2p network. Or configuration-specified tradeoffs of + * performance/hardfork resilience vs. paranoia. + */ + class node_property_object { public: - typedef key_create_operation operation_type; + node_property_object() : skip_flags(0) {} + ~node_property_object(){} - object_id_type do_evaluate( const key_create_operation& op ) - { - return object_id_type(); - } - - object_id_type do_apply( const key_create_operation& op ) - { - new_key_object = &db().create( [&]( key_object& obj ){ - obj.key_data = op.key_data; - }); - - return new_key_object->id; - } - - const key_object* new_key_object = nullptr; + uint32_t skip_flags; }; - } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/operation_history_object.hpp b/libraries/chain/include/graphene/chain/operation_history_object.hpp index 78c8f16a..644668ab 100644 --- a/libraries/chain/include/graphene/chain/operation_history_object.hpp +++ b/libraries/chain/include/graphene/chain/operation_history_object.hpp @@ -16,6 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include namespace graphene { namespace chain { diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp deleted file mode 100644 index b670be1d..00000000 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ /dev/null @@ -1,1668 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/* Copyright (C) Cryptonomex, Inc - All Rights Reserved - * - * All modifications become property of Cryptonomex, Inc. - * - **/ -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -namespace graphene { namespace chain { - - bool is_valid_symbol( const string& symbol ); - bool is_valid_name( const string& s ); - bool is_premium_name( const string& n ); - bool is_cheap_name( const string& n ); - - struct void_result{}; - typedef fc::static_variant operation_result; - - struct balance_accumulator - { - void adjust( account_id_type account, const asset& delta ) - { - balance[ std::make_pair(account, delta.asset_id) ] += delta.amount; - } - flat_map< pair, share_type > balance; - }; - - /** - * @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 - * - * @{ - */ - - /** - * @brief reserves a new ID to refer to a particular key or address. - * @ingroup operations - */ - struct key_create_operation - { - asset fee; - account_id_type fee_paying_account; - static_variant key_data; - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth(flat_set& active_auth_set , flat_set&)const; - share_type calculate_fee( const fee_schedule_type& k )const{ return k.key_create_fee; } - void validate()const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @ingroup operations - */ - struct account_create_operation - { - asset fee; - /// This account pays the fee. Must be a lifetime member. - account_id_type registrar; - - /// This account receives a portion of the fee split between registrar and referrer. Must be a member. - account_id_type referrer; - /// Of the fee split between registrar and referrer, this percentage goes to the referrer. The rest goes to the - /// registrar. - uint8_t referrer_percent = 0; - - string name; - authority owner; - authority active; - account_id_type voting_account; - object_id_type memo_key = key_id_type(); - - uint16_t num_witness = 0; - uint16_t num_committee = 0; - flat_set vote; - - account_id_type fee_payer()const { return registrar; } - void get_required_auth(flat_set& active_auth_set , flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief This operation is used to whitelist and blacklist accounts, primarily for transacting in whitelisted assets - * @ingroup operations - * - * Accounts can freely specify opinions about other accounts, in the form of either whitelisting or blacklisting - * them. This information is used in chain validation only to determine whether an account is authorized to transact - * in an asset type which enforces a whitelist, but third parties can use this information for other uses as well, - * as long as it does not conflict with the use of whitelisted assets. - * - * An asset which enforces a whitelist specifies a list of accounts to maintain its whitelist, and a list of - * accounts to maintain its blacklist. In order for a given account A to hold and transact in a whitelisted asset S, - * A must be whitelisted by at least one of S's whitelist_authorities and blacklisted by none of S's - * blacklist_authorities. If A receives a balance of S, and is later removed from the whitelist(s) which allowed it - * to hold S, or added to any blacklist S specifies as authoritative, A's balance of S will be frozen until A's - * authorization is reinstated. - * - * This operation requires authorizing_account's signature, but not account_to_list's. The fee is paid by - * authorizing_account. - */ - struct account_whitelist_operation - { - enum account_listing { - no_listing = 0x0, ///< No opinion is specified about this account - white_listed = 0x1, ///< This account is whitelisted, but not blacklisted - black_listed = 0x2, ///< This account is blacklisted, but not whitelisted - white_and_black_listed = white_listed | black_listed ///< This account is both whitelisted and blacklisted - }; - - /// Paid by authorizing_account - asset fee; - /// The account which is specifying an opinion of another account - account_id_type authorizing_account; - /// The account being opined about - account_id_type account_to_list; - /// The new white and blacklist status of account_to_list, as determined by authorizing_account - /// This is a bitfield using values defined in the account_listing enum - uint8_t new_listing; - - account_id_type fee_payer()const { return authorizing_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT(new_listing < 0x4); } - share_type calculate_fee(const fee_schedule_type& k)const { return k.account_whitelist_fee; } - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @ingroup operations - */ - struct account_update_operation - { - asset fee; - account_id_type account; - optional owner; - optional active; - optional voting_account; - optional memo_key; - optional> vote; - uint16_t num_witness = 0; - uint16_t num_committee = 0; - - account_id_type fee_payer()const { return account; } - void get_required_auth(flat_set& active_auth_set , flat_set& owner_auth_set)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Manage an account's membership status - * @ingroup operations - * - * This operation is used to upgrade an account to a member, or renew its subscription. If an account which is an - * unexpired annual subscription member publishes this operation with @ref upgrade_to_lifetime_member set to false, - * the account's membership expiration date will be pushed backward one year. If a basic account publishes it with - * @ref upgrade_to_lifetime_member set to false, the account will be upgraded to a subscription member with an - * expiration date one year after the processing time of this operation. - * - * Any account may use this operation to become a lifetime member by setting @ref upgrade_to_lifetime_member to - * true. Once an account has become a lifetime member, it may not use this operation anymore. - */ - struct account_upgrade_operation - { - asset fee; - /// The account to upgrade; must not already be a lifetime member - account_id_type account_to_upgrade; - /// If true, the account will be upgraded to a lifetime member; otherwise, it will add a year to the subscription - bool upgrade_to_lifetime_member = false; - - account_id_type fee_payer()const { return account_to_upgrade; } - void get_required_auth(flat_set& active_auth_set , flat_set&)const - { active_auth_set.insert(account_to_upgrade); } - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief transfers the account to another account while clearing the white list - * @ingroup operations - * - * In theory an account can be transferred by simply updating the authorities, but that kind - * of transfer lacks semantic meaning and is more often done to rotate keys without transferring - * ownership. This operation is used to indicate the legal transfer of title to this account and - * a break in the operation history. - * - * The account_id's owner/active/voting/memo authority should be set to new_owner - * - * This operation will clear the account's whitelist statuses, but not the blacklist statuses. - */ - struct account_transfer_operation - { - asset fee; - account_id_type account_id; - account_id_type new_owner; - - account_id_type fee_payer()const { return account_id; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Create a delegate object, as a bid to hold a delegate seat on the network. - * @ingroup operations - * - * Accounts which wish to become delegates may use this operation to create a delegate object which stakeholders may - * vote on to approve its position as a delegate. - */ - struct delegate_create_operation - { - asset fee; - /// The account which owns the delegate. This account pays the fee for this operation. - account_id_type delegate_account; - - account_id_type fee_payer()const { return delegate_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Create a witness object, as a bid to hold a witness position on the network. - * @ingroup operations - * - * Accounts which wish to become witnesses may use this operation to create a witness object which stakeholders may - * vote on to approve its position as a witness. - */ - struct witness_create_operation - { - asset fee; - /// The account which owns the delegate. This account pays the fee for this operation. - account_id_type witness_account; - key_id_type block_signing_key; - secret_hash_type initial_secret; - - account_id_type fee_payer()const { return witness_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @ingroup operations - * Used to move witness pay from accumulated_income to their account balance. - */ - struct witness_withdraw_pay_operation - { - asset fee; - /// The account to pay. Must match from_witness->witness_account. This account pays the fee for this operation. - account_id_type to_account; - witness_id_type from_witness; - share_type amount; - - account_id_type fee_payer()const { return to_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( to_account, amount ); - } - }; - - /** - * @brief Used by delegates to update the global parameters of the blockchain. - * @ingroup operations - * - * This operation allows the delegates to update the global parameters on the blockchain. These control various - * tunable aspects of the chain, including block and maintenance intervals, maximum data sizes, the fees charged by - * the network, etc. - * - * This operation may only be used in a proposed transaction, and a proposed transaction which contains this - * operation must have a review period specified in the current global parameters before it may be accepted. - */ - struct global_parameters_update_operation - { - asset fee; - chain_parameters new_parameters; - - account_id_type fee_payer()const { return account_id_type(); } - void get_required_auth(flat_set& active_auth_set, flat_set&)const - { active_auth_set.insert(account_id_type()); } - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief defines a message and checksum to enable validation of successful decryption - * - * When encrypting/decrypting a checksum is required to determine whether or not - * decryption was successful. - */ - struct memo_message - { - memo_message(){} - memo_message( uint32_t checksum, const std::string& text ) - :checksum(checksum),text(text){} - - uint32_t checksum = 0; - std::string text; - - string serialize() const; - static memo_message deserialize(const string& serial); - }; - - /** - * @brief defines the keys used to derive the shared secret - * - * Because account authorities and keys can change at any time, each memo must - * capture the specific keys used to derive the shared secret. In order to read - * the cipher message you will need one of the two private keys. - * - * If @ref from == @ref to and @ref from == 0 then no encryption is used, the memo is public. - * If @ref from == @ref to and @ref from != 0 then invalid memo data - * - */ - struct memo_data - { - key_id_type from; - key_id_type to; - /** - * 64 bit nonce format: - * [ 8 bits | 56 bits ] - * [ entropy | timestamp ] - * Timestamp is number of microseconds since the epoch - * Entropy is a byte taken from the hash of a new private key - * - * This format is not mandated or verified; it is chosen to ensure uniqueness of key-IV pairs only. This should - * be unique with high probability as long as the generating host has a high-resolution clock OR a strong source - * of entropy for generating private keys. - */ - uint64_t nonce; - /** - * This field contains the AES encrypted packed @ref memo_message - */ - vector message; - - void set_message( const fc::ecc::private_key& priv, - const fc::ecc::public_key& pub, const string& msg ); - - std::string get_message( const fc::ecc::private_key& priv, - const fc::ecc::public_key& pub )const; - }; - - /** - * @ingroup operations - * - * @brief Transfers an amount of one asset from one account to another - * - * Fees are paid by the "from" account - * - * @pre amount.amount > 0 - * @pre fee.amount >= 0 - * @pre from != to - * @post from account's balance will be reduced by fee and amount - * @post to account's balance will be increased by amount - * @return n/a - */ - struct transfer_operation - { - /** paid by the from account, may be of any asset for which there is a funded fee pool - **/ - asset fee; - account_id_type from; - account_id_type to; - /** the amount and asset type that will be withdrawn from account "from" and added to account "to" - * - **/ - asset amount; - - /** user provided data encrypted to the memo key of the "to" account */ - optional memo; - - account_id_type fee_payer()const { return from; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( from, -amount ); - acc.adjust( to, amount ); - } - }; - - /** - * @ingroup operations - */ - struct asset_create_operation - { - asset fee; - /// This account must sign and pay the fee for this operation. Later, this account may update the asset - account_id_type issuer; - /// The ticker symbol of this asset - string symbol; - /// Number of digits to the right of decimal point, must be less than or equal to 12 - uint8_t precision = 0; - - /// Options common to all assets. - /// - /// @note common_options.core_exchange_rate technically needs to store the asset ID of this new asset. Since this - /// ID is not known at the time this operation is created, create this price as though the new asset has instance - /// ID 1, and the chain will overwrite it with the new asset's ID. - asset_object::asset_options common_options; - /// Options only available for BitAssets. MUST be non-null if and only if the @ref market_issued flag is set in - /// common_options.flags - fc::optional bitasset_options; - /// For BitAssets, set this to true if the asset implements a @ref prediction_market; false otherwise - bool is_prediction_market = false; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief allows global settling of bitassets (black swan or prediction markets) - * - * In order to use this operation, @ref asset_to_settle must have the global_settle flag set - * - * When this operation is executed all balances are converted into the backing asset at the - * settle_price and all open margin positions are called at the settle price. If this asset is - * used as backing for other bitassets, those bitassets will be force settled at their current - * feed price. - */ - struct asset_global_settle_operation - { - asset fee; - account_id_type issuer; ///< must equal @ref asset_to_settle->issuer - asset_id_type asset_to_settle; - price settle_price; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Schedules a market-issued asset for automatic settlement - * @ingroup operations - * - * Holders of market-issued assests may request a forced settlement for some amount of their asset. This means that - * the specified sum will be locked by the chain and held for the settlement period, after which time the chain will - * choose a margin posision holder and buy the settled asset using the margin's collateral. The price of this sale - * will be based on the feed price for the market-issued asset being settled. The exact settlement price will be the - * feed price at the time of settlement with an offset in favor of the margin position, where the offset is a - * blockchain parameter set in the global_property_object. - * - * The fee is paid by @ref account, and @ref account must authorize this operation - */ - struct asset_settle_operation - { - asset fee; - /// Account requesting the force settlement. This account pays the fee - account_id_type account; - /// Amount of asset to force settle. This must be a market-issued asset - asset amount; - - account_id_type fee_payer()const { return account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( account, -amount ); - } - }; - - /** - * @ingroup operations - */ - struct asset_fund_fee_pool_operation - { - asset fee; ///< core asset - account_id_type from_account; - asset_id_type asset_id; - share_type amount; ///< core asset - - account_id_type fee_payer()const { return from_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( fee_payer(), -amount ); - } - }; - - /** - * @brief Update options common to all assets - * @ingroup operations - * - * There are a number of options which all assets in the network use. These options are enumerated in the @ref - * asset_object::asset_options struct. This operation is used to update these options for an existing asset. - * - * @note This operation cannot be used to update BitAsset-specific options. For these options, use @ref - * asset_update_bitasset_operation instead. - * - * @pre @ref issuer SHALL be an existing account and MUST match asset_object::issuer on @ref asset_to_update - * @pre @ref fee SHALL be nonnegative, and @ref issuer MUST have a sufficient balance to pay it - * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() - * @post @ref asset_to_update will have options matching those of new_options - */ - struct asset_update_operation - { - asset_update_operation(){} - /// Initializes the operation to apply changes to the provided asset, and copies old.options into @ref new_options - asset_update_operation(const asset_object& old); - - asset fee; - account_id_type issuer; - asset_id_type asset_to_update; - - /// If the asset is to be given a new issuer, specify his ID here. - optional new_issuer; - asset_object::asset_options new_options; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Update options specific to BitAssets - * @ingroup operations - * - * BitAssets have some options which are not relevant to other asset types. This operation is used to update those - * options an an existing BitAsset. - * - * @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update - * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref asset_object::is_market_issued() returns true - * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it - * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() - * @post @ref asset_to_update will have BitAsset-specific options matching those of new_options - */ - struct asset_update_bitasset_operation - { - asset fee; - account_id_type issuer; - asset_id_type asset_to_update; - - asset_object::bitasset_options new_options; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Update the set of feed-producing accounts for a BitAsset - * @ingroup operations - * - * BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers. - * This operation is used to specify which accounts may produce feeds for a given BitAsset. - * - * @pre @ref issuer MUST be an existing account, and MUST match asset_object::issuer on @ref asset_to_update - * @pre @ref issuer MUST NOT be the genesis account - * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref asset_object::is_market_issued() returns true - * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it - * @pre Cardinality of @ref new_feed_producers MUST NOT exceed @ref chain_parameters::maximum_asset_feed_publishers - * @post @ref asset_to_update will have a set of feed producers matching @ref new_feed_producers - * @post All valid feeds supplied by feed producers in @ref new_feed_producers, which were already feed producers - * prior to execution of this operation, will be preserved - */ - struct asset_update_feed_producers_operation - { - asset fee; - account_id_type issuer; - asset_id_type asset_to_update; - - flat_set new_feed_producers; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const - { active_auth_set.insert(fee_payer()); } - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const - { return k.asset_update_fee; } - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Publish price feeds for market-issued assets - * @ingroup operations - * - * Price feed providers use this operation to publish their price feeds for market-issued assets. A price feed is - * used to tune the market for a particular market-issued asset. For each value in the feed, the median across all - * delegate feeds for that asset is calculated and the market for the asset is configured with the median of that - * value. - * - * The feed in the operation contains three prices: a call price limit, a short price limit, and a settlement price. - * The call limit price is structured as (collateral asset) / (debt asset) and the short limit price is structured - * as (asset for sale) / (collateral asset). Note that the asset IDs are opposite to eachother, so if we're - * publishing a feed for USD, the call limit price will be CORE/USD and the short limit price will be USD/CORE. The - * settlement price may be flipped either direction, as long as it is a ratio between the market-issued asset and - * its collateral. - */ - struct asset_publish_feed_operation - { - asset fee; ///< paid for by publisher - account_id_type publisher; - asset_id_type asset_id; ///< asset for which the feed is published - price_feed feed; - - account_id_type fee_payer()const { return publisher; } - void get_required_auth( flat_set& active_auth_set, flat_set& )const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset() )const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @ingroup operations - */ - struct asset_issue_operation - { - asset fee; - account_id_type issuer; ///< Must be asset_to_issue->asset_id->issuer - asset asset_to_issue; - account_id_type issue_to_account; - - - /** user provided data encrypted to the memo key of the "to" account */ - optional memo; - - account_id_type fee_payer()const { return issuer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { acc.adjust( fee_payer(), -fee ); acc.adjust(issue_to_account, asset_to_issue); } - }; - - /** - * @brief used to take an asset out of circulation - * @ingroup operations - * - * @note You cannot burn market-issued assets. - */ - struct asset_burn_operation - { - asset fee; - account_id_type payer; - asset amount_to_burn; - - account_id_type fee_payer()const { return payer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( fee_payer(), -amount_to_burn ); - } - }; - - /** - * @class limit_order_create_operation - * @brief instructs the blockchain to attempt to sell one asset for another - * @ingroup operations - * - * The blockchain will atempt to sell amount_to_sell.asset_id for as - * much min_to_receive.asset_id as possible. The fee will be paid by - * the seller's account. Market fees will apply as specified by the - * issuer of both the selling asset and the receiving asset as - * a percentage of the amount exchanged. - * - * If either the selling asset or the receiving asset is white list - * restricted, the order will only be created if the seller is on - * the white list of the restricted asset type. - * - * Market orders are matched in the order they are included - * in the block chain. - */ - struct limit_order_create_operation - { - asset fee; - account_id_type seller; - asset amount_to_sell; - asset min_to_receive; - /** - * This order should expire if not filled by expiration - */ - time_point_sec expiration = time_point_sec::maximum(); - - /** if this flag is set the entire order must be filled or - * the operation is rejected. - */ - bool fill_or_kill = false; - - pair get_market()const - { - return amount_to_sell.asset_id < min_to_receive.asset_id ? - std::make_pair( amount_to_sell.asset_id, min_to_receive.asset_id ) : - std::make_pair( min_to_receive.asset_id, amount_to_sell.asset_id ); - } - account_id_type fee_payer()const { return seller; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - price get_price()const { return amount_to_sell / min_to_receive; } - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( seller, -amount_to_sell ); - } - }; - - - /** - * @ingroup operations - * Used to cancel an existing limit order. Both fee_pay_account and the - * account to receive the proceeds must be the same as order->seller. - * - * @return the amount actually refunded - */ - struct limit_order_cancel_operation - { - limit_order_id_type order; - /** must be order->seller */ - account_id_type fee_paying_account; - asset fee; - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( fee_payer(), result.get() ); - } - }; - - /** - * @ingroup operations - * - * Define a new short order, if it is filled it will - * be merged with existing call orders for the same - * account. If maintenance_collateral_ratio is set - * it will update any existing open call orders to - * use the new maintenance level. - * - * When shorting you specify the total amount to sell - * and the amount of collateral along with the initial - * ratio. The price it will sell at is (amount_to_sell/(collateral*initial_collateral_ratio/2000)) - */ - struct short_order_create_operation - { - /// The account placing a short order (this account must sign the transaction) - account_id_type seller; - /// The amount of market-issued asset to short sell - asset amount_to_sell; - /// The fee paid by seller - asset fee; - /// The amount of collateral to withdraw from the seller - asset collateral; - /// Fixed point representation of initial collateral ratio, with three digits of precision - /// Must be greater than or equal to the minimum specified by price feed - uint16_t initial_collateral_ratio = GRAPHENE_DEFAULT_INITIAL_COLLATERAL_RATIO; - /// Fixed point representation of maintenance collateral ratio, with three digits of precision - /// Must be greater than or equal to the minimum specified by price feed - uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO; - /// Expiration time for this order. Any unfilled portion of this order which is on the books at or past this time - /// will automatically be canceled. - time_point_sec expiration = time_point_sec::maximum(); - - account_id_type fee_payer()const { return seller; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - pair get_market()const - { - return amount_to_sell.asset_id < collateral.asset_id ? - std::make_pair( amount_to_sell.asset_id, collateral.asset_id ) : - std::make_pair( collateral.asset_id, amount_to_sell.asset_id ); - } - - /** convention: amount_to_sell / amount_to_receive */ - price sell_price()const { return ~price::call_price(amount_to_sell, collateral, initial_collateral_ratio); } - - /** convention: amount_to_sell / amount_to_receive means we are - * selling collateral to receive debt - **/ - price call_price() const { return price::call_price(amount_to_sell, collateral, maintenance_collateral_ratio); } - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( seller, -collateral ); - } - }; - - /** - * @ingroup operations - * Cancel the short order and return the balance to the - * order->seller account. - */ - struct short_order_cancel_operation - { - short_order_id_type order; - account_id_type fee_paying_account; ///< Must be order->seller - asset fee; ///< paid by order->seller - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result )const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( fee_payer(), result.get() ); - } - }; - - - /** - * @ingroup operations - * - * This operation can be used to add collateral, cover, and adjust the margin call price with a new maintenance - * collateral ratio. - * - * The only way to "cancel" a call order is to pay off the balance due. The order is invalid if the payoff amount - * is greater than the amount due. - * - * @note the call_order_id is implied by the funding_account and assets involved. This implies that the assets must - * have appropriate asset_ids, even if the amount is zero. - * - * @note this operation can be used to force a market order using the collateral without requiring outside funds. - */ - struct call_order_update_operation - { - account_id_type funding_account; ///< pays fee, collateral, and cover - asset fee; ///< paid by funding_account - asset collateral_to_add; ///< the amount of collateral to add to the margin position - asset amount_to_cover; ///< the amount of the debt to be paid off - uint16_t maintenance_collateral_ratio = 0; ///< 0 means don't change, 1000 means feed - - account_id_type fee_payer()const { return funding_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( funding_account, -collateral_to_add ); - acc.adjust( funding_account, -amount_to_cover ); - } - }; - - /** - * @defgroup proposed_transactions The Graphene Transaction Proposal Protocol - * @ingroup operations - * - * Graphene allows users to propose a transaction which requires approval of multiple accounts in order to execute. - * The user proposes a transaction using proposal_create_operation, then signatory accounts use - * proposal_update_operations to add or remove their approvals from this operation. When a sufficient number of - * approvals have been granted, the operations in the proposal are used to create a virtual transaction which is - * subsequently evaluated. Even if the transaction fails, the proposal will be kept until the expiration time, at - * which point, if sufficient approval is granted, the transaction will be evaluated a final time. This allows - * transactions which will not execute successfully until a given time to still be executed through the proposal - * mechanism. The first time the proposed transaction succeeds, the proposal will be regarded as resolved, and all - * future updates will be invalid. - * - * The proposal system allows for arbitrarily complex or recursively nested authorities. If a recursive authority - * (i.e. an authority which requires approval of 'nested' authorities on other accounts) is required for a - * proposal, then a second proposal can be used to grant the nested authority's approval. That is, a second - * proposal can be created which, when sufficiently approved, adds the approval of a nested authority to the first - * proposal. This multiple-proposal scheme can be used to acquire approval for an arbitrarily deep authority tree. - * - * Note that at any time, a proposal can be approved in a single transaction if sufficient signatures are available - * on the proposal_update_operation, as long as the authority tree to approve the proposal does not exceed the - * maximum recursion depth. In practice, however, it is easier to use proposals to acquire all approvals, as this - * leverages on-chain notification of all relevant parties that their approval is required. Off-chain - * multi-signature approval requires some off-chain mechanism for acquiring several signatures on a single - * transaction. This off-chain synchronization can be avoided using proposals. - * @{ - */ - /** - * op_wrapper is used to get around the circular definition of operation and proposals that contain them. - */ - struct op_wrapper; - /** - * @brief The proposal_create_operation creates a transaction proposal, for use in multi-sig scenarios - * @ingroup operations - * - * Creates a transaction proposal. The operations which compose the transaction are listed in order in proposed_ops, - * and expiration_time specifies the time by which the proposal must be accepted or it will fail permanently. The - * expiration_time cannot be farther in the future than the maximum expiration time set in the global properties - * object. - */ - struct proposal_create_operation - { - account_id_type fee_paying_account; - asset fee; - vector proposed_ops; - time_point_sec expiration_time; - optional review_period_seconds; - - /// Constructs a proposal_create_operation suitable for genesis proposals, with fee, expiration time and review - /// period set appropriately. - static proposal_create_operation genesis_proposal(const class database& db); - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth( flat_set& active_auth_set, flat_set& )const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const { return 0; } - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief The proposal_update_operation updates an existing transaction proposal - * @ingroup operations - * - * This operation allows accounts to add or revoke approval of a proposed transaction. Signatures sufficient to - * satisfy the authority of each account in approvals are required on the transaction containing this operation. - * - * If an account with a multi-signature authority is listed in approvals_to_add or approvals_to_remove, either all - * required signatures to satisfy that account's authority must be provided in the transaction containing this - * operation, or a secondary proposal must be created which contains this operation. - * - * NOTE: If the proposal requires only an account's active authority, the account must not update adding its owner - * authority's approval. This is considered an error. An owner approval may only be added if the proposal requires - * the owner's authority. - * - * If an account's owner and active authority are both required, only the owner authority may approve. An attempt to - * add or remove active authority approval to such a proposal will fail. - */ - struct proposal_update_operation - { - account_id_type fee_paying_account; - asset fee; - proposal_id_type proposal; - flat_set active_approvals_to_add; - flat_set active_approvals_to_remove; - flat_set owner_approvals_to_add; - flat_set owner_approvals_to_remove; - flat_set key_approvals_to_add; - flat_set key_approvals_to_remove; - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth(flat_set& active_auth_set, flat_set& owner_auth_set)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const { return 0; } - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief The proposal_delete_operation deletes an existing transaction proposal - * @ingroup operations - * - * This operation allows the early veto of a proposed transaction. It may be used by any account which is a required - * authority on the proposed transaction, when that account's holder feels the proposal is ill-advised and he decides - * he will never approve of it and wishes to put an end to all discussion of the issue. Because he is a required - * authority, he could simply refuse to add his approval, but this would leave the topic open for debate until the - * proposal expires. Using this operation, he can prevent any further breath from being wasted on such an absurd - * proposal. - */ - struct proposal_delete_operation - { - account_id_type fee_paying_account; - bool using_owner_authority = false; - asset fee; - proposal_id_type proposal; - - account_id_type fee_payer()const { return fee_paying_account; } - void get_required_auth(flat_set& active_auth_set, flat_set& owner_auth_set)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const { return 0; } - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - ///@} - - /** - * @ingroup operations - * - * @note This is a virtual operation that is created while matching orders and - * emitted for the purpose of accurately tracking account history, accelerating - * a reindex. - */ - struct fill_order_operation - { - object_id_type order_id; - account_id_type account_id; - asset pays; - asset receives; - asset fee; // paid by receiving account - - - pair get_market()const - { - return pays.asset_id < receives.asset_id ? - std::make_pair( pays.asset_id, receives.asset_id ) : - std::make_pair( receives.asset_id, pays.asset_id ); - } - account_id_type fee_payer()const { return account_id; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const - { active_auth_set.insert(fee_payer()); } - void validate()const { FC_ASSERT( !"virtual operation" ); } - share_type calculate_fee( const fee_schedule_type& k )const { return share_type(); } - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { - // acc.adjust( fee_payer(), -fee ); fee never actually entered the account, this is a virtual operation - acc.adjust( account_id, receives ); - } - }; - - /** - * @brief Create a new withdrawal permission - * @ingroup operations - * - * This operation creates a withdrawal permission, which allows some authorized account to withdraw from an - * authorizing account. This operation is primarily useful for scheduling recurring payments. - * - * Withdrawal permissions define withdrawal periods, which is a span of time during which the authorized account may - * make a withdrawal. Any number of withdrawals may be made so long as the total amount withdrawn per period does - * not exceed the limit for any given period. - * - * Withdrawal permissions authorize only a specific pairing, i.e. a permission only authorizes one specified - * authorized account to withdraw from one specified authorizing account. Withdrawals are limited and may not exceet - * the withdrawal limit. The withdrawal must be made in the same asset as the limit; attempts with withdraw any - * other asset type will be rejected. - * - * The fee for this operation is paid by withdraw_from_account, and this account is required to authorize this - * operation. - */ - struct withdraw_permission_create_operation - { - asset fee; - /// The account authorizing withdrawals from its balances - account_id_type withdraw_from_account; - /// The account authorized to make withdrawals from withdraw_from_account - account_id_type authorized_account; - /// The maximum amount authorized_account is allowed to withdraw in a given withdrawal period - asset withdrawal_limit; - /// Length of the withdrawal period in seconds - uint32_t withdrawal_period_sec; - /// The number of withdrawal periods this permission is valid for - uint32_t periods_until_expiration; - /// Time at which the first withdrawal period begins; must be in the future - time_point_sec period_start_time; - - account_id_type fee_payer()const { return withdraw_from_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Update an existing withdraw permission - * @ingroup operations - * - * This oeration is used to update the settings for an existing withdrawal permission. The accounts to withdraw to - * and from may never be updated. The fields which may be updated are the withdrawal limit (both amount and asset - * type may be updated), the withdrawal period length, the remaining number of periods until expiration, and the - * starting time of the new period. - * - * Fee is paid by withdraw_from_account, which is required to authorize this operation - */ - struct withdraw_permission_update_operation - { - asset fee; - /// This account pays the fee. Must match permission_to_update->withdraw_from_account - account_id_type withdraw_from_account; - /// The account authorized to make withdrawals. Must match permission_to_update->authorized_account - account_id_type authorized_account; - /// ID of the permission which is being updated - withdraw_permission_id_type permission_to_update; - /// New maximum amount the withdrawer is allowed to charge per withdrawal period - asset withdrawal_limit; - /// New length of the period between withdrawals - uint32_t withdrawal_period_sec; - /// New beginning of the next withdrawal period; must be in the future - time_point_sec period_start_time; - /// The new number of withdrawal periods for which this permission will be valid - uint32_t periods_until_expiration; - - account_id_type fee_payer()const { return withdraw_from_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - - /** - * @brief Withdraw from an account which has published a withdrawal permission - * @ingroup operations - * - * This operation is used to withdraw from an account which has authorized such a withdrawal. It may be executed at - * most once per withdrawal period for the given permission. On execution, amount_to_withdraw is transferred from - * withdraw_from_account to withdraw_to_account, assuming amount_to_withdraw is within the withdrawal limit. The - * withdrawal permission will be updated to note that the withdrawal for the current period has occurred, and - * further withdrawals will not be permitted until the next withdrawal period, assuming the permission has not - * expired. This operation may be executed at any time within the current withdrawal period. - * - * Fee is paid by withdraw_to_account, which is required to authorize this operation - */ - struct withdraw_permission_claim_operation - { - /// Paid by withdraw_to_account - asset fee; - /// ID of the permission authorizing this withdrawal - withdraw_permission_id_type withdraw_permission; - /// Must match withdraw_permission->withdraw_from_account - account_id_type withdraw_from_account; - /// Must match withdraw_permision->authorized_account - account_id_type withdraw_to_account; - /// Amount to withdraw. Must not exceed withdraw_permission->withdrawal_limit - asset amount_to_withdraw; - /// Memo for withdraw_from_account. Should generally be encrypted with withdraw_from_account->memo_key - optional memo; - - account_id_type fee_payer()const { return withdraw_to_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( withdraw_to_account, amount_to_withdraw ); - acc.adjust( withdraw_from_account, -amount_to_withdraw ); - } - }; - - /** - * @brief Delete an existing withdrawal permission - * @ingroup operations - * - * This operation cancels a withdrawal permission, thus preventing any future withdrawals using that permission. - * - * Fee is paid by withdraw_from_account, which is required to authorize this operation - */ - struct withdraw_permission_delete_operation - { - asset fee; - /// Must match withdrawal_permission->withdraw_from_account. This account pays the fee. - account_id_type withdraw_from_account; - /// The account previously authorized to make withdrawals. Must match withdrawal_permission->authorized_account - account_id_type authorized_account; - /// ID of the permission to be revoked. - withdraw_permission_id_type withdrawal_permission; - - account_id_type fee_payer()const { return withdraw_from_account; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - } - }; - - /** - * @brief Create a vesting balance. - * @ingroup operations - * - * The chain allows a user to create a vesting balance. - * Normally, vesting balances are created automatically as part - * of cashback and worker operations. This operation allows - * vesting balances to be created manually as well. - * - * Manual creation of vesting balances can be used by a stakeholder - * to publicly demonstrate that they are committed to the chain. - * It can also be used as a building block to create transactions - * that function like public debt. Finally, it is useful for - * testing vesting balance functionality. - * - * @return ID of newly created vesting_balance_object - */ - struct vesting_balance_create_operation - { - asset fee; - account_id_type creator; ///< Who provides funds initially - account_id_type owner; ///< Who is able to withdraw the balance - asset amount; - uint32_t vesting_seconds; - - account_id_type fee_payer()const { return creator; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( creator, -amount ); - } - }; - - /** - * @brief Withdraw from a vesting balance. - * @ingroup operations - * - * Withdrawal from a not-completely-mature vesting balance - * will result in paying fees. - * - * @return Nothing - */ - struct vesting_balance_withdraw_operation - { - asset fee; - vesting_balance_id_type vesting_balance; - account_id_type owner; ///< Must be vesting_balance.owner - asset amount; - - account_id_type fee_payer()const { return owner; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - acc.adjust( owner, amount ); - } - }; - - /** - * @defgroup workers The Blockchain Worker System - * @ingroup operations - * - * Graphene blockchains allow the creation of special "workers" which are elected positions paid by the blockchain - * for services they provide. There may be several types of workers, and the semantics of how and when they are paid - * are defined by the @ref worker_type_enum enumeration. All workers are elected by core stakeholder approval, by - * voting for or against them. - * - * Workers are paid from the blockchain's daily budget if their total approval (votes for - votes against) is - * positive, ordered from most positive approval to least, until the budget is exhausted. Payments are processed at - * the blockchain maintenance interval. If a worker does not have positive approval during payment processing, or if - * the chain's budget is exhausted before the worker is paid, that worker is simply not paid at that interval. - * Payment is not prorated based on percentage of the interval the worker was approved. If the chain attempts to pay - * a worker, but the budget is insufficient to cover its entire pay, the worker is paid the remaining budget funds, - * even though this does not fulfill his total pay. The worker will not receive extra pay to make up the difference - * later. Worker pay is placed in a vesting balance and vests over the number of days specified at the worker's - * creation. - * - * Once created, a worker is immutable and will be kept by the blockchain forever. - * - * @{ - */ - /** - * @brief Create a new worker object - * @ingroup operations - */ - struct worker_create_operation - { - asset fee; - account_id_type owner; - time_point_sec work_begin_date; - time_point_sec work_end_date; - share_type daily_pay; - /// This should be set to the initializer appropriate for the type of worker to be created. - worker_initializer initializer; - - account_id_type fee_payer()const { return owner; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - } - - }; - ///@} - - /** - * @brief provides a generic way to add higher level protocols on top of witness consensus - * @ingroup operations - * - * There is no validation for this operation other than that required auths are valid and a fee - * is paid that is appropriate for the data contained. - */ - struct custom_operation - { - asset fee; - account_id_type payer; - flat_set required_auths; - uint16_t id; - vector data; - - account_id_type fee_payer()const { return payer; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const - { - acc.adjust( fee_payer(), -fee ); - } - }; - - /** - * @ingroup operations - * - * Defines the set of valid operations as a discriminated union type. - */ - typedef fc::static_variant< - transfer_operation, - limit_order_create_operation, - short_order_create_operation, - limit_order_cancel_operation, - short_order_cancel_operation, - call_order_update_operation, - key_create_operation, - account_create_operation, - account_update_operation, - account_whitelist_operation, - account_upgrade_operation, - account_transfer_operation, - asset_create_operation, - asset_update_operation, - asset_update_bitasset_operation, - asset_update_feed_producers_operation, - asset_issue_operation, - asset_burn_operation, - asset_fund_fee_pool_operation, - asset_settle_operation, - asset_global_settle_operation, - asset_publish_feed_operation, - delegate_create_operation, - witness_create_operation, - witness_withdraw_pay_operation, - proposal_create_operation, - proposal_update_operation, - proposal_delete_operation, - withdraw_permission_create_operation, - withdraw_permission_update_operation, - withdraw_permission_claim_operation, - withdraw_permission_delete_operation, - fill_order_operation, - global_parameters_update_operation, - vesting_balance_create_operation, - vesting_balance_withdraw_operation, - worker_create_operation, - custom_operation - > operation; - - /// @} // operations group - - /** - * Used to track the result of applying an operation and when it was applied. - */ - struct applied_operation - { - operation op; - operation_result result; - uint32_t block_num; - uint16_t transaction_num; - uint16_t op_num; - }; - - /** - * @brief Used to find accounts which must sign off on operations in a polymorphic manner - */ - struct operation_get_required_auths - { - flat_set& active_auth_set; - flat_set& owner_auth_set; - operation_get_required_auths(flat_set& active_auth_set, - flat_set& owner_auth_set) - : active_auth_set(active_auth_set), - owner_auth_set(owner_auth_set) - {} - typedef void result_type; - template - void operator()(const T& v)const - { - v.get_required_auth(active_auth_set, owner_auth_set); -#ifndef NDEBUG - if( !(active_auth_set.count(v.fee_payer()) || owner_auth_set.count(v.fee_payer())) ) - elog("Fee payer not in required auths on ${op}", ("op", fc::get_typename::name())); - assert(active_auth_set.count(v.fee_payer()) || owner_auth_set.count(v.fee_payer())); -#endif - } - }; - - /** - * @brief Used to validate operations in a polymorphic manner - */ - struct operation_validator - { - typedef void result_type; - template - void operator()( const T& v )const { v.validate(); } - }; - - /** - * @brief Used to calculate fees in a polymorphic manner - * - * If you wish to pay fees in an asset other than CORE, use the core_exchange_rate argument to specify the rate of - - * exchange rate. It is up to the caller to ensure that the core_exchange_rate converts to an asset accepted by the - * delegates at a rate which they will accept. - */ - struct operation_calculate_fee - { - const fee_schedule_type& fees; - const price& core_exchange_rate; - operation_calculate_fee( const fee_schedule_type& f, const price& core_exchange_rate = price::unit_price() ) - : fees(f), - core_exchange_rate(core_exchange_rate) - {} - typedef share_type result_type; - template - share_type operator()( const T& v )const { return (v.calculate_fee(fees) * core_exchange_rate).amount; } - }; - - /** - * @brief Used to set fees in a polymorphic manner - * - * If you wish to pay fees in an asset other than CORE, use the core_exchange_rate argument to specify the rate of - * conversion you wish to use. The operation's fee will be set by multiplying the CORE fee by the provided exchange - * rate. It is up to the caller to ensure that the core_exchange_rate converts to an asset accepted by the delegates - * at a rate which they will accept. - * - * If total_fee is not nullptr, the total fee for all operations visited will be stored in the provided share_type. - * The share_type will be set to zero when the visitor is constructed. - */ - struct operation_set_fee - { - const fee_schedule_type& fees; - const price& core_exchange_rate; - share_type* total_fee; - operation_set_fee( const fee_schedule_type& f, - const price& core_exchange_rate = price::unit_price(), - share_type* total_fee = nullptr ) - : fees(f), - core_exchange_rate(core_exchange_rate), - total_fee(total_fee) - { - if( total_fee ) - *total_fee = 0; - } - typedef asset result_type; - template - asset operator()( T& v )const { - asset fee = (v.calculate_fee(fees)) * core_exchange_rate; - if( total_fee ) *total_fee += fee.amount; - return v.fee = fee; - } - }; - - /** - * @brief necessary to support nested operations inside the proposal_create_operation - */ - struct op_wrapper - { - public: - op_wrapper(const operation& op = operation()):op(op){} - operation op; - - void validate()const { op.visit( operation_validator() ); } - void get_required_auth(flat_set& active, flat_set& owner) { - op.visit(operation_get_required_auths(active, owner)); - } - asset set_fee( const fee_schedule_type& k ) { return op.visit( operation_set_fee( k ) ); } - share_type calculate_fee( const fee_schedule_type& k )const { return op.visit( operation_calculate_fee( k ) ); } - }; - -} } // graphene::chain -FC_REFLECT( graphene::chain::op_wrapper, (op) ) -FC_REFLECT( graphene::chain::memo_message, (checksum)(text) ) -FC_REFLECT( graphene::chain::memo_data, (from)(to)(nonce)(message) ) - -FC_REFLECT( graphene::chain::key_create_operation, - (fee)(fee_paying_account) - (key_data) - ) - -FC_REFLECT( graphene::chain::account_create_operation, - (fee)(registrar) - (referrer)(referrer_percent) - (name) - (owner)(active)(voting_account)(memo_key) - (num_witness)(num_committee)(vote) - ) - -FC_REFLECT( graphene::chain::account_update_operation, - (fee)(account)(owner)(active)(voting_account)(memo_key)(num_witness)(num_committee)(vote) - ) -FC_REFLECT( graphene::chain::account_upgrade_operation, (fee)(account_to_upgrade)(upgrade_to_lifetime_member) ) - -FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing) -FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, - (no_listing)(white_listed)(black_listed)(white_and_black_listed)) - -FC_REFLECT( graphene::chain::account_whitelist_operation, (fee)(authorizing_account)(account_to_list)(new_listing)) -FC_REFLECT( graphene::chain::account_transfer_operation, (fee)(account_id)(new_owner) ) - -FC_REFLECT( graphene::chain::delegate_create_operation, - (fee)(delegate_account) ) - -FC_REFLECT( graphene::chain::witness_create_operation, (fee)(witness_account)(block_signing_key)(initial_secret) ) -FC_REFLECT( graphene::chain::witness_withdraw_pay_operation, (fee)(from_witness)(to_account)(amount) ) - -FC_REFLECT( graphene::chain::limit_order_create_operation, - (fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill) - ) -FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives) ) -FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_account)(order) ) -FC_REFLECT( graphene::chain::short_order_cancel_operation,(fee)(fee_paying_account)(order) ) -FC_REFLECT( graphene::chain::short_order_create_operation, (fee)(seller)(amount_to_sell)(collateral) - (initial_collateral_ratio)(maintenance_collateral_ratio)(expiration) ) -FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(collateral_to_add)(amount_to_cover)(maintenance_collateral_ratio) ) - -FC_REFLECT( graphene::chain::transfer_operation, - (fee)(from)(to)(amount)(memo) ) - -FC_REFLECT( graphene::chain::asset_create_operation, - (fee) - (issuer) - (symbol) - (precision) - (common_options) - (bitasset_options) - (is_prediction_market) - ) -FC_REFLECT( graphene::chain::asset_update_operation, - (fee) - (issuer) - (asset_to_update) - (new_issuer) - (new_options) - ) -FC_REFLECT( graphene::chain::asset_update_bitasset_operation, - (fee) - (issuer) - (asset_to_update) - (new_options) - ) -FC_REFLECT( graphene::chain::asset_update_feed_producers_operation, - (fee)(issuer)(asset_to_update)(new_feed_producers) - ) -FC_REFLECT( graphene::chain::asset_publish_feed_operation, - (fee)(publisher)(asset_id)(feed) ) -FC_REFLECT( graphene::chain::asset_settle_operation, (fee)(account)(amount) ) -FC_REFLECT( graphene::chain::asset_global_settle_operation, (fee)(issuer)(asset_to_settle)(settle_price) ) -FC_REFLECT( graphene::chain::asset_issue_operation, - (fee)(issuer)(asset_to_issue)(issue_to_account)(memo) ) -FC_REFLECT( graphene::chain::asset_burn_operation, - (fee)(payer)(amount_to_burn) ) - -FC_REFLECT( graphene::chain::proposal_create_operation, (fee)(fee_paying_account)(expiration_time) - (proposed_ops)(review_period_seconds) ) -FC_REFLECT( graphene::chain::proposal_update_operation, (fee)(fee_paying_account)(proposal) - (active_approvals_to_add)(active_approvals_to_remove)(owner_approvals_to_add)(owner_approvals_to_remove) - (key_approvals_to_add)(key_approvals_to_remove) ) -FC_REFLECT( graphene::chain::proposal_delete_operation, (fee)(fee_paying_account)(using_owner_authority)(proposal) ) - -FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount) ); - -FC_REFLECT( graphene::chain::global_parameters_update_operation, (fee)(new_parameters) ); - -FC_REFLECT( graphene::chain::withdraw_permission_create_operation, (fee)(withdraw_from_account)(authorized_account) - (withdrawal_limit)(withdrawal_period_sec)(periods_until_expiration)(period_start_time) ) -FC_REFLECT( graphene::chain::withdraw_permission_update_operation, (fee)(withdraw_from_account)(authorized_account) - (permission_to_update)(withdrawal_limit)(withdrawal_period_sec)(period_start_time)(periods_until_expiration) ) -FC_REFLECT( graphene::chain::withdraw_permission_claim_operation, (fee)(withdraw_permission)(withdraw_from_account)(withdraw_to_account)(amount_to_withdraw)(memo) ); -FC_REFLECT( graphene::chain::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account) - (withdrawal_permission) ) - -FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(vesting_seconds) ) -FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) ) - -FC_REFLECT( graphene::chain::worker_create_operation, - (fee)(owner)(work_begin_date)(work_end_date)(daily_pay)(initializer) ) - -FC_REFLECT( graphene::chain::custom_operation, (fee)(payer)(required_auths)(id)(data) ) -FC_REFLECT( graphene::chain::void_result, ) - -FC_REFLECT_TYPENAME( graphene::chain::operation ) -FC_REFLECT_TYPENAME( fc::flat_set ) diff --git a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp index 454be5ab..606895bd 100644 --- a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp @@ -17,8 +17,8 @@ */ #pragma once +#include #include -#include #include #include @@ -29,7 +29,7 @@ namespace graphene { namespace chain { public: typedef proposal_create_operation operation_type; - object_id_type do_evaluate( const proposal_create_operation& o ); + void_result do_evaluate( const proposal_create_operation& o ); object_id_type do_apply( const proposal_create_operation& o ); transaction _proposed_trx; diff --git a/libraries/chain/include/graphene/chain/proposal_object.hpp b/libraries/chain/include/graphene/chain/proposal_object.hpp index 1427c4c4..eb8d890c 100644 --- a/libraries/chain/include/graphene/chain/proposal_object.hpp +++ b/libraries/chain/include/graphene/chain/proposal_object.hpp @@ -17,8 +17,7 @@ */ #pragma once -#include -#include +#include #include #include @@ -44,9 +43,33 @@ class proposal_object : public abstract_object flat_set available_active_approvals; flat_set required_owner_approvals; flat_set available_owner_approvals; - flat_set available_key_approvals; + flat_set available_key_approvals; - bool is_authorized_to_execute(database* db)const; + bool is_authorized_to_execute(database& db)const; +}; + +/** + * @brief tracks all of the proposal objects that requrie approval of + * an individual account. + * + * @ingroup object + * @ingroup protocol + * + * This is a secondary index on the proposal_index + * + * @note the set of required approvals is constant + */ +class required_approval_index : public secondary_index +{ + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override{}; + virtual void object_modified( const object& after ) override{}; + + void remove( account_id_type a, proposal_id_type p ); + + map > _account_to_proposals; }; struct by_expiration{}; diff --git a/libraries/chain/include/graphene/chain/protocol/README.md b/libraries/chain/include/graphene/chain/protocol/README.md new file mode 100644 index 00000000..6f19578c --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/README.md @@ -0,0 +1,10 @@ +Protocol Definition +-------------------- + +The classes declared in these headers provide the complete definition of the +Graphene protocol and are organized according to feature. Nothing in this +directory should depend upon anything other than fc or other types defined +in the protocol directory. + +To be more specific, implementation details such as the objects defined in +the object database should not be required here. diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp new file mode 100644 index 00000000..4c3cbd7e --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -0,0 +1,257 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + bool is_valid_name( const string& s ); + bool is_cheap_name( const string& n ); + + /// These are the fields which can be updated by the active authority. + struct account_options + { + /// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non- + /// validated account activities. This field is here to prevent confusion if the active authority has zero or + /// multiple keys in it. + public_key_type memo_key; + /// If this field is set to an account ID other than 0, this account's votes will be ignored and its stake + /// will be counted as voting for the referenced account's selected votes instead. + account_id_type voting_account; + + /// The number of active witnesses this account votes the blockchain should appoint + /// Must not exceed the actual number of witnesses voted for in @ref votes + uint16_t num_witness = 0; + /// The number of active committee members this account votes the blockchain should appoint + /// Must not exceed the actual number of committee members voted for in @ref votes + uint16_t num_committee = 0; + /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this + /// account's balance of core asset. + flat_set votes; + extensions_type extensions; + + void validate()const; + }; + + /** + * @ingroup operations + */ + struct account_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t basic_fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account + uint64_t premium_fee = 2000*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + /// This account pays the fee. Must be a lifetime member. + account_id_type registrar; + + /// This account receives a portion of the fee split between registrar and referrer. Must be a member. + account_id_type referrer; + /// Of the fee split between registrar and referrer, this percentage goes to the referrer. The rest goes to the + /// registrar. + uint16_t referrer_percent = 0; + + string name; + authority owner; + authority active; + + account_options options; + extensions_type extensions; + + account_id_type fee_payer()const { return registrar; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& )const; + + void get_impacted_accounts( flat_set& i )const + { + i.insert(registrar); + i.insert(referrer); + add_authority_accounts( i, owner ); + add_authority_accounts( i, active ); + } + }; + + /** + * @ingroup operations + * @brief Update an existing account + * + * This operation is used to update an existing account. It can be used to update the authorities, or adjust the options on the account. + * See @ref account_object::options_type for the options which may be updated. + */ + struct account_update_operation : public base_operation + { + struct fee_parameters_type { + share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + /// The account to update + account_id_type account; + + /// New owner authority. If set, this operation requires owner authority to execute. + optional owner; + /// New active authority. If set, this operation requires owner authority to execute: TODO: why? + optional active; + + /// New account options + optional new_options; + extensions_type extensions; + + account_id_type fee_payer()const { return account; } + void validate()const; + share_type calculate_fee( const fee_parameters_type& k )const; + + void get_required_owner_authorities( flat_set& a )const + { if( owner || active ) a.insert( account ); } + + void get_impacted_accounts( flat_set& i )const + { + i.insert(account); + if( owner ) add_authority_accounts( i, *owner ); + if( active ) add_authority_accounts( i, *active ); + } + }; + + + /** + * @brief This operation is used to whitelist and blacklist accounts, primarily for transacting in whitelisted assets + * @ingroup operations + * + * Accounts can freely specify opinions about other accounts, in the form of either whitelisting or blacklisting + * them. This information is used in chain validation only to determine whether an account is authorized to transact + * in an asset type which enforces a whitelist, but third parties can use this information for other uses as well, + * as long as it does not conflict with the use of whitelisted assets. + * + * An asset which enforces a whitelist specifies a list of accounts to maintain its whitelist, and a list of + * accounts to maintain its blacklist. In order for a given account A to hold and transact in a whitelisted asset S, + * A must be whitelisted by at least one of S's whitelist_authorities and blacklisted by none of S's + * blacklist_authorities. If A receives a balance of S, and is later removed from the whitelist(s) which allowed it + * to hold S, or added to any blacklist S specifies as authoritative, A's balance of S will be frozen until A's + * authorization is reinstated. + * + * This operation requires authorizing_account's signature, but not account_to_list's. The fee is paid by + * authorizing_account. + */ + struct account_whitelist_operation : public base_operation + { + struct fee_parameters_type { share_type fee = 300000; }; + enum account_listing { + no_listing = 0x0, ///< No opinion is specified about this account + white_listed = 0x1, ///< This account is whitelisted, but not blacklisted + black_listed = 0x2, ///< This account is blacklisted, but not whitelisted + white_and_black_listed = white_listed | black_listed ///< This account is both whitelisted and blacklisted + }; + + /// Paid by authorizing_account + asset fee; + /// The account which is specifying an opinion of another account + account_id_type authorizing_account; + /// The account being opined about + account_id_type account_to_list; + /// The new white and blacklist status of account_to_list, as determined by authorizing_account + /// This is a bitfield using values defined in the account_listing enum + uint8_t new_listing; + extensions_type extensions; + + account_id_type fee_payer()const { return authorizing_account; } + void validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT(new_listing < 0x4); } + + void get_impacted_accounts( flat_set& i )const + { i.insert(account_to_list); } + + }; + + + /** + * @brief Manage an account's membership status + * @ingroup operations + * + * This operation is used to upgrade an account to a member, or renew its subscription. If an account which is an + * unexpired annual subscription member publishes this operation with @ref upgrade_to_lifetime_member set to false, + * the account's membership expiration date will be pushed backward one year. If a basic account publishes it with + * @ref upgrade_to_lifetime_member set to false, the account will be upgraded to a subscription member with an + * expiration date one year after the processing time of this operation. + * + * Any account may use this operation to become a lifetime member by setting @ref upgrade_to_lifetime_member to + * true. Once an account has become a lifetime member, it may not use this operation anymore. + */ + struct account_upgrade_operation : public base_operation + { + struct fee_parameters_type { + uint64_t membership_annual_fee = 2000 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t membership_lifetime_fee = 10000 * GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to upgrade to a lifetime member + }; + + asset fee; + /// The account to upgrade; must not already be a lifetime member + account_id_type account_to_upgrade; + /// If true, the account will be upgraded to a lifetime member; otherwise, it will add a year to the subscription + bool upgrade_to_lifetime_member = false; + extensions_type extensions; + + account_id_type fee_payer()const { return account_to_upgrade; } + void validate()const; + share_type calculate_fee( const fee_parameters_type& k )const; + }; + + /** + * @brief transfers the account to another account while clearing the white list + * @ingroup operations + * + * In theory an account can be transferred by simply updating the authorities, but that kind + * of transfer lacks semantic meaning and is more often done to rotate keys without transferring + * ownership. This operation is used to indicate the legal transfer of title to this account and + * a break in the operation history. + * + * The account_id's owner/active/voting/memo authority should be set to new_owner + * + * This operation will clear the account's whitelist statuses, but not the blacklist statuses. + */ + struct account_transfer_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type account_id; + account_id_type new_owner; + extensions_type extensions; + + account_id_type fee_payer()const { return account_id; } + void validate()const; + + void get_impacted_accounts( flat_set& i )const + { + i.insert(new_owner); + } + + }; + +} } +FC_REFLECT(graphene::chain::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions)) +FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing) +FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, + (no_listing)(white_listed)(black_listed)(white_and_black_listed)) + +FC_REFLECT( graphene::chain::account_create_operation, + (fee)(registrar) + (referrer)(referrer_percent) + (name)(owner)(active)(options)(extensions) + ) +FC_REFLECT( graphene::chain::account_update_operation, + (fee)(account)(owner)(active)(new_options)(extensions) + ) + +FC_REFLECT( graphene::chain::account_upgrade_operation, + (fee)(account_to_upgrade)(upgrade_to_lifetime_member)(extensions) ) + +FC_REFLECT( graphene::chain::account_whitelist_operation, (fee)(authorizing_account)(account_to_list)(new_listing)(extensions)) + +FC_REFLECT( graphene::chain::account_create_operation::fee_parameters_type, (basic_fee)(premium_fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::account_whitelist_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::account_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::account_upgrade_operation::fee_parameters_type, (membership_annual_fee)(membership_lifetime_fee) ) +FC_REFLECT( graphene::chain::account_transfer_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::chain::account_transfer_operation, (fee)(account_id)(new_owner)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/address.hpp b/libraries/chain/include/graphene/chain/protocol/address.hpp similarity index 97% rename from libraries/chain/include/graphene/chain/address.hpp rename to libraries/chain/include/graphene/chain/protocol/address.hpp index 33357828..39f80800 100644 --- a/libraries/chain/include/graphene/chain/address.hpp +++ b/libraries/chain/include/graphene/chain/protocol/address.hpp @@ -56,6 +56,7 @@ namespace graphene { namespace chain { explicit operator std::string()const; ///< converts to base58 + checksum + friend size_t hash_value( const address& v ) { return *((size_t*)&v.addr._hash[2]); } fc::ripemd160 addr; }; inline bool operator == ( const address& a, const address& b ) { return a.addr == b.addr; } diff --git a/libraries/chain/include/graphene/chain/protocol/assert.hpp b/libraries/chain/include/graphene/chain/protocol/assert.hpp new file mode 100644 index 00000000..2177df87 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/assert.hpp @@ -0,0 +1,75 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * Used to verify that account_id->name is equal to the given string literal. + */ + struct account_name_eq_lit_predicate + { + account_id_type account_id; + string name; + + /** + * Perform state-independent checks. Verify + * account_name is a valid account name. + */ + bool validate()const; + }; + + /** + * Used to verify that asset_id->symbol is equal to the given string literal. + */ + struct asset_symbol_eq_lit_predicate + { + asset_id_type asset_id; + string symbol; + + /** + * Perform state independent checks. Verify symbol is a + * valid asset symbol. + */ + bool validate()const; + + }; + + /** + * When defining predicates do not make the protocol dependent upon + * implementation details. + */ + typedef static_variant< + account_name_eq_lit_predicate, + asset_symbol_eq_lit_predicate + > predicate; + + + /** + * @brief assert that some conditions are true. + * @ingroup operations + * + * This operation performs no changes to the database state, but can but used to verify + * pre or post conditions for other operations. + */ + struct assert_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type fee_paying_account; + vector predicates; + flat_set required_auths; + extensions_type extensions; + + account_id_type fee_payer()const { return fee_paying_account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::assert_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::account_name_eq_lit_predicate, (account_id)(name) ) +FC_REFLECT( graphene::chain::asset_symbol_eq_lit_predicate, (asset_id)(symbol) ) +FC_REFLECT_TYPENAME( graphene::chain::predicate ) +FC_REFLECT( graphene::chain::assert_operation, (fee)(fee_paying_account)(predicates)(required_auths)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp similarity index 70% rename from libraries/chain/include/graphene/chain/asset.hpp rename to libraries/chain/include/graphene/chain/protocol/asset.hpp index bee277ec..4c116c37 100644 --- a/libraries/chain/include/graphene/chain/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -16,8 +16,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include +#include namespace graphene { namespace chain { @@ -42,20 +42,34 @@ namespace graphene { namespace chain { return *this; } asset operator -()const { return asset( -amount, asset_id ); } + friend bool operator == ( const asset& a, const asset& b ) { - return tie(a.asset_id,a.amount) == tie(b.asset_id,b.amount); + return std::tie( a.asset_id, a.amount ) == std::tie( b.asset_id, b.amount ); } - friend bool operator >= ( const asset& a, const asset& b ) + friend bool operator < ( const asset& a, const asset& b ) { FC_ASSERT( a.asset_id == b.asset_id ); - return a.amount >= b.amount; + return a.amount < b.amount; + } + friend bool operator <= ( const asset& a, const asset& b ) + { + return (a == b) || (a < b); + } + + friend bool operator != ( const asset& a, const asset& b ) + { + return !(a == b); } friend bool operator > ( const asset& a, const asset& b ) { - FC_ASSERT( a.asset_id == b.asset_id ); - return a.amount > b.amount; + return !(a <= b); } + friend bool operator >= ( const asset& a, const asset& b ) + { + return !(a < b); + } + friend asset operator - ( const asset& a, const asset& b ) { FC_ASSERT( a.asset_id == b.asset_id ); @@ -66,7 +80,6 @@ namespace graphene { namespace chain { FC_ASSERT( a.asset_id == b.asset_id ); return asset( a.amount + b.amount, a.asset_id ); } - }; /** @@ -121,47 +134,14 @@ namespace graphene { namespace chain { /** * @class price_feed - * @brief defines market parameters for shorts and margin positions + * @brief defines market parameters for margin positions */ struct price_feed { - /** - * This is the lowest price at which margin positions will be forced to sell their collateral. This does not - * directly affect the price at which margin positions will be called; it is only a safety to prevent calls at - * unreasonable prices. - */ - price call_limit; - /** - * Short orders will only be matched against bids above this price. - */ - price short_limit; - /** - * Forced settlements will evaluate using this price. - */ - price settlement_price; - /** - * Maximum number of seconds margin positions should be able to remain open. - */ - uint32_t max_margin_period_sec = GRAPHENE_DEFAULT_MARGIN_PERIOD_SEC; - /** * Required maintenance collateral is defined * as a fixed point number with a maximum value of 10.000 - * and a minimum value of 1.000. - * - * This value must be greater than required_maintenance_collateral or - * a margin call would be triggered immediately. - * - * Default requirement is $2 of collateral per $1 of debt based - * upon the premise that both parties to every trade should bring - * equal value to the table. - */ - uint16_t required_initial_collateral = GRAPHENE_DEFAULT_INITIAL_COLLATERAL_RATIO; - - /** - * Required maintenance collateral is defined - * as a fixed point number with a maximum value of 10.000 - * and a minimum value of 1.000. + * and a minimum value of 1.000. (denominated in GRAPHENE_COLLATERAL_RATIO_DENOM) * * A black swan event occurs when value_of_collateral equals * value_of_debt, to avoid a black swan a margin call is @@ -169,19 +149,46 @@ namespace graphene { namespace chain { * equals value_of_collateral using rate. * * Default requirement is $1.75 of collateral per $1 of debt + * + * BlackSwan ---> SQR ---> MCR ----> SP */ - uint16_t required_maintenance_collateral = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO; + ///@{ + /** + * Forced settlements will evaluate using this price, defined as BITASSET / COLLATERAL + */ + price settlement_price; - friend bool operator < ( const price_feed& a, const price_feed& b ) - { - return std::tie( a.call_limit.base.asset_id, a.call_limit.quote.asset_id ) < - std::tie( b.call_limit.base.asset_id, b.call_limit.quote.asset_id ); - } + /// Price at which automatically exchanging this asset for CORE from fee pool occurs (used for paying fees) + price core_exchange_rate; + + /** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */ + uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO; + + /** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */ + uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO; + + /** + * When updating a call order the following condition must be maintained: + * + * debt * maintenance_price() < collateral + * debt * settlement_price < debt * maintenance + * debt * maintenance_price() < debt * max_short_squeeze_price() + price maintenance_price()const; + */ + + /** When selling collateral to pay off debt, the least amount of debt to receive should be + * min_usd = max_short_squeeze_price() * collateral + * + * This is provided to ensure that a black swan cannot be trigged due to poor liquidity alone, it + * must be confirmed by having the max_short_squeeze_price() move below the black swan price. + */ + price max_short_squeeze_price()const; + ///@} friend bool operator == ( const price_feed& a, const price_feed& b ) { - return std::tie( a.call_limit.base.asset_id, a.call_limit.quote.asset_id ) == - std::tie( b.call_limit.base.asset_id, b.call_limit.quote.asset_id ); + return std::tie( a.settlement_price, a.maintenance_collateral_ratio, a.maximum_short_squeeze_ratio ) == + std::tie( b.settlement_price, b.maintenance_collateral_ratio, b.maximum_short_squeeze_ratio ); } void validate() const; @@ -191,6 +198,9 @@ namespace graphene { namespace chain { FC_REFLECT( graphene::chain::asset, (amount)(asset_id) ) FC_REFLECT( graphene::chain::price, (base)(quote) ) -#define GRAPHENE_PRICE_FEED_FIELDS (call_limit)(short_limit)(settlement_price)(max_margin_period_sec)\ - (required_initial_collateral)(required_maintenance_collateral) + +#define GRAPHENE_PRICE_FEED_FIELDS (settlement_price)(maintenance_collateral_ratio)(maximum_short_squeeze_ratio) \ + (core_exchange_rate) + FC_REFLECT( graphene::chain::price_feed, GRAPHENE_PRICE_FEED_FIELDS ) + diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp new file mode 100644 index 00000000..5cdc6661 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -0,0 +1,461 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + bool is_valid_symbol( const string& symbol ); + + /** + * @brief The asset_options struct contains options available on all assets in the network + * + * @note Changes to this struct will break protocol compatibility + */ + struct asset_options { + /// The maximum supply of this asset which may exist at any given time. This can be as large as + /// GRAPHENE_MAX_SHARE_SUPPLY + share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + /// When this asset is traded on the markets, this percentage of the total traded will be exacted and paid + /// to the issuer. This is a fixed point value, representing hundredths of a percent, i.e. a value of 100 + /// in this field means a 1% fee is charged on market trades of this asset. + uint16_t market_fee_percent = 0; + /// Market fees calculated as @ref market_fee_percent of the traded volume are capped to this value + share_type max_market_fee = GRAPHENE_MAX_SHARE_SUPPLY; + + /// The flags which the issuer has permission to update. See @ref asset_issuer_permission_flags + uint16_t issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + /// The currently active flags on this permission. See @ref asset_issuer_permission_flags + uint16_t flags = 0; + + /// When a non-core asset is used to pay a fee, the blockchain must convert that asset to core asset in + /// order to accept the fee. If this asset's fee pool is funded, the chain will automatically deposite fees + /// in this asset to its accumulated fees, and withdraw from the fee pool the same amount as converted at + /// the core exchange rate. + price core_exchange_rate; + + /// A set of accounts which maintain whitelists to consult for this asset. If enforce_white_list() returns + /// true, an account may only send, receive, trade, etc. in this asset if one of these accounts appears in + /// its account_object::whitelisting_accounts field. + flat_set whitelist_authorities; + /// A set of accounts which maintain blacklists to consult for this asset. If enforce_white_list() returns + /// true, an account may only send, receive, trade, etc. in this asset if none of these accounts appears in + /// its account_object::blacklisting_accounts field. If the account is blacklisted, it may not transact in + /// this asset even if it is also whitelisted. + flat_set blacklist_authorities; + + /** defines the assets that this asset may be traded against in the market */ + flat_set whitelist_markets; + /** defines the assets that this asset may not be traded against in the market, must not overlap whitelist */ + flat_set blacklist_markets; + + /** + * data that describes the meaning/purpose of this asset, fee will be charged proportional to + * size of description. + */ + string description; + extensions_type extensions; + + /// Perform internal consistency checks. + /// @throws fc::exception if any check fails + void validate()const; + }; + + /** + * @brief The bitasset_options struct contains configurable options available only to BitAssets. + * + * @note Changes to this struct will break protocol compatibility + */ + struct bitasset_options { + /// Time before a price feed expires + uint32_t feed_lifetime_sec = GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME; + /// Minimum number of unexpired feeds required to extract a median feed from + uint8_t minimum_feeds = 1; + /// This is the delay between the time a long requests settlement and the chain evaluates the settlement + uint32_t force_settlement_delay_sec = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY; + /// This is the percent to adjust the feed price in the short's favor in the event of a forced settlement + uint16_t force_settlement_offset_percent = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET; + /// Force settlement volume can be limited such that only a certain percentage of the total existing supply + /// of the asset may be force-settled within any given chain maintenance interval. This field stores the + /// percentage of the current supply which may be force settled within the current maintenance interval. If + /// force settlements come due in an interval in which the maximum volume has already been settled, the new + /// settlements will be enqueued and processed at the beginning of the next maintenance interval. + uint16_t maximum_force_settlement_volume = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME; + /// This speicifies which asset type is used to collateralize short sales + /// This field may only be updated if the current supply of the asset is zero. + asset_id_type short_backing_asset; + extensions_type extensions; + + /// Perform internal consistency checks. + /// @throws fc::exception if any check fails + void validate()const; + }; + + + /** + * @ingroup operations + */ + struct asset_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t symbol3 = 500000 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t symbol4 = 300000 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t long_symbol = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; /// only required for large memos. + }; + + asset fee; + /// This account must sign and pay the fee for this operation. Later, this account may update the asset + account_id_type issuer; + /// The ticker symbol of this asset + string symbol; + /// Number of digits to the right of decimal point, must be less than or equal to 12 + uint8_t precision = 0; + + /// Options common to all assets. + /// + /// @note common_options.core_exchange_rate technically needs to store the asset ID of this new asset. Since this + /// ID is not known at the time this operation is created, create this price as though the new asset has instance + /// ID 1, and the chain will overwrite it with the new asset's ID. + asset_options common_options; + /// Options only available for BitAssets. MUST be non-null if and only if the @ref market_issued flag is set in + /// common_options.flags + optional bitasset_opts; + /// For BitAssets, set this to true if the asset implements a @ref prediction_market; false otherwise + bool is_prediction_market = false; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + share_type calculate_fee( const fee_parameters_type& k )const; + }; + + /** + * @brief allows global settling of bitassets (black swan or prediction markets) + * + * In order to use this operation, @ref asset_to_settle must have the global_settle flag set + * + * When this operation is executed all balances are converted into the backing asset at the + * settle_price and all open margin positions are called at the settle price. If this asset is + * used as backing for other bitassets, those bitassets will be force settled at their current + * feed price. + */ + struct asset_global_settle_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type issuer; ///< must equal @ref asset_to_settle->issuer + asset_id_type asset_to_settle; + price settle_price; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + }; + + /** + * @brief Schedules a market-issued asset for automatic settlement + * @ingroup operations + * + * Holders of market-issued assests may request a forced settlement for some amount of their asset. This means that + * the specified sum will be locked by the chain and held for the settlement period, after which time the chain will + * choose a margin posision holder and buy the settled asset using the margin's collateral. The price of this sale + * will be based on the feed price for the market-issued asset being settled. The exact settlement price will be the + * feed price at the time of settlement with an offset in favor of the margin position, where the offset is a + * blockchain parameter set in the global_property_object. + * + * The fee is paid by @ref account, and @ref account must authorize this operation + */ + struct asset_settle_operation : public base_operation + { + struct fee_parameters_type { + /** this fee should be high to encourage small settlement requests to + * be performed on the market rather than via forced settlement. + * + * Note that in the event of a black swan or prediction market close out + * everyone will have to pay this fee. + */ + uint64_t fee = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + /// Account requesting the force settlement. This account pays the fee + account_id_type account; + /// Amount of asset to force settle. This must be a market-issued asset + asset amount; + extensions_type extensions; + + account_id_type fee_payer()const { return account; } + void validate()const; + }; + + /** + * @ingroup operations + */ + struct asset_fund_fee_pool_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< core asset + account_id_type from_account; + asset_id_type asset_id; + share_type amount; ///< core asset + extensions_type extensions; + + account_id_type fee_payer()const { return from_account; } + void validate()const; + }; + + /** + * @brief Update options common to all assets + * @ingroup operations + * + * There are a number of options which all assets in the network use. These options are enumerated in the @ref + * asset_options struct. This operation is used to update these options for an existing asset. + * + * @note This operation cannot be used to update BitAsset-specific options. For these options, use @ref + * asset_update_bitasset_operation instead. + * + * @pre @ref issuer SHALL be an existing account and MUST match asset_object::issuer on @ref asset_to_update + * @pre @ref fee SHALL be nonnegative, and @ref issuer MUST have a sufficient balance to pay it + * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() + * @post @ref asset_to_update will have options matching those of new_options + */ + struct asset_update_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + asset_update_operation(){} + + asset fee; + account_id_type issuer; + asset_id_type asset_to_update; + + /// If the asset is to be given a new issuer, specify his ID here. + optional new_issuer; + asset_options new_options; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_impacted_accounts( flat_set& i)const + { if( new_issuer ) i.insert( *new_issuer ); } + }; + + /** + * @brief Update options specific to BitAssets + * @ingroup operations + * + * BitAssets have some options which are not relevant to other asset types. This operation is used to update those + * options an an existing BitAsset. + * + * @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update + * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref asset_object::is_market_issued() returns true + * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it + * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() + * @post @ref asset_to_update will have BitAsset-specific options matching those of new_options + */ + struct asset_update_bitasset_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type issuer; + asset_id_type asset_to_update; + + bitasset_options new_options; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + }; + + /** + * @brief Update the set of feed-producing accounts for a BitAsset + * @ingroup operations + * + * BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers. + * This operation is used to specify which accounts may produce feeds for a given BitAsset. + * + * @pre @ref issuer MUST be an existing account, and MUST match asset_object::issuer on @ref asset_to_update + * @pre @ref issuer MUST NOT be the committee account + * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref asset_object::is_market_issued() returns true + * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it + * @pre Cardinality of @ref new_feed_producers MUST NOT exceed @ref chain_parameters::maximum_asset_feed_publishers + * @post @ref asset_to_update will have a set of feed producers matching @ref new_feed_producers + * @post All valid feeds supplied by feed producers in @ref new_feed_producers, which were already feed producers + * prior to execution of this operation, will be preserved + */ + struct asset_update_feed_producers_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type issuer; + asset_id_type asset_to_update; + + flat_set new_feed_producers; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + }; + + /** + * @brief Publish price feeds for market-issued assets + * @ingroup operations + * + * Price feed providers use this operation to publish their price feeds for market-issued assets. A price feed is + * used to tune the market for a particular market-issued asset. For each value in the feed, the median across all + * committee_member feeds for that asset is calculated and the market for the asset is configured with the median of that + * value. + * + * The feed in the operation contains three prices: a call price limit, a short price limit, and a settlement price. + * The call limit price is structured as (collateral asset) / (debt asset) and the short limit price is structured + * as (asset for sale) / (collateral asset). Note that the asset IDs are opposite to eachother, so if we're + * publishing a feed for USD, the call limit price will be CORE/USD and the short limit price will be USD/CORE. The + * settlement price may be flipped either direction, as long as it is a ratio between the market-issued asset and + * its collateral. + */ + struct asset_publish_feed_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< paid for by publisher + account_id_type publisher; + asset_id_type asset_id; ///< asset for which the feed is published + price_feed feed; + extensions_type extensions; + + account_id_type fee_payer()const { return publisher; } + void validate()const; + }; + + /** + * @ingroup operations + */ + struct asset_issue_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type issuer; ///< Must be asset_to_issue->asset_id->issuer + asset asset_to_issue; + account_id_type issue_to_account; + + + /** user provided data encrypted to the memo key of the "to" account */ + optional memo; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_impacted_accounts( flat_set& i)const + { i.insert( issue_to_account ); } + }; + + /** + * @brief used to take an asset out of circulation, returning to the issuer + * @ingroup operations + * + * @note You cannot burn market-issued assets. + */ + struct asset_reserve_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type payer; + asset amount_to_reserve; + extensions_type extensions; + + account_id_type fee_payer()const { return payer; } + void validate()const; + }; + + +} } // graphene::chain +FC_REFLECT( graphene::chain::asset_options, + (max_supply) + (market_fee_percent) + (max_market_fee) + (issuer_permissions) + (flags) + (core_exchange_rate) + (whitelist_authorities) + (blacklist_authorities) + (whitelist_markets) + (blacklist_markets) + (description) + (extensions) + ) +FC_REFLECT( graphene::chain::bitasset_options, + (feed_lifetime_sec) + (minimum_feeds) + (force_settlement_delay_sec) + (force_settlement_offset_percent) + (maximum_force_settlement_volume) + (short_backing_asset) + (extensions) + ) + + +FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::asset_update_bitasset_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) ) + + +FC_REFLECT( graphene::chain::asset_create_operation, + (fee) + (issuer) + (symbol) + (precision) + (common_options) + (bitasset_opts) + (is_prediction_market) + (extensions) + ) +FC_REFLECT( graphene::chain::asset_update_operation, + (fee) + (issuer) + (asset_to_update) + (new_issuer) + (new_options) + (extensions) + ) +FC_REFLECT( graphene::chain::asset_update_bitasset_operation, + (fee) + (issuer) + (asset_to_update) + (new_options) + (extensions) + ) +FC_REFLECT( graphene::chain::asset_update_feed_producers_operation, + (fee)(issuer)(asset_to_update)(new_feed_producers)(extensions) + ) +FC_REFLECT( graphene::chain::asset_publish_feed_operation, + (fee)(publisher)(asset_id)(feed)(extensions) ) +FC_REFLECT( graphene::chain::asset_settle_operation, (fee)(account)(amount)(extensions) ) +FC_REFLECT( graphene::chain::asset_global_settle_operation, (fee)(issuer)(asset_to_settle)(settle_price)(extensions) ) +FC_REFLECT( graphene::chain::asset_issue_operation, + (fee)(issuer)(asset_to_issue)(issue_to_account)(memo)(extensions) ) +FC_REFLECT( graphene::chain::asset_reserve_operation, + (fee)(payer)(amount_to_reserve)(extensions) ) + +FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) ); + diff --git a/libraries/chain/include/graphene/chain/authority.hpp b/libraries/chain/include/graphene/chain/protocol/authority.hpp similarity index 72% rename from libraries/chain/include/graphene/chain/authority.hpp rename to libraries/chain/include/graphene/chain/protocol/authority.hpp index 93c612ff..843a4638 100644 --- a/libraries/chain/include/graphene/chain/authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/authority.hpp @@ -16,8 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include namespace graphene { namespace chain { @@ -43,17 +42,17 @@ namespace graphene { namespace chain { active = 1, key = 2 }; - void add_authority( key_id_type k, weight_type w ) + void add_authority( const public_key_type& k, weight_type w ) { - auths[k] = w; + key_auths[k] = w; + } + void add_authority( const address& k, weight_type w ) + { + address_auths[k] = w; } void add_authority( account_id_type k, weight_type w ) { - auths[k] = w; - } - void add_authority( relative_key_id_type k, weight_type w ) - { - auths[k] = w; + account_auths[k] = w; } template @@ -68,24 +67,26 @@ namespace graphene { namespace chain { add_authorities(auths...); } - vector get_keys() const + vector get_keys() const { - vector result; - result.reserve( auths.size() ); - for( const pair& item : auths ) - { - if( item.first.type() == key_object_type ) - result.push_back( item.first ); - } + vector result; + result.reserve( key_auths.size() ); + for( const auto& k : key_auths ) + result.push_back(k.first); return result; } + uint32_t num_auths()const { return account_auths.size() + key_auths.size(); } + void clear() { account_auths.clear(); key_auths.clear(); } - uint32_t weight_threshold = 0; - flat_map auths; + uint32_t weight_threshold = 0; + flat_map account_auths; + flat_map key_auths; + /** needed for backward compatibility only */ + flat_map address_auths; }; } } // namespace graphene::chain -FC_REFLECT( graphene::chain::authority, (weight_threshold)(auths) ) +FC_REFLECT( graphene::chain::authority, (weight_threshold)(account_auths)(key_auths)(address_auths) ) FC_REFLECT_TYPENAME( graphene::chain::authority::classification ) FC_REFLECT_ENUM( graphene::chain::authority::classification, (owner)(active)(key) ) diff --git a/libraries/chain/include/graphene/chain/protocol/balance.hpp b/libraries/chain/include/graphene/chain/protocol/balance.hpp new file mode 100644 index 00000000..612a8214 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/balance.hpp @@ -0,0 +1,36 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @brief Claim a balance in a @ref balanc_object + * + * This operation is used to claim the balance in a given @ref balance_object. If the balance object contains a + * vesting balance, @ref total_claimed must not exceed @ref balance_object::available at the time of evaluation. If + * the object contains a non-vesting balance, @ref total_claimed must be the full balance of the object. + */ + struct balance_claim_operation : public base_operation + { + struct fee_parameters_type {}; + + asset fee; + account_id_type deposit_to_account; + balance_id_type balance_to_claim; + public_key_type balance_owner_key; + asset total_claimed; + + account_id_type fee_payer()const { return deposit_to_account; } + share_type calculate_fee(const fee_parameters_type& )const { return 0; } + void validate()const; + void get_required_authorities( vector& a )const + { + a.push_back( authority( 1, balance_owner_key, 1 ) ); + } + }; + +}} // graphene::chain + +FC_REFLECT( graphene::chain::balance_claim_operation::fee_parameters_type, ) +FC_REFLECT( graphene::chain::balance_claim_operation, + (fee)(deposit_to_account)(balance_to_claim)(balance_owner_key)(total_claimed) ) diff --git a/libraries/chain/include/graphene/chain/protocol/base.hpp b/libraries/chain/include/graphene/chain/protocol/base.hpp new file mode 100644 index 00000000..b162eea4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/base.hpp @@ -0,0 +1,105 @@ +#pragma once +#include +#include +#include + + +namespace graphene { 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 void_result{}; + typedef fc::static_variant operation_result; + + struct base_operation + { + template + share_type calculate_fee(const T& params)const + { + return params.fee; + } + void get_required_authorities( vector& )const{} + void get_required_active_authorities( flat_set& )const{} + void get_required_owner_authorities( flat_set& )const{} + void get_impacted_accounts( flat_set& )const{} + void validate()const{} + + static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte ); + static void add_authority_accounts( flat_set& i, const authority& a ) + { + for( auto& item : a.account_auths ) + i.insert( item.first ); + } + }; + + /** + * For future expansion many structus include a single member of type + * extensions_type that can be changed when updating a protocol. You can + * always add new types to a static_variant without breaking backward + * compatibility. + */ + typedef static_variant future_extensions; + + /** + * A flat_set is used to make sure that only one extension of + * each type is added and that they are added in order. + * + * @note static_variant compares only the type tag and not the + * content. + */ + typedef flat_set extensions_type; + + ///@} + +} } +FC_REFLECT_TYPENAME( graphene::chain::operation_result ) +FC_REFLECT( graphene::chain::void_result, ) diff --git a/libraries/chain/include/graphene/chain/block.hpp b/libraries/chain/include/graphene/chain/protocol/block.hpp similarity index 85% rename from libraries/chain/include/graphene/chain/block.hpp rename to libraries/chain/include/graphene/chain/protocol/block.hpp index f8988eab..d9e882a1 100644 --- a/libraries/chain/include/graphene/chain/block.hpp +++ b/libraries/chain/include/graphene/chain/protocol/block.hpp @@ -16,14 +16,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include namespace graphene { namespace chain { - struct void_header{}; - typedef fc::static_variant header_extension; - struct block_header { digest_type digest()const; @@ -34,9 +30,9 @@ namespace graphene { namespace chain { secret_hash_type next_secret_hash; secret_hash_type previous_secret; checksum_type transaction_merkle_root; - vector extensions; + extensions_type extensions; - static uint32_t num_from_id(const block_id_type& id) { return htonl(id._hash[0]); } + static uint32_t num_from_id(const block_id_type& id); }; struct signed_block_header : public block_header @@ -46,7 +42,7 @@ namespace graphene { namespace chain { void sign( const fc::ecc::private_key& signer ); bool validate_signee( const fc::ecc::public_key& expected_signee )const; - signature_type delegate_signature; + signature_type witness_signature; }; struct signed_block : public signed_block_header @@ -57,8 +53,7 @@ namespace graphene { namespace chain { } } // graphene::chain -FC_REFLECT( graphene::chain::void_header, ) FC_REFLECT( graphene::chain::block_header, (previous)(timestamp)(witness) (next_secret_hash)(previous_secret)(transaction_merkle_root)(extensions) ) -FC_REFLECT_DERIVED( graphene::chain::signed_block_header, (graphene::chain::block_header), (delegate_signature) ) +FC_REFLECT_DERIVED( graphene::chain::signed_block_header, (graphene::chain::block_header), (witness_signature) ) FC_REFLECT_DERIVED( graphene::chain::signed_block, (graphene::chain::signed_block_header), (transactions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp new file mode 100644 index 00000000..55a068c0 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once +#include +#include + +namespace graphene { namespace chain { struct fee_schedule; } } +/* +namespace fc { + template inline void pack( Stream& s, const graphene::chain::fee_schedule& value ); + template inline void unpack( Stream& s, graphene::chain::fee_schedule& value ); +} // namespace fc +*/ + +namespace graphene { namespace chain { + + typedef static_variant<> parameter_extension; + struct chain_parameters + { + /** using a smart ref breaks the circular dependency created between operations and the fee schedule */ + smart_ref current_fees; ///< current schedule of fees + uint8_t block_interval = GRAPHENE_DEFAULT_BLOCK_INTERVAL; ///< interval in seconds between blocks + uint32_t maintenance_interval = GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL; ///< interval in sections between blockchain maintenance events + uint32_t committee_proposal_review_period = GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC; ///< minimum time in seconds that a proposed transaction requiring committee authority may not be signed, prior to expiration + uint32_t maximum_transaction_size = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE; ///< maximum allowable size in bytes for a transaction + uint32_t maximum_block_size = GRAPHENE_DEFAULT_MAX_BLOCK_SIZE; ///< maximum allowable size in bytes for a block + uint32_t maximum_undo_history = GRAPHENE_DEFAULT_MAX_UNDO_HISTORY; ///< maximum number of undo states to keep in RAM + uint32_t maximum_time_until_expiration = GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION; ///< maximum lifetime in seconds for transactions to be valid, before expiring + uint32_t maximum_proposal_lifetime = GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC; ///< maximum lifetime in seconds for proposed transactions to be kept, before expiring + uint8_t maximum_asset_whitelist_authorities = GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES; ///< maximum number of accounts which an asset may list as authorities for its whitelist OR blacklist + uint8_t maximum_asset_feed_publishers = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS; ///< the maximum number of feed publishers for a given asset + uint16_t maximum_witness_count = GRAPHENE_DEFAULT_MAX_WITNESSES; ///< maximum number of active witnesses + uint16_t maximum_committee_count = GRAPHENE_DEFAULT_MAX_COMMITTEE; ///< maximum number of active committee_members + uint16_t maximum_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have + uint16_t reserve_percent_of_fee = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of the network's allocation of a fee that is taken out of circulation + uint16_t network_percent_of_fee = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; ///< percent of transaction fees paid to network + uint16_t lifetime_referrer_percent_of_fee = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; ///< percent of transaction fees paid to network + uint32_t cashback_vesting_period_seconds = GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC; ///< time after cashback rewards are accrued before they become liquid + share_type cashback_vesting_threshold = GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD; ///< the maximum cashback that can be received without vesting + uint16_t max_bulk_discount_percent_of_fee = GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT; ///< the maximum percentage discount for bulk discounts + share_type bulk_discount_threshold_min = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MIN; ///< the minimum amount of fees paid to qualify for bulk discounts + share_type bulk_discount_threshold_max = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MAX; ///< the amount of fees paid to qualify for the max bulk discount percent + bool count_non_member_votes = true; ///< set to false to restrict voting privlegages to member accounts + bool allow_non_member_whitelists = false; ///< true if non-member accounts may set whitelists and blacklists; false otherwise + share_type witness_pay_per_block = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK; ///< CORE to be allocated to witnesses (per block) + share_type worker_budget_per_day = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY; ///< CORE to be allocated to workers (per day) + uint16_t max_predicate_opcode = GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE; ///< predicate_opcode must be less than this number + share_type fee_liquidation_threshold = GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD; ///< value in CORE at which accumulated fees in blockchain-issued market assets should be liquidated + uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings + uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling + extensions_type extensions; + + void validate()const + { + FC_ASSERT( reserve_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( network_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( max_bulk_discount_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( network_percent_of_fee + lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( bulk_discount_threshold_min <= bulk_discount_threshold_max ); + FC_ASSERT( bulk_discount_threshold_min > 0 ); + + FC_ASSERT( block_interval >= GRAPHENE_MIN_BLOCK_INTERVAL ); + FC_ASSERT( block_interval <= GRAPHENE_MAX_BLOCK_INTERVAL ); + FC_ASSERT( block_interval > 0 ); + FC_ASSERT( maintenance_interval > block_interval, + "Maintenance interval must be longer than block interval" ); + FC_ASSERT( maintenance_interval % block_interval == 0, + "Maintenance interval must be a multiple of block interval" ); + FC_ASSERT( maximum_transaction_size >= GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT, + "Transaction size limit is too low" ); + FC_ASSERT( maximum_block_size >= GRAPHENE_MIN_BLOCK_SIZE_LIMIT, + "Block size limit is too low" ); + FC_ASSERT( maximum_time_until_expiration > block_interval, + "Maximum transaction expiration time must be greater than a block interval" ); + FC_ASSERT( maximum_proposal_lifetime - committee_proposal_review_period > block_interval, + "Committee proposal review period must be less than the maximum proposal lifetime" ); + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::chain_parameters, + (current_fees) + (block_interval) + (maintenance_interval) + (committee_proposal_review_period) + (maximum_transaction_size) + (maximum_block_size) + (maximum_undo_history) + (maximum_time_until_expiration) + (maximum_proposal_lifetime) + (maximum_asset_whitelist_authorities) + (maximum_asset_feed_publishers) + (maximum_witness_count) + (maximum_committee_count) + (maximum_authority_membership) + (reserve_percent_of_fee) + (network_percent_of_fee) + (lifetime_referrer_percent_of_fee) + (max_bulk_discount_percent_of_fee) + (cashback_vesting_period_seconds) + (cashback_vesting_threshold) + (bulk_discount_threshold_min) + (bulk_discount_threshold_max) + (count_non_member_votes) + (allow_non_member_whitelists) + (witness_pay_per_block) + (worker_budget_per_day) + (max_predicate_opcode) + (fee_liquidation_threshold) + (accounts_per_fee_scale) + (account_fee_scale_bitshifts) + (extensions) + ) diff --git a/libraries/chain/include/graphene/chain/protocol/committee_member.hpp b/libraries/chain/include/graphene/chain/protocol/committee_member.hpp new file mode 100644 index 00000000..0b774468 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/committee_member.hpp @@ -0,0 +1,59 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + /** + * @brief Create a committee_member object, as a bid to hold a committee_member seat on the network. + * @ingroup operations + * + * Accounts which wish to become committee_members may use this operation to create a committee_member object which stakeholders may + * vote on to approve its position as a committee_member. + */ + struct committee_member_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// The account which owns the committee_member. This account pays the fee for this operation. + account_id_type committee_member_account; + string url; + + account_id_type fee_payer()const { return committee_member_account; } + void validate()const; + }; + + /** + * @brief Used by committee_members to update the global parameters of the blockchain. + * @ingroup operations + * + * This operation allows the committee_members to update the global parameters on the blockchain. These control various + * tunable aspects of the chain, including block and maintenance intervals, maximum data sizes, the fees charged by + * the network, etc. + * + * This operation may only be used in a proposed transaction, and a proposed transaction which contains this + * operation must have a review period specified in the current global parameters before it may be accepted. + */ + struct committee_member_update_global_parameters_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + chain_parameters new_parameters; + + account_id_type fee_payer()const { return account_id_type(); } + void validate()const; + }; + + /// TODO: committee_member_resign_operation : public base_operation + +} } // graphene::chain +FC_REFLECT( graphene::chain::committee_member_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::committee_member_update_global_parameters_operation::fee_parameters_type, (fee) ) + + +FC_REFLECT( graphene::chain::committee_member_create_operation, + (fee)(committee_member_account)(url) ) + +FC_REFLECT( graphene::chain::committee_member_update_global_parameters_operation, (fee)(new_parameters) ); diff --git a/libraries/chain/include/graphene/chain/protocol/config.hpp b/libraries/chain/include/graphene/chain/protocol/config.hpp new file mode 100644 index 00000000..05a3e495 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/config.hpp @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/libraries/chain/include/graphene/chain/protocol/custom.hpp b/libraries/chain/include/graphene/chain/protocol/custom.hpp new file mode 100644 index 00000000..552f5b45 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/custom.hpp @@ -0,0 +1,35 @@ + +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @brief provides a generic way to add higher level protocols on top of witness consensus + * @ingroup operations + * + * There is no validation for this operation other than that required auths are valid and a fee + * is paid that is appropriate for the data contained. + */ + struct custom_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + asset fee; + account_id_type payer; + flat_set required_auths; + uint16_t id; + vector data; + + account_id_type fee_payer()const { return payer; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + + +} } // namespace graphene::chain +FC_REFLECT( graphene::chain::custom_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::custom_operation, (fee)(payer)(required_auths)(id)(data) ) diff --git a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp new file mode 100644 index 00000000..5cbb5abd --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + template struct transform_to_fee_parameters; + template + struct transform_to_fee_parameters> + { + typedef fc::static_variant< typename T::fee_parameters_type... > type; + }; + typedef transform_to_fee_parameters::type fee_parameters; + + /** + * @brief contains all of the parameters necessary to calculate the fee for any operation + */ + struct fee_schedule + { + fee_schedule(); + + static fee_schedule get_default(); + + /** + * Finds the appropriate fee parameter struct for the operation + * and then calculates the appropriate fee. + */ + asset calculate_fee( const operation& op, const price& core_exchange_rate = price::unit_price() )const; + asset set_fee( operation& op, const price& core_exchange_rate = price::unit_price() )const; + + void zero_all_fees(); + + /** + * Validates all of the parameters are present and accounted for. + */ + void validate()const; + + template + const typename Operation::fee_parameters_type& get()const + { + auto itr = parameters.find( typename Operation::fee_parameters_type() ); + FC_ASSERT( itr != parameters.end() ); + return itr->template get(); + } + + /** + * @note must be sorted by fee_parameters.which() and have no duplicates + */ + flat_set parameters; + uint32_t scale = GRAPHENE_100_PERCENT; ///< fee * scale / GRAPHENE_100_PERCENT + }; + + typedef fee_schedule fee_schedule_type; + +} } // graphene::chain + +FC_REFLECT_TYPENAME( graphene::chain::fee_parameters ) +FC_REFLECT( graphene::chain::fee_schedule, (parameters)(scale) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/market.hpp b/libraries/chain/include/graphene/chain/protocol/market.hpp new file mode 100644 index 00000000..45bdde48 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/market.hpp @@ -0,0 +1,152 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @class limit_order_create_operation + * @brief instructs the blockchain to attempt to sell one asset for another + * @ingroup operations + * + * The blockchain will atempt to sell amount_to_sell.asset_id for as + * much min_to_receive.asset_id as possible. The fee will be paid by + * the seller's account. Market fees will apply as specified by the + * issuer of both the selling asset and the receiving asset as + * a percentage of the amount exchanged. + * + * If either the selling asset or the receiving asset is white list + * restricted, the order will only be created if the seller is on + * the white list of the restricted asset type. + * + * Market orders are matched in the order they are included + * in the block chain. + */ + struct limit_order_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type seller; + asset amount_to_sell; + asset min_to_receive; + + /// The order will be removed from the books if not filled by expiration + /// Upon expiration, all unsold asset will be returned to seller + time_point_sec expiration = time_point_sec::maximum(); + + /// If this flag is set the entire order must be filled or the operation is rejected + bool fill_or_kill = false; + extensions_type extensions; + + pair get_market()const + { + return amount_to_sell.asset_id < min_to_receive.asset_id ? + std::make_pair(amount_to_sell.asset_id, min_to_receive.asset_id) : + std::make_pair(min_to_receive.asset_id, amount_to_sell.asset_id); + } + account_id_type fee_payer()const { return seller; } + void validate()const; + price get_price()const { return amount_to_sell / min_to_receive; } + }; + + + /** + * @ingroup operations + * Used to cancel an existing limit order. Both fee_pay_account and the + * account to receive the proceeds must be the same as order->seller. + * + * @return the amount actually refunded + */ + struct limit_order_cancel_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + limit_order_id_type order; + /** must be order->seller */ + account_id_type fee_paying_account; + extensions_type extensions; + + account_id_type fee_payer()const { return fee_paying_account; } + void validate()const; + }; + + + + /** + * @ingroup operations + * + * This operation can be used to add collateral, cover, and adjust the margin call price for a particular user. + * + * For prediction markets the collateral and debt must always be equal. + * + * This operation will fail if it would trigger a margin call that couldn't be filled. If the margin call hits + * the call price limit then it will fail if the call price is above the settlement price. + * + * @note this operation can be used to force a market order using the collateral without requiring outside funds. + */ + struct call_order_update_operation : public base_operation + { + /** this is slightly more expensive than limit orders, this pricing impacts prediction markets */ + struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type funding_account; ///< pays fee, collateral, and cover + asset delta_collateral; ///< the amount of collateral to add to the margin position + asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt + extensions_type extensions; + + account_id_type fee_payer()const { return funding_account; } + void validate()const; + }; + + /** + * @ingroup operations + * + * @note This is a virtual operation that is created while matching orders and + * emitted for the purpose of accurately tracking account history, accelerating + * a reindex. + */ + struct fill_order_operation : public base_operation + { + struct fee_parameters_type {}; + + fill_order_operation(){} + fill_order_operation( object_id_type o, account_id_type a, asset p, asset r, asset f ) + :order_id(o),account_id(a),pays(p),receives(r),fee(f){} + + object_id_type order_id; + account_id_type account_id; + asset pays; + asset receives; + asset fee; // paid by receiving account + + + pair get_market()const + { + return pays.asset_id < receives.asset_id ? + std::make_pair( pays.asset_id, receives.asset_id ) : + std::make_pair( receives.asset_id, pays.asset_id ); + } + account_id_type fee_payer()const { return account_id; } + void validate()const { FC_ASSERT( !"virtual operation" ); } + + /// This is a virtual operation; there is no fee + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + void get_impacted_accounts( flat_set& i)const + { i.insert( account_id ); } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::limit_order_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::limit_order_cancel_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::call_order_update_operation::fee_parameters_type, (fee) ) +/// THIS IS THE ONLY VIRTUAL OPERATION THUS FAR... +FC_REFLECT( graphene::chain::fill_order_operation::fee_parameters_type, ) + + +FC_REFLECT( graphene::chain::limit_order_create_operation,(fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions)) +FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_account)(order)(extensions) ) +FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(delta_collateral)(delta_debt)(extensions) ) +FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives) ) diff --git a/libraries/chain/include/graphene/chain/protocol/memo.hpp b/libraries/chain/include/graphene/chain/protocol/memo.hpp new file mode 100644 index 00000000..b5bec350 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/memo.hpp @@ -0,0 +1,68 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @brief defines the keys used to derive the shared secret + * + * Because account authorities and keys can change at any time, each memo must + * capture the specific keys used to derive the shared secret. In order to read + * the cipher message you will need one of the two private keys. + * + * If @ref from == @ref to and @ref from == 0 then no encryption is used, the memo is public. + * If @ref from == @ref to and @ref from != 0 then invalid memo data + * + */ + struct memo_data + { + public_key_type from; + public_key_type to; + /** + * 64 bit nonce format: + * [ 8 bits | 56 bits ] + * [ entropy | timestamp ] + * Timestamp is number of microseconds since the epoch + * Entropy is a byte taken from the hash of a new private key + * + * This format is not mandated or verified; it is chosen to ensure uniqueness of key-IV pairs only. This should + * be unique with high probability as long as the generating host has a high-resolution clock OR a strong source + * of entropy for generating private keys. + */ + uint64_t nonce; + /** + * This field contains the AES encrypted packed @ref memo_message + */ + vector message; + + /// @note custom_nonce is for debugging only; do not set to a nonzero value in production + void set_message(const fc::ecc::private_key& priv, + const fc::ecc::public_key& pub, const string& msg, uint64_t custom_nonce = 0); + + std::string get_message(const fc::ecc::private_key& priv, + const fc::ecc::public_key& pub)const; + }; + + /** + * @brief defines a message and checksum to enable validation of successful decryption + * + * When encrypting/decrypting a checksum is required to determine whether or not + * decryption was successful. + */ + struct memo_message + { + memo_message(){} + memo_message( uint32_t checksum, const std::string& text ) + :checksum(checksum),text(text){} + + uint32_t checksum = 0; + std::string text; + + string serialize() const; + static memo_message deserialize(const string& serial); + }; + +} } // namespace graphene::chain + +FC_REFLECT( graphene::chain::memo_message, (checksum)(text) ) +FC_REFLECT( graphene::chain::memo_data, (from)(to)(nonce)(message) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp new file mode 100644 index 00000000..8a63d0b9 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -0,0 +1,117 @@ +/* Copyright (C) Cryptonomex, Inc - All Rights Reserved **/ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + + /** + * @ingroup operations + * + * Defines the set of valid operations as a discriminated union type. + */ + typedef fc::static_variant< + transfer_operation, + limit_order_create_operation, + limit_order_cancel_operation, + call_order_update_operation, + fill_order_operation, + account_create_operation, + account_update_operation, + account_whitelist_operation, + account_upgrade_operation, + account_transfer_operation, + asset_create_operation, + asset_update_operation, + asset_update_bitasset_operation, + asset_update_feed_producers_operation, + asset_issue_operation, + asset_reserve_operation, + asset_fund_fee_pool_operation, + asset_settle_operation, + asset_global_settle_operation, + asset_publish_feed_operation, + witness_create_operation, + witness_withdraw_pay_operation, + proposal_create_operation, + proposal_update_operation, + proposal_delete_operation, + withdraw_permission_create_operation, + withdraw_permission_update_operation, + withdraw_permission_claim_operation, + withdraw_permission_delete_operation, + committee_member_create_operation, + committee_member_update_global_parameters_operation, + vesting_balance_create_operation, + vesting_balance_withdraw_operation, + worker_create_operation, + custom_operation, + assert_operation, + balance_claim_operation, + override_transfer_operation + > operation; + + /// @} // operations group + + /** + * Appends required authorites to the result vector. The authorities appended are not the + * same as those returned by get_required_auth + * + * @return a set of required authorities for @ref op + */ + void operation_get_required_authorities( const operation& op, + flat_set& active, + flat_set& owner, + vector& other ); + + void operation_get_impacted_accounts( const operation& op, + flat_set& accounts ); + + void operation_validate( const operation& op ); + + /** + * Used to track the result of applying an operation and when it was applied. + * + * TODO: this doesn't belong here. + */ + struct applied_operation + { + operation op; + operation_result result; + uint32_t block_num; + uint16_t transaction_num; + uint16_t op_num; + }; + + /** + * @brief necessary to support nested operations inside the proposal_create_operation + */ + struct op_wrapper + { + public: + op_wrapper(const operation& op = operation()):op(op){} + operation op; + }; + +} } // graphene::chain + +FC_REFLECT_TYPENAME( graphene::chain::operation ) +FC_REFLECT( graphene::chain::op_wrapper, (op) ) + + + + + + diff --git a/libraries/chain/include/graphene/chain/protocol/proposal.hpp b/libraries/chain/include/graphene/chain/protocol/proposal.hpp new file mode 100644 index 00000000..e23329f9 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/proposal.hpp @@ -0,0 +1,153 @@ +#pragma once +#include + +namespace graphene { namespace chain { + /** + * @defgroup proposed_transactions The Graphene Transaction Proposal Protocol + * @ingroup operations + * + * Graphene allows users to propose a transaction which requires approval of multiple accounts in order to execute. + * The user proposes a transaction using proposal_create_operation, then signatory accounts use + * proposal_update_operations to add or remove their approvals from this operation. When a sufficient number of + * approvals have been granted, the operations in the proposal are used to create a virtual transaction which is + * subsequently evaluated. Even if the transaction fails, the proposal will be kept until the expiration time, at + * which point, if sufficient approval is granted, the transaction will be evaluated a final time. This allows + * transactions which will not execute successfully until a given time to still be executed through the proposal + * mechanism. The first time the proposed transaction succeeds, the proposal will be regarded as resolved, and all + * future updates will be invalid. + * + * The proposal system allows for arbitrarily complex or recursively nested authorities. If a recursive authority + * (i.e. an authority which requires approval of 'nested' authorities on other accounts) is required for a + * proposal, then a second proposal can be used to grant the nested authority's approval. That is, a second + * proposal can be created which, when sufficiently approved, adds the approval of a nested authority to the first + * proposal. This multiple-proposal scheme can be used to acquire approval for an arbitrarily deep authority tree. + * + * Note that at any time, a proposal can be approved in a single transaction if sufficient signatures are available + * on the proposal_update_operation, as long as the authority tree to approve the proposal does not exceed the + * maximum recursion depth. In practice, however, it is easier to use proposals to acquire all approvals, as this + * leverages on-chain notification of all relevant parties that their approval is required. Off-chain + * multi-signature approval requires some off-chain mechanism for acquiring several signatures on a single + * transaction. This off-chain synchronization can be avoided using proposals. + * @{ + */ + /** + * op_wrapper is used to get around the circular definition of operation and proposals that contain them. + */ + struct op_wrapper; + /** + * @brief The proposal_create_operation creates a transaction proposal, for use in multi-sig scenarios + * @ingroup operations + * + * Creates a transaction proposal. The operations which compose the transaction are listed in order in proposed_ops, + * and expiration_time specifies the time by which the proposal must be accepted or it will fail permanently. The + * expiration_time cannot be farther in the future than the maximum expiration time set in the global properties + * object. + */ + struct proposal_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + asset fee; + account_id_type fee_paying_account; + vector proposed_ops; + time_point_sec expiration_time; + optional review_period_seconds; + extensions_type extensions; + + /// Constructs a proposal_create_operation suitable for committee proposals, with fee, expiration time and review + /// period set appropriately. + static proposal_create_operation committee_proposal(const chain_parameters& param, fc::time_point_sec head_block_time ); + + account_id_type fee_payer()const { return fee_paying_account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + + void get_impacted_accounts( flat_set& )const; + }; + + /** + * @brief The proposal_update_operation updates an existing transaction proposal + * @ingroup operations + * + * This operation allows accounts to add or revoke approval of a proposed transaction. Signatures sufficient to + * satisfy the authority of each account in approvals are required on the transaction containing this operation. + * + * If an account with a multi-signature authority is listed in approvals_to_add or approvals_to_remove, either all + * required signatures to satisfy that account's authority must be provided in the transaction containing this + * operation, or a secondary proposal must be created which contains this operation. + * + * NOTE: If the proposal requires only an account's active authority, the account must not update adding its owner + * authority's approval. This is considered an error. An owner approval may only be added if the proposal requires + * the owner's authority. + * + * If an account's owner and active authority are both required, only the owner authority may approve. An attempt to + * add or remove active authority approval to such a proposal will fail. + */ + struct proposal_update_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + account_id_type fee_paying_account; + asset fee; + proposal_id_type proposal; + flat_set active_approvals_to_add; + flat_set active_approvals_to_remove; + flat_set owner_approvals_to_add; + flat_set owner_approvals_to_remove; + flat_set key_approvals_to_add; + flat_set key_approvals_to_remove; + extensions_type extensions; + + account_id_type fee_payer()const { return fee_paying_account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_required_authorities( vector& )const; + void get_required_active_authorities( flat_set& )const; + void get_required_owner_authorities( flat_set& )const; + }; + + /** + * @brief The proposal_delete_operation deletes an existing transaction proposal + * @ingroup operations + * + * This operation allows the early veto of a proposed transaction. It may be used by any account which is a required + * authority on the proposed transaction, when that account's holder feels the proposal is ill-advised and he decides + * he will never approve of it and wishes to put an end to all discussion of the issue. Because he is a required + * authority, he could simply refuse to add his approval, but this would leave the topic open for debate until the + * proposal expires. Using this operation, he can prevent any further breath from being wasted on such an absurd + * proposal. + */ + struct proposal_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + account_id_type fee_paying_account; + bool using_owner_authority = false; + asset fee; + proposal_id_type proposal; + extensions_type extensions; + + account_id_type fee_payer()const { return fee_paying_account; } + void validate()const; + }; + ///@} + +}} // graphene::chain + +FC_REFLECT( graphene::chain::proposal_create_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::proposal_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::proposal_delete_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::chain::proposal_create_operation, (fee)(fee_paying_account)(expiration_time) + (proposed_ops)(review_period_seconds)(extensions) ) +FC_REFLECT( graphene::chain::proposal_update_operation, (fee)(fee_paying_account)(proposal) + (active_approvals_to_add)(active_approvals_to_remove)(owner_approvals_to_add)(owner_approvals_to_remove) + (key_approvals_to_add)(key_approvals_to_remove)(extensions) ) +FC_REFLECT( graphene::chain::proposal_delete_operation, (fee)(fee_paying_account)(using_owner_authority)(proposal)(extensions) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/protocol.hpp b/libraries/chain/include/graphene/chain/protocol/protocol.hpp new file mode 100644 index 00000000..c995fe12 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/protocol.hpp @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/libraries/chain/include/graphene/chain/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp similarity index 82% rename from libraries/chain/include/graphene/chain/transaction.hpp rename to libraries/chain/include/graphene/chain/protocol/transaction.hpp index e4287d1b..4575742e 100644 --- a/libraries/chain/include/graphene/chain/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -16,19 +16,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include +#include #include -// this is for htonl() and ntohl() functions -// TODO: write and use FC wrappers for these functions -#ifndef WIN32 - #include -#else - #include -#endif - namespace graphene { namespace chain { /** @@ -96,6 +87,7 @@ namespace graphene { namespace chain { */ uint16_t relative_expiration = 1; vector operations; + extensions_type extensions; /// Calculate the digest for a transaction with a reference block /// @param ref_block_id Full block ID of the reference block @@ -105,20 +97,8 @@ namespace graphene { namespace chain { transaction_id_type id()const; void validate() const; - void set_expiration( fc::time_point_sec expiration_time ) - { - ref_block_num = 0; - relative_expiration = 0; - ref_block_prefix = expiration_time.sec_since_epoch(); - block_id_cache.reset(); - } - void set_expiration( const block_id_type& reference_block, unsigned_int lifetime_intervals = 3 ) - { - ref_block_num = ntohl(reference_block._hash[0]); - ref_block_prefix = reference_block._hash[1]; - relative_expiration = lifetime_intervals; - block_id_cache = reference_block; - } + void set_expiration( fc::time_point_sec expiration_time ); + void set_expiration( const block_id_type& reference_block, unsigned_int lifetime_intervals = 3 ); /// visit all operations template @@ -127,6 +107,14 @@ namespace graphene { namespace chain { for( auto& op : operations ) op.visit( std::forward( visitor ) ); } + template + void visit( Visitor&& visitor )const + { + for( auto& op : operations ) + op.visit( std::forward( visitor ) ); + } + + void get_required_authorities( flat_set& active, flat_set& owner, vector& other )const; protected: // Intentionally unreflected: does not go on wire @@ -141,18 +129,28 @@ namespace graphene { namespace chain { signed_transaction( const transaction& trx = transaction() ) : transaction(trx){} - void sign( key_id_type id, const private_key_type& key ); + /** signs and appends to signatures */ + const signature_type& sign( const private_key_type& key ); - flat_map signatures; + /** returns signature but does not append */ + signature_type sign( const private_key_type& key )const; - /** some operations may depend only upon a signature and not - * require account approval. This allows those extra signatures - * to be added to the transaction. + /** + * Given a set of private keys sign this transaction with a minimial subset of required keys. + * + * @pram get_auth - used to fetch the active authority required for an account referenced by another authority */ - flat_map extra_signatures; + void sign( const vector& keys, + const std::function& get_active, + const std::function& get_owner ); + + bool verify( const std::function& get_active, + const std::function& get_owner )const; + + vector signatures; /// Removes all operations and signatures - void clear() { operations.clear(); signatures.clear(); extra_signatures.clear(); } + void clear() { operations.clear(); signatures.clear(); } }; /** @@ -183,6 +181,6 @@ namespace graphene { namespace chain { } } -FC_REFLECT( graphene::chain::transaction, (ref_block_num)(ref_block_prefix)(relative_expiration)(operations) ) -FC_REFLECT_DERIVED( graphene::chain::signed_transaction, (graphene::chain::transaction), (signatures)(extra_signatures) ) +FC_REFLECT( graphene::chain::transaction, (ref_block_num)(ref_block_prefix)(relative_expiration)(operations)(extensions) ) +FC_REFLECT_DERIVED( graphene::chain::signed_transaction, (graphene::chain::transaction), (signatures) ) FC_REFLECT_DERIVED( graphene::chain::processed_transaction, (graphene::chain::signed_transaction), (operation_results) ) diff --git a/libraries/chain/include/graphene/chain/protocol/transfer.hpp b/libraries/chain/include/graphene/chain/protocol/transfer.hpp new file mode 100644 index 00000000..7e5b528c --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/transfer.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + /** + * @ingroup operations + * + * @brief Transfers an amount of one asset from one account to another + * + * Fees are paid by the "from" account + * + * @pre amount.amount > 0 + * @pre fee.amount >= 0 + * @pre from != to + * @post from account's balance will be reduced by fee and amount + * @post to account's balance will be increased by amount + * @return n/a + */ + struct transfer_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; /// only required for large memos. + }; + + asset fee; + /// Account to transfer asset from + account_id_type from; + /// Account to transfer asset to + account_id_type to; + /// The amount of asset to transfer from @ref from to @ref to + asset amount; + + /// User provided data encrypted to the memo key of the "to" account + optional memo; + extensions_type extensions; + + account_id_type fee_payer()const { return from; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_impacted_accounts( flat_set& i )const + { i.insert(to); } + }; + + /** + * @class override_transfer_operation + * @brief Allows the issuer of an asset to transfer an asset from any account to any account if they have override_authority + * @ingroup operations + * + * @pre amount.asset_id->issuer == issuer + * @pre issuer != from because this is pointless, use a normal transfer operation + */ + struct override_transfer_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; /// only required for large memos. + }; + + asset fee; + account_id_type issuer; + /// Account to transfer asset from + account_id_type from; + /// Account to transfer asset to + account_id_type to; + /// The amount of asset to transfer from @ref from to @ref to + asset amount; + + /// User provided data encrypted to the memo key of the "to" account + optional memo; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_impacted_accounts( flat_set& i )const + { + i.insert(to); + i.insert(from); + i.insert(issuer); + } + }; + +}} // graphene::chain + +FC_REFLECT( graphene::chain::transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::override_transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) + +FC_REFLECT( graphene::chain::override_transfer_operation, (fee)(issuer)(from)(to)(amount)(memo)(extensions) ) +FC_REFLECT( graphene::chain::transfer_operation, (fee)(from)(to)(amount)(memo)(extensions) ) + diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp similarity index 57% rename from libraries/chain/include/graphene/chain/types.hpp rename to libraries/chain/include/graphene/chain/protocol/types.hpp index 7a7d15d1..b9a1a0fb 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -18,7 +18,6 @@ #pragma once #include #include -#include #include #include #include @@ -28,11 +27,18 @@ #include #include #include +#include +#include +#include +#include + #include #include #include -#include +#include +#include #include +#include namespace graphene { namespace chain { using namespace graphene::db; @@ -51,6 +57,7 @@ namespace graphene { namespace chain { using std::tie; using std::make_pair; + using fc::smart_ref; using fc::variant_object; using fc::variant; using fc::enum_type; @@ -66,6 +73,7 @@ namespace graphene { namespace chain { using fc::ecc::range_proof_type; using fc::ecc::range_proof_info; using fc::ecc::commitment_type; + struct void_t{}; typedef fc::ecc::private_key private_key_type; @@ -101,14 +109,12 @@ namespace graphene { namespace chain { { null_object_type, base_object_type, - key_object_type, account_object_type, asset_object_type, force_settlement_object_type, - delegate_object_type, + committee_member_object_type, witness_object_type, limit_order_object_type, - short_order_object_type, call_order_object_type, custom_object_type, proposal_object_type, @@ -116,6 +122,7 @@ namespace graphene { namespace chain { withdraw_permission_object_type, vesting_balance_object_type, worker_object_type, + balance_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -126,7 +133,7 @@ namespace graphene { namespace chain { impl_index_meta_object_type, impl_asset_dynamic_data_type, impl_asset_bitasset_data_type, - impl_delegate_feeds_object_type, + impl_committee_member_feeds_object_type, impl_account_balance_object_type, impl_account_statistics_object_type, impl_account_debt_object_type, @@ -142,17 +149,17 @@ namespace graphene { namespace chain { meta_account_object_type }; + struct chain_parameters; + //typedef fc::unsigned_int object_id_type; //typedef uint64_t object_id_type; class account_object; - class delegate_object; + class committee_member_object; class witness_object; class asset_object; class force_settlement_object; - class key_object; class limit_order_object; - class short_order_object; class call_order_object; class custom_object; class proposal_object; @@ -161,15 +168,14 @@ namespace graphene { namespace chain { class vesting_balance_object; class witness_schedule_object; class worker_object; + class balance_object; - typedef object_id< protocol_ids, key_object_type, key_object> key_id_type; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; typedef object_id< protocol_ids, force_settlement_object_type, force_settlement_object> force_settlement_id_type; - typedef object_id< protocol_ids, delegate_object_type, delegate_object> delegate_id_type; + typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; typedef object_id< protocol_ids, witness_object_type, witness_object> witness_id_type; typedef object_id< protocol_ids, limit_order_object_type, limit_order_object> limit_order_id_type; - typedef object_id< protocol_ids, short_order_object_type, short_order_object> short_order_id_type; typedef object_id< protocol_ids, call_order_object_type, call_order_object> call_order_id_type; typedef object_id< protocol_ids, custom_object_type, custom_object> custom_id_type; typedef object_id< protocol_ids, proposal_object_type, proposal_object> proposal_id_type; @@ -177,9 +183,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, withdraw_permission_object_type,withdraw_permission_object> withdraw_permission_id_type; typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; - - typedef object_id< relative_protocol_ids, key_object_type, key_object> relative_key_id_type; - typedef object_id< relative_protocol_ids, account_object_type, account_object> relative_account_id_type; + typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; // implementation types class global_property_object; @@ -196,7 +200,7 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; - typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> dynamic_asset_data_id_type; + typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type; typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type; typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type; typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type; @@ -209,15 +213,15 @@ namespace graphene { namespace chain { account_transaction_history_object> account_transaction_history_id_type; typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object > witness_schedule_id_type; - typedef fc::array symbol_type; - typedef fc::ripemd160 block_id_type; - typedef fc::ripemd160 checksum_type; - typedef fc::ripemd160 transaction_id_type; - typedef fc::sha256 digest_type; - typedef fc::ecc::compact_signature signature_type; - typedef safe share_type; - typedef fc::sha224 secret_hash_type; - typedef uint16_t weight_type; + typedef fc::array symbol_type; + typedef fc::ripemd160 block_id_type; + typedef fc::ripemd160 checksum_type; + typedef fc::ripemd160 transaction_id_type; + typedef fc::sha256 digest_type; + typedef fc::ecc::compact_signature signature_type; + typedef safe share_type; + typedef fc::ripemd160 secret_hash_type; + typedef uint16_t weight_type; /** * @brief An ID for some votable object @@ -314,73 +318,6 @@ namespace graphene { namespace chain { return std::to_string(type()) + ":" + std::to_string(instance()); } }; - - struct fee_schedule_type - { - /** - * @brief The fee_set_visitor struct sets all fees to a particular value in one fell swoop - * - * Example: - * @code - * fee_schedule_type sch; - * // Set all fees to 50 - * fc::reflector::visit(fee_schedule_type::fee_set_visitor{sch, 50}); - * @endcode - */ - struct fee_set_visitor { - fee_schedule_type& f; - uint32_t fee; - - template - void operator()(const char*)const - { - f.*member = fee; - } - }; - - fee_schedule_type() - { - memset( (char*)this, 0, sizeof(*this) ); - } - - uint32_t key_create_fee; ///< the cost to register a public key with the blockchain - uint32_t account_create_fee; ///< the cost to register the cheapest non-free account - uint32_t account_len8_fee; - uint32_t account_len7_fee; - uint32_t account_len6_fee; - uint32_t account_len5_fee; - uint32_t account_len4_fee; - uint32_t account_len3_fee; - uint32_t account_len2_fee; - uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false - uint32_t account_whitelist_fee; ///< the fee to whitelist an account - uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates - uint32_t witness_withdraw_pay_fee; ///< fee for withdrawing witness pay - uint32_t transfer_fee; ///< fee for transferring some asset - uint32_t limit_order_fee; ///< fee for placing a limit order in the markets - uint32_t short_order_fee; ///< fee for placing a short order in the markets - uint32_t publish_feed_fee; ///< fee for publishing a price feed - uint32_t asset_create_fee; ///< the cost to register the cheapest asset - uint32_t asset_update_fee; ///< the cost to modify a registered asset - uint32_t asset_issue_fee; ///< the cost to modify a registered asset - uint32_t asset_fund_fee_pool_fee; ///< the cost to add funds to an asset's fee pool - uint32_t asset_settle_fee; ///< the cost to trigger a forced settlement of a market-issued asset - uint32_t market_fee; ///< a percentage charged on market orders - uint32_t transaction_fee; ///< a base price for every transaction - uint32_t data_fee; ///< a price per 1024 bytes of user data - uint32_t signature_fee; ///< a surcharge on transactions with more than 2 signatures. - uint32_t global_parameters_update_fee; ///< the cost to update the global parameters - uint32_t membership_annual_fee; ///< the annual cost of a membership subscription - uint32_t membership_lifetime_fee; ///< the cost to upgrade to a lifetime member - uint32_t withdraw_permission_update_fee; ///< the cost to create/update a withdraw permission - uint32_t vesting_balance_create_fee; - uint32_t vesting_balance_withdraw_fee; - uint32_t global_settle_fee; - uint32_t worker_create_fee; ///< the cost to create a new worker - uint32_t worker_delete_fee; ///< the cost to delete a worker - }; - - struct public_key_type { struct binary_key @@ -389,9 +326,7 @@ namespace graphene { namespace chain { uint32_t check; fc::ecc::public_key_data data; }; - fc::ecc::public_key_data key_data; - public_key_type(); public_key_type( const fc::ecc::public_key_data& data ); public_key_type( const fc::ecc::public_key& pubkey ); @@ -402,62 +337,8 @@ namespace graphene { namespace chain { friend bool operator == ( const public_key_type& p1, const fc::ecc::public_key& p2); friend bool operator == ( const public_key_type& p1, const public_key_type& p2); friend bool operator != ( const public_key_type& p1, const public_key_type& p2); - }; - - struct chain_parameters - { - fee_schedule_type current_fees; ///< current schedule of fees - uint8_t block_interval = GRAPHENE_DEFAULT_BLOCK_INTERVAL; ///< interval in seconds between blocks - uint32_t maintenance_interval = GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL; ///< interval in sections between blockchain maintenance events - uint32_t maximum_transaction_size = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE; ///< maximum allowable size in bytes for a transaction - uint32_t maximum_block_size = GRAPHENE_DEFAULT_MAX_BLOCK_SIZE; ///< maximum allowable size in bytes for a block - uint32_t maximum_undo_history = GRAPHENE_DEFAULT_MAX_UNDO_HISTORY; ///< maximum number of undo states to keep in RAM - uint32_t maximum_time_until_expiration = GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION; ///< maximum lifetime in seconds for transactions to be valid, before expiring - uint32_t maximum_proposal_lifetime = GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC; ///< maximum lifetime in seconds for proposed transactions to be kept, before expiring - uint32_t genesis_proposal_review_period = GRAPHENE_DEFAULT_GENESIS_PROPOSAL_REVIEW_PERIOD_SEC; ///< minimum time in seconds that a proposed transaction requiring genesis authority may not be signed, prior to expiration - uint8_t maximum_asset_whitelist_authorities = GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES; ///< maximum number of accounts which an asset may list as authorities for its whitelist OR blacklist - uint8_t maximum_asset_feed_publishers = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS; ///< the maximum number of feed publishers for a given asset - uint16_t maximum_witness_count = GRAPHENE_DEFAULT_MAX_WITNESSES; ///< maximum number of active witnesses - uint16_t maximum_committee_count = GRAPHENE_DEFAULT_MAX_COMMITTEE; ///< maximum number of active delegates - uint16_t maximum_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have - uint16_t burn_percent_of_fee = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of the network's allocation of a fee that is taken out of circulation - uint16_t network_percent_of_fee = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; ///< percent of transaction fees paid to network - uint16_t lifetime_referrer_percent_of_fee = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; ///< percent of transaction fees paid to network - uint32_t cashback_vesting_period_seconds = GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC; ///< time after cashback rewards are accrued before they become liquid - share_type cashback_vesting_threshold = GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD; ///< the maximum cashback that can be received without vesting - uint16_t max_bulk_discount_percent_of_fee = GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT; ///< the maximum percentage discount for bulk discounts - share_type bulk_discount_threshold_min = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MIN; ///< the minimum amount of fees paid to qualify for bulk discounts - share_type bulk_discount_threshold_max = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MAX; ///< the amount of fees paid to qualify for the max bulk discount percent - bool count_non_member_votes = true; ///< set to false to restrict voting privlegages to member accounts - bool allow_non_member_whitelists = false; ///< true if non-member accounts may set whitelists and blacklists; false otherwise - share_type witness_pay_per_block = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK; ///< CORE to be allocated to witnesses (per block) - share_type worker_budget_per_day = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY; ///< CORE to be allocated to workers (per day) - - void validate()const - { - FC_ASSERT( burn_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( network_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( max_bulk_discount_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( network_percent_of_fee + lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( bulk_discount_threshold_min <= bulk_discount_threshold_max ); - FC_ASSERT( bulk_discount_threshold_min > 0 ); - - FC_ASSERT( block_interval <= GRAPHENE_MAX_BLOCK_INTERVAL ); - FC_ASSERT( block_interval > 0 ); - FC_ASSERT( maintenance_interval > block_interval, - "Maintenance interval must be longer than block interval" ); - FC_ASSERT( maintenance_interval % block_interval == 0, - "Maintenance interval must be a multiple of block interval" ); - FC_ASSERT( maximum_transaction_size >= GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT, - "Transaction size limit is too low" ); - FC_ASSERT( maximum_block_size >= GRAPHENE_MIN_BLOCK_SIZE_LIMIT, - "Block size limit is too low" ); - FC_ASSERT( maximum_time_until_expiration > block_interval, - "Maximum transaction expiration time must be greater than a block interval" ); - FC_ASSERT( maximum_proposal_lifetime - genesis_proposal_review_period > block_interval, - "Genesis proposal review period must be less than the maximum proposal lifetime" ); - } + // TODO: This is temporary for testing + bool is_valid_v1( const std::string& base58str ); }; } } // graphene::chain @@ -469,8 +350,9 @@ namespace fc void to_variant( const graphene::chain::vote_id_type& var, fc::variant& vo ); void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo ); } - FC_REFLECT_TYPENAME( graphene::chain::vote_id_type::vote_type ) +FC_REFLECT_TYPENAME( fc::flat_set ) + FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) ) FC_REFLECT( graphene::chain::vote_id_type, (content) ) @@ -480,14 +362,12 @@ FC_REFLECT( graphene::chain::public_key_type::binary_key, (data)(check) ) FC_REFLECT_ENUM( graphene::chain::object_type, (null_object_type) (base_object_type) - (key_object_type) (account_object_type) (force_settlement_object_type) (asset_object_type) - (delegate_object_type) + (committee_member_object_type) (witness_object_type) (limit_order_object_type) - (short_order_object_type) (call_order_object_type) (custom_object_type) (proposal_object_type) @@ -495,6 +375,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (withdraw_permission_object_type) (vesting_balance_object_type) (worker_object_type) + (balance_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -503,7 +384,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_index_meta_object_type) (impl_asset_dynamic_data_type) (impl_asset_bitasset_data_type) - (impl_delegate_feeds_object_type) + (impl_committee_member_feeds_object_type) (impl_account_balance_object_type) (impl_account_statistics_object_type) (impl_account_debt_object_type) @@ -515,79 +396,14 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, FC_REFLECT_ENUM( graphene::chain::meta_info_object_type, (meta_account_object_type)(meta_asset_object_type) ) +FC_REFLECT_TYPENAME( graphene::chain::share_type ) -FC_REFLECT( graphene::chain::fee_schedule_type, - (key_create_fee) - (account_create_fee) - (account_len8_fee) - (account_len7_fee) - (account_len6_fee) - (account_len5_fee) - (account_len4_fee) - (account_len3_fee) - (account_len2_fee) - (account_premium_fee) - (account_whitelist_fee) - (delegate_create_fee) - (witness_withdraw_pay_fee) - (transfer_fee) - (limit_order_fee) - (short_order_fee) - (publish_feed_fee) - (asset_create_fee) - (asset_update_fee) - (asset_issue_fee) - (asset_fund_fee_pool_fee) - (asset_settle_fee) - (market_fee) - (transaction_fee) - (data_fee) - (signature_fee) - (global_parameters_update_fee) - (membership_annual_fee) - (membership_lifetime_fee) - (withdraw_permission_update_fee) - (vesting_balance_create_fee) - (vesting_balance_withdraw_fee) - (global_settle_fee) - (worker_create_fee) - (worker_delete_fee) - ) - -FC_REFLECT( graphene::chain::chain_parameters, - (current_fees) - (block_interval) - (maintenance_interval) - (maximum_transaction_size) - (maximum_block_size) - (maximum_undo_history) - (maximum_time_until_expiration) - (maximum_proposal_lifetime) - (maximum_asset_whitelist_authorities) - (maximum_asset_feed_publishers) - (maximum_authority_membership) - (burn_percent_of_fee) - (network_percent_of_fee) - (lifetime_referrer_percent_of_fee) - (max_bulk_discount_percent_of_fee) - (cashback_vesting_period_seconds) - (cashback_vesting_threshold) - (bulk_discount_threshold_min) - (bulk_discount_threshold_max) - (count_non_member_votes) - (allow_non_member_whitelists) - (witness_pay_per_block) - (worker_budget_per_day) - ) - -FC_REFLECT_TYPENAME( graphene::chain::key_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_id_type ) FC_REFLECT_TYPENAME( graphene::chain::asset_id_type ) FC_REFLECT_TYPENAME( graphene::chain::force_settlement_id_type ) -FC_REFLECT_TYPENAME( graphene::chain::delegate_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::committee_member_id_type ) FC_REFLECT_TYPENAME( graphene::chain::witness_id_type ) FC_REFLECT_TYPENAME( graphene::chain::limit_order_id_type ) -FC_REFLECT_TYPENAME( graphene::chain::short_order_id_type ) FC_REFLECT_TYPENAME( graphene::chain::call_order_id_type ) FC_REFLECT_TYPENAME( graphene::chain::custom_id_type ) FC_REFLECT_TYPENAME( graphene::chain::proposal_id_type ) @@ -595,11 +411,9 @@ FC_REFLECT_TYPENAME( graphene::chain::operation_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::withdraw_permission_id_type ) FC_REFLECT_TYPENAME( graphene::chain::vesting_balance_id_type ) FC_REFLECT_TYPENAME( graphene::chain::worker_id_type ) -FC_REFLECT_TYPENAME( graphene::chain::relative_key_id_type ) -FC_REFLECT_TYPENAME( graphene::chain::relative_account_id_type ) FC_REFLECT_TYPENAME( graphene::chain::global_property_id_type ) FC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_id_type ) -FC_REFLECT_TYPENAME( graphene::chain::dynamic_asset_data_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_id_type ) FC_REFLECT_TYPENAME( graphene::chain::asset_bitasset_data_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_balance_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_statistics_id_type ) @@ -608,5 +422,6 @@ FC_REFLECT_TYPENAME( graphene::chain::transaction_obj_id_type ) FC_REFLECT_TYPENAME( graphene::chain::block_summary_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::witness_schedule_id_type ) +FC_REFLECT( graphene::chain::void_t, ) FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (charge_market_fee)(white_list)(transfer_restricted)(override_authority)(disable_force_settle)(global_settle) ) diff --git a/libraries/chain/include/graphene/chain/protocol/vesting.hpp b/libraries/chain/include/graphene/chain/protocol/vesting.hpp new file mode 100644 index 00000000..c3674a6c --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/vesting.hpp @@ -0,0 +1,99 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct linear_vesting_policy_initializer + { + /** while vesting begins on begin_timestamp, none may be claimed before vesting_cliff_seconds have passed */ + fc::time_point_sec begin_timestamp; + uint32_t vesting_cliff_seconds = 0; + uint32_t vesting_duration_seconds = 0; + }; + + struct cdd_vesting_policy_initializer + { + /** while coindays may accrue over time, none may be claimed before the start_claim time */ + fc::time_point_sec start_claim; + uint32_t vesting_seconds = 0; + cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){} + }; + + typedef fc::static_variant vesting_policy_initializer; + + + + /** + * @brief Create a vesting balance. + * @ingroup operations + * + * The chain allows a user to create a vesting balance. + * Normally, vesting balances are created automatically as part + * of cashback and worker operations. This operation allows + * vesting balances to be created manually as well. + * + * Manual creation of vesting balances can be used by a stakeholder + * to publicly demonstrate that they are committed to the chain. + * It can also be used as a building block to create transactions + * that function like public debt. Finally, it is useful for + * testing vesting balance functionality. + * + * @return ID of newly created vesting_balance_object + */ + struct vesting_balance_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type creator; ///< Who provides funds initially + account_id_type owner; ///< Who is able to withdraw the balance + asset amount; + vesting_policy_initializer policy; + + account_id_type fee_payer()const { return creator; } + void validate()const + { + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( amount.amount > 0 ); + } + void get_impacted_accounts( flat_set& i )const + { i.insert(owner); } + + }; + + /** + * @brief Withdraw from a vesting balance. + * @ingroup operations + * + * Withdrawal from a not-completely-mature vesting balance + * will result in paying fees. + * + * @return Nothing + */ + struct vesting_balance_withdraw_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + vesting_balance_id_type vesting_balance; + account_id_type owner; ///< Must be vesting_balance.owner + asset amount; + + account_id_type fee_payer()const { return owner; } + void validate()const + { + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( amount.amount > 0 ); + } + }; + +}} // graphene::chain +FC_REFLECT( graphene::chain::vesting_balance_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy) ) +FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) ) + +FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) ) +FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) ) +FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer ) diff --git a/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp b/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp new file mode 100644 index 00000000..a6d7582e --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp @@ -0,0 +1,167 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + /** + * @brief Create a new withdrawal permission + * @ingroup operations + * + * This operation creates a withdrawal permission, which allows some authorized account to withdraw from an + * authorizing account. This operation is primarily useful for scheduling recurring payments. + * + * Withdrawal permissions define withdrawal periods, which is a span of time during which the authorized account may + * make a withdrawal. Any number of withdrawals may be made so long as the total amount withdrawn per period does + * not exceed the limit for any given period. + * + * Withdrawal permissions authorize only a specific pairing, i.e. a permission only authorizes one specified + * authorized account to withdraw from one specified authorizing account. Withdrawals are limited and may not exceet + * the withdrawal limit. The withdrawal must be made in the same asset as the limit; attempts with withdraw any + * other asset type will be rejected. + * + * The fee for this operation is paid by withdraw_from_account, and this account is required to authorize this + * operation. + */ + struct withdraw_permission_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// The account authorizing withdrawals from its balances + account_id_type withdraw_from_account; + /// The account authorized to make withdrawals from withdraw_from_account + account_id_type authorized_account; + /// The maximum amount authorized_account is allowed to withdraw in a given withdrawal period + asset withdrawal_limit; + /// Length of the withdrawal period in seconds + uint32_t withdrawal_period_sec; + /// The number of withdrawal periods this permission is valid for + uint32_t periods_until_expiration; + /// Time at which the first withdrawal period begins; must be in the future + time_point_sec period_start_time; + + account_id_type fee_payer()const { return withdraw_from_account; } + void validate()const; + + void get_impacted_accounts( flat_set& i)const + { i.insert( authorized_account ); } + }; + + /** + * @brief Update an existing withdraw permission + * @ingroup operations + * + * This oeration is used to update the settings for an existing withdrawal permission. The accounts to withdraw to + * and from may never be updated. The fields which may be updated are the withdrawal limit (both amount and asset + * type may be updated), the withdrawal period length, the remaining number of periods until expiration, and the + * starting time of the new period. + * + * Fee is paid by withdraw_from_account, which is required to authorize this operation + */ + struct withdraw_permission_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// This account pays the fee. Must match permission_to_update->withdraw_from_account + account_id_type withdraw_from_account; + /// The account authorized to make withdrawals. Must match permission_to_update->authorized_account + account_id_type authorized_account; + /// ID of the permission which is being updated + withdraw_permission_id_type permission_to_update; + /// New maximum amount the withdrawer is allowed to charge per withdrawal period + asset withdrawal_limit; + /// New length of the period between withdrawals + uint32_t withdrawal_period_sec; + /// New beginning of the next withdrawal period; must be in the future + time_point_sec period_start_time; + /// The new number of withdrawal periods for which this permission will be valid + uint32_t periods_until_expiration; + + account_id_type fee_payer()const { return withdraw_from_account; } + void validate()const; + void get_impacted_accounts( flat_set& i)const + { i.insert( authorized_account ); } + }; + + /** + * @brief Withdraw from an account which has published a withdrawal permission + * @ingroup operations + * + * This operation is used to withdraw from an account which has authorized such a withdrawal. It may be executed at + * most once per withdrawal period for the given permission. On execution, amount_to_withdraw is transferred from + * withdraw_from_account to withdraw_to_account, assuming amount_to_withdraw is within the withdrawal limit. The + * withdrawal permission will be updated to note that the withdrawal for the current period has occurred, and + * further withdrawals will not be permitted until the next withdrawal period, assuming the permission has not + * expired. This operation may be executed at any time within the current withdrawal period. + * + * Fee is paid by withdraw_to_account, which is required to authorize this operation + */ + struct withdraw_permission_claim_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + /// Paid by withdraw_to_account + asset fee; + /// ID of the permission authorizing this withdrawal + withdraw_permission_id_type withdraw_permission; + /// Must match withdraw_permission->withdraw_from_account + account_id_type withdraw_from_account; + /// Must match withdraw_permision->authorized_account + account_id_type withdraw_to_account; + /// Amount to withdraw. Must not exceed withdraw_permission->withdrawal_limit + asset amount_to_withdraw; + /// Memo for withdraw_from_account. Should generally be encrypted with withdraw_from_account->memo_key + optional memo; + + account_id_type fee_payer()const { return withdraw_to_account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + void get_impacted_accounts( flat_set& i)const + { i.insert( withdraw_from_account ); } + }; + + /** + * @brief Delete an existing withdrawal permission + * @ingroup operations + * + * This operation cancels a withdrawal permission, thus preventing any future withdrawals using that permission. + * + * Fee is paid by withdraw_from_account, which is required to authorize this operation + */ + struct withdraw_permission_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + /// Must match withdrawal_permission->withdraw_from_account. This account pays the fee. + account_id_type withdraw_from_account; + /// The account previously authorized to make withdrawals. Must match withdrawal_permission->authorized_account + account_id_type authorized_account; + /// ID of the permission to be revoked. + withdraw_permission_id_type withdrawal_permission; + + account_id_type fee_payer()const { return withdraw_from_account; } + void validate()const; + void get_impacted_accounts( flat_set& i)const + { i.insert( authorized_account ); } + }; + +} } // graphene::chain +FC_REFLECT( graphene::chain::withdraw_permission_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::withdraw_permission_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::withdraw_permission_claim_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::withdraw_permission_delete_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::chain::withdraw_permission_create_operation, (fee)(withdraw_from_account)(authorized_account) + (withdrawal_limit)(withdrawal_period_sec)(periods_until_expiration)(period_start_time) ) +FC_REFLECT( graphene::chain::withdraw_permission_update_operation, (fee)(withdraw_from_account)(authorized_account) + (permission_to_update)(withdrawal_limit)(withdrawal_period_sec)(period_start_time)(periods_until_expiration) ) +FC_REFLECT( graphene::chain::withdraw_permission_claim_operation, (fee)(withdraw_permission)(withdraw_from_account)(withdraw_to_account)(amount_to_withdraw)(memo) ); +FC_REFLECT( graphene::chain::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account) + (withdrawal_permission) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/witness.hpp b/libraries/chain/include/graphene/chain/protocol/witness.hpp new file mode 100644 index 00000000..f375b1fa --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/witness.hpp @@ -0,0 +1,59 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @brief Create a witness object, as a bid to hold a witness position on the network. + * @ingroup operations + * + * Accounts which wish to become witnesses may use this operation to create a witness object which stakeholders may + * vote on to approve its position as a witness. + */ + struct witness_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// The account which owns the witness. This account pays the fee for this operation. + account_id_type witness_account; + string url; + public_key_type block_signing_key; + secret_hash_type initial_secret; + + account_id_type fee_payer()const { return witness_account; } + void validate()const; + }; + + + /** + * @ingroup operations + * Used to move witness pay from accumulated_income to their account balance. + * + * TODO: remove this operation, send witness pay into a vesting balance object and + * have the witness claim the funds from there. + */ + struct witness_withdraw_pay_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// The account to pay. Must match from_witness->witness_account. This account pays the fee for this operation. + account_id_type to_account; + witness_id_type from_witness; + share_type amount; + + account_id_type fee_payer()const { return to_account; } + void validate()const; + }; + + + /// TODO: witness_resign_operation : public base_operation + +} } // graphene::chain + +FC_REFLECT( graphene::chain::witness_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::witness_withdraw_pay_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::chain::witness_create_operation, (fee)(witness_account)(url)(block_signing_key)(initial_secret) ) +FC_REFLECT( graphene::chain::witness_withdraw_pay_operation, (fee)(from_witness)(to_account)(amount) ) diff --git a/libraries/chain/include/graphene/chain/protocol/worker.hpp b/libraries/chain/include/graphene/chain/protocol/worker.hpp new file mode 100644 index 00000000..8f4e0465 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/worker.hpp @@ -0,0 +1,83 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + /** + * @defgroup workers The Blockchain Worker System + * @ingroup operations + * + * Graphene blockchains allow the creation of special "workers" which are elected positions paid by the blockchain + * for services they provide. There may be several types of workers, and the semantics of how and when they are paid + * are defined by the @ref worker_type_enum enumeration. All workers are elected by core stakeholder approval, by + * voting for or against them. + * + * Workers are paid from the blockchain's daily budget if their total approval (votes for - votes against) is + * positive, ordered from most positive approval to least, until the budget is exhausted. Payments are processed at + * the blockchain maintenance interval. If a worker does not have positive approval during payment processing, or if + * the chain's budget is exhausted before the worker is paid, that worker is simply not paid at that interval. + * Payment is not prorated based on percentage of the interval the worker was approved. If the chain attempts to pay + * a worker, but the budget is insufficient to cover its entire pay, the worker is paid the remaining budget funds, + * even though this does not fulfill his total pay. The worker will not receive extra pay to make up the difference + * later. Worker pay is placed in a vesting balance and vests over the number of days specified at the worker's + * creation. + * + * Once created, a worker is immutable and will be kept by the blockchain forever. + * + * @{ + */ + + + struct vesting_balance_worker_initializer + { + vesting_balance_worker_initializer(uint16_t days=0):pay_vesting_period_days(days){} + uint16_t pay_vesting_period_days = 0; + }; + + struct burn_worker_initializer + {}; + + struct refund_worker_initializer + {}; + + + typedef static_variant< + refund_worker_initializer, + vesting_balance_worker_initializer, + burn_worker_initializer > worker_initializer; + + + /** + * @brief Create a new worker object + * @ingroup operations + */ + struct worker_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5000*GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type owner; + time_point_sec work_begin_date; + time_point_sec work_end_date; + share_type daily_pay; + string name; + string url; + /// This should be set to the initializer appropriate for the type of worker to be created. + worker_initializer initializer; + + account_id_type fee_payer()const { return owner; } + void validate()const; + }; + ///@} + +} } + +FC_REFLECT( graphene::chain::vesting_balance_worker_initializer, (pay_vesting_period_days) ) +FC_REFLECT( graphene::chain::burn_worker_initializer, ) +FC_REFLECT( graphene::chain::refund_worker_initializer, ) +FC_REFLECT_TYPENAME( graphene::chain::worker_initializer ) + +FC_REFLECT( graphene::chain::worker_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::worker_create_operation, + (fee)(owner)(work_begin_date)(work_end_date)(daily_pay)(name)(url)(initializer) ) + diff --git a/libraries/chain/include/graphene/chain/short_order_evaluator.hpp b/libraries/chain/include/graphene/chain/short_order_evaluator.hpp deleted file mode 100644 index acf1acd4..00000000 --- a/libraries/chain/include/graphene/chain/short_order_evaluator.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once -#include -#include -#include - -namespace graphene { namespace chain { - - class short_order_create_evaluator : public evaluator - { - public: - typedef short_order_create_operation operation_type; - - object_id_type do_evaluate( const short_order_create_operation& o ); - object_id_type do_apply( const short_order_create_operation& o ); - - const short_order_create_operation* _op = nullptr; - const account_object* _seller = nullptr; - const asset_object* _sell_asset = nullptr; - const asset_object* _receive_asset = nullptr; - share_type _priority_fee = 0; - }; - - class short_order_cancel_evaluator : public evaluator - { - public: - typedef short_order_cancel_operation operation_type; - - asset do_evaluate( const short_order_cancel_operation& o ); - asset do_apply( const short_order_cancel_operation& o ); - - const short_order_object* _order; - }; - - class call_order_update_evaluator : public evaluator - { - public: - typedef call_order_update_operation operation_type; - - asset do_evaluate( const call_order_update_operation& o ); - asset do_apply( const call_order_update_operation& o ); - - bool _closing_order = false; - const asset_object* _debt_asset = nullptr; - const account_object* _paying_account = nullptr; - const call_order_object* _order = nullptr; - }; - -} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp index 20faadf5..6054763c 100644 --- a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp +++ b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp @@ -16,46 +16,48 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include -#include -#include +#include namespace graphene { namespace chain { class database; struct signed_transaction; /** - * Place holder for state tracked while processing a - * transaction. This class provides helper methods - * that are common to many different operations and - * also tracks which keys have signed the transaction + * Place holder for state tracked while processing a transaction. This class provides helper methods that are + * common to many different operations and also tracks which keys have signed the transaction */ class transaction_evaluation_state { public: - transaction_evaluation_state( database* db = nullptr, bool skip_authority_check = false ) - :_db(db),_skip_authority_check(skip_authority_check){} + transaction_evaluation_state( database* db = nullptr ) + :_db(db){} - bool check_authority( const account_object&, authority::classification auth_class = authority::active, int depth = 0 ); + bool check_authority(const account_object&, + authority::classification auth_class = authority::active, + int depth = 0); + + bool check_authority(const authority&, + authority::classification auth_class = authority::active, + int depth = 0); database& db()const { FC_ASSERT( _db ); return *_db; } - bool signed_by( key_id_type id )const; + bool signed_by(const public_key_type& k); + bool signed_by(const address& k); - /** derived from signatures on transaction - flat_set
signed_by; - */ - /** cached approval (accounts and keys) */ - flat_set< pair > approved_by; + /// cached approval (accounts and keys) + flat_set> approved_by; + + /// Used to look up new objects using transaction relative IDs + vector operation_results; /** - * Used to lookup new objects using transaction relative IDs + * When an address is referenced via check authority it is flagged as being used, all addresses must be + * flagged as being used or the transaction will fail. */ - vector operation_results; - - const signed_transaction* _trx = nullptr; - database* _db = nullptr; - bool _skip_authority_check = false; - bool _is_proposed_trx = false; + flat_map _sigs; + const signed_transaction* _trx = nullptr; + database* _db = nullptr; + bool _is_proposed_trx = false; }; } } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/transaction_object.hpp b/libraries/chain/include/graphene/chain/transaction_object.hpp index 8cec3797..e8f630e7 100644 --- a/libraries/chain/include/graphene/chain/transaction_object.hpp +++ b/libraries/chain/include/graphene/chain/transaction_object.hpp @@ -18,7 +18,7 @@ #pragma once #include -#include +#include #include #include #include @@ -34,12 +34,9 @@ namespace graphene { namespace chain { using boost::multi_index_container; using namespace boost::multi_index; /** - * The purpose of this object is to enable the detection - * of duplicate transactions. When a transaction is - * included in a block a transaction_object is - * added. At the end of block processing all - * transaction_objects that have expired can - * be removed from the index. + * The purpose of this object is to enable the detection of duplicate transactions. When a transaction is included + * in a block a transaction_object is added. At the end of block processing all transaction_objects that have + * expired can be removed from the index. */ class transaction_object : public abstract_object { @@ -52,7 +49,6 @@ namespace graphene { namespace chain { transaction_id_type trx_id; }; - struct by_expiration; struct by_id; struct by_trx_id; @@ -66,7 +62,6 @@ namespace graphene { namespace chain { > transaction_multi_index_type; typedef generic_index transaction_index; - } } FC_REFLECT_DERIVED( graphene::chain::transaction_object, (graphene::db::object), (trx)(expiration) ) diff --git a/libraries/chain/include/graphene/chain/transfer_evaluator.hpp b/libraries/chain/include/graphene/chain/transfer_evaluator.hpp index c7551cee..b9c70902 100644 --- a/libraries/chain/include/graphene/chain/transfer_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/transfer_evaluator.hpp @@ -16,8 +16,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include #include -#include #include namespace graphene { namespace chain { @@ -31,4 +31,13 @@ namespace graphene { namespace chain { void_result do_apply( const transfer_operation& o ); }; + class override_transfer_evaluator : public evaluator + { + public: + typedef override_transfer_operation operation_type; + + void_result do_evaluate( const override_transfer_operation& o ); + void_result do_apply( const override_transfer_operation& o ); + }; + } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/vesting_balance_evaluator.hpp b/libraries/chain/include/graphene/chain/vesting_balance_evaluator.hpp index 09c49607..7634a7d0 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_evaluator.hpp @@ -29,7 +29,7 @@ class vesting_balance_create_evaluator : public evaluator +#include +#include +#include #include #include -#include -#include +#include + + namespace graphene { namespace chain { using namespace graphene::db; @@ -38,20 +41,29 @@ namespace graphene { namespace chain { asset _amount) : balance(_balance), now(_now), amount(_amount) {} - asset balance; + asset balance; fc::time_point_sec now; - asset amount; + asset amount; }; /** - * Linear vesting balance. + * @brief Linear vesting balance with cliff + * + * This vesting balance type is used to mimic traditional stock vesting contracts where + * each day a certain amount vests until it is fully matured. + * + * @note New funds may not be added to a linear vesting balance. */ struct linear_vesting_policy { - uint32_t vesting_seconds; // must be greater than zero - fc::time_point_sec begin_date; - share_type begin_balance; // same asset as balance - share_type total_withdrawn; // same asset as balance + /// This is the time at which funds begin vesting. + fc::time_point_sec begin_timestamp; + /// No amount may be withdrawn before this many seconds of the vesting period have elapsed. + uint32_t vesting_cliff_seconds = 0; + /// Duration of the vesting period, in seconds. Must be greater than 0 and greater than vesting_cliff_seconds. + uint32_t vesting_duration_seconds = 0; + /// The total amount of asset to vest. + share_type begin_balance; asset get_allowed_withdraw(const vesting_policy_context& ctx)const; bool is_deposit_allowed(const vesting_policy_context& ctx)const; @@ -63,10 +75,19 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; + /** + * @brief defines vesting in terms of coin-days accrued which allows for dynamic deposit/withdraw + * + * The economic effect of this vesting policy is to require a certain amount of "interest" to accrue + * before the full balance may be withdrawn. Interest accrues as coindays (balance * length held). If + * some of the balance is withdrawn, the remaining balance must be held longer. + */ struct cdd_vesting_policy { - uint32_t vesting_seconds; + uint32_t vesting_seconds = 0; fc::uint128_t coin_seconds_earned; + /** while coindays may accrue over time, none may be claimed before first_claim date */ + fc::time_point_sec start_claim; fc::time_point_sec coin_seconds_earned_last_update; /** @@ -98,8 +119,7 @@ namespace graphene { namespace chain { > vesting_policy; /** - * Timelocked balance object is a balance that is locked by the - * blockchain for a period of time. + * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ class vesting_balance_object : public abstract_object { @@ -107,15 +127,17 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = vesting_balance_object_type; - account_id_type owner; - asset balance; - vesting_policy policy; + /// Account which owns and may withdraw from this vesting balance + account_id_type owner; + /// Total amount remaining in this vesting balance + /// Includes the unvested funds, and the vested funds which have not yet been withdrawn + asset balance; + /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn + vesting_policy policy; vesting_balance_object() {} - /** - * Used to increase existing vesting balances. - */ + ///@brief Deposit amount into vesting balance, requiring it to vest before withdrawal void deposit(const fc::time_point_sec& now, const asset& amount); bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const; @@ -124,32 +146,48 @@ namespace graphene { namespace chain { bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const; /** - * Used to remove a vesting balance from the VBO. As well - * as the balance field, coin_seconds_earned and + * Used to remove a vesting balance from the VBO. As well as the + * balance field, coin_seconds_earned and * coin_seconds_earned_last_update fields are updated. * - * The money doesn't "go" anywhere; the caller is responsible - * for crediting it to the proper account. + * The money doesn't "go" anywhere; the caller is responsible for + * crediting it to the proper account. */ void withdraw(const fc::time_point_sec& now, const asset& amount); bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const; }; + /** + * @ingroup object_index + */ + typedef multi_index_container< + vesting_balance_object, + indexed_by< + hashed_unique< tag, member< object, object_id_type, &object::id > > + > + > vesting_balance_multi_index_type; + /** + * @ingroup object_index + */ + typedef generic_index vesting_balance_index; } } // graphene::chain FC_REFLECT(graphene::chain::linear_vesting_policy, - (vesting_seconds) - (begin_date) + (begin_timestamp) + (vesting_cliff_seconds) + (vesting_duration_seconds) (begin_balance) - (total_withdrawn) ) FC_REFLECT(graphene::chain::cdd_vesting_policy, (vesting_seconds) + (start_claim) (coin_seconds_earned) (coin_seconds_earned_last_update) ) +FC_REFLECT_TYPENAME( graphene::chain::vesting_policy ) + FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), (owner) (balance) diff --git a/libraries/chain/include/graphene/chain/withdraw_permission_evaluator.hpp b/libraries/chain/include/graphene/chain/withdraw_permission_evaluator.hpp index 7edecf03..e814112b 100644 --- a/libraries/chain/include/graphene/chain/withdraw_permission_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/withdraw_permission_evaluator.hpp @@ -26,7 +26,7 @@ class withdraw_permission_create_evaluator : public evaluator -#include +#include #include namespace graphene { namespace chain { diff --git a/libraries/chain/include/graphene/chain/witness_evaluator.hpp b/libraries/chain/include/graphene/chain/witness_evaluator.hpp index 8476c096..2fabdfb7 100644 --- a/libraries/chain/include/graphene/chain/witness_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/witness_evaluator.hpp @@ -26,7 +26,7 @@ namespace graphene { namespace chain { public: typedef witness_create_operation operation_type; - object_id_type do_evaluate( const witness_create_operation& o ); + void_result do_evaluate( const witness_create_operation& o ); object_id_type do_apply( const witness_create_operation& o ); }; diff --git a/libraries/chain/include/graphene/chain/witness_object.hpp b/libraries/chain/include/graphene/chain/witness_object.hpp index 5d5ba882..875db07e 100644 --- a/libraries/chain/include/graphene/chain/witness_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_object.hpp @@ -16,8 +16,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once -#include +#include #include +#include namespace graphene { namespace chain { using namespace graphene::db; @@ -30,23 +31,37 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = witness_object_type; - account_id_type witness_account; - key_id_type signing_key; - secret_hash_type next_secret; - secret_hash_type last_secret; - share_type accumulated_income; - vote_id_type vote_id; + account_id_type witness_account; + public_key_type signing_key; + secret_hash_type next_secret_hash; + secret_hash_type previous_secret; + share_type accumulated_income; + vote_id_type vote_id; + string url; witness_object() : vote_id(vote_id_type::witness) {} }; + struct by_account; + using witness_multi_index_type = multi_index_container< + witness_object, + indexed_by< + hashed_unique< tag, + member + >, + hashed_unique< tag, + member + > + > + >; + using witness_index = generic_index; } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::witness_object, (graphene::db::object), (witness_account) (signing_key) - (next_secret) - (last_secret) + (next_secret_hash) + (previous_secret) (accumulated_income) - (vote_id) ) - + (vote_id) + (url) ) diff --git a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp index 2e0d43f5..cd11ebca 100644 --- a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp @@ -19,8 +19,9 @@ // needed to serialize witness_scheduler #include +#include -#include +#include #include #include @@ -56,7 +57,13 @@ class witness_schedule_object : public abstract_object witness_scheduler scheduler; uint32_t last_scheduling_block; uint64_t slots_since_genesis = 0; - fc::array< char, GRAPHENE_RNG_SEED_LENGTH > rng_seed; + fc::array< char, sizeof(secret_hash_type) > rng_seed; + + /** + * Not necessary for consensus, but used for figuring out the participation rate. + * The nth bit is 0 if the nth slot was unfilled, else it is 1. + */ + fc::uint128 recent_slots_filled; }; } } @@ -77,4 +84,5 @@ FC_REFLECT_DERIVED( graphene::chain::witness_schedule_object, (graphene::chain:: (last_scheduling_block) (slots_since_genesis) (rng_seed) + (recent_slots_filled) ) diff --git a/libraries/chain/include/graphene/chain/witness_scheduler.hpp b/libraries/chain/include/graphene/chain/witness_scheduler.hpp index e3f18c4d..ec595922 100644 --- a/libraries/chain/include/graphene/chain/witness_scheduler.hpp +++ b/libraries/chain/include/graphene/chain/witness_scheduler.hpp @@ -40,6 +40,7 @@ class generic_witness_scheduler public: void check_invariant() const { +#ifndef NDEBUG CountType tokens = _ineligible_no_turn.size() + _eligible.size(); CountType turns = _eligible.size(); for( const std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token ) @@ -47,7 +48,7 @@ class generic_witness_scheduler assert( _tokens == tokens ); assert( _turns == turns ); - +#endif flat_set< WitnessID > witness_set; // make sure each witness_id occurs only once among the three states @@ -312,6 +313,40 @@ class generic_witness_scheduler return true; } + /** + * Reset the schedule, then re-schedule the given witness as the + * first witness. + */ + void reset_schedule( WitnessID first_witness ) + { + _schedule.clear(); + for( const WitnessID& wid : _ineligible_no_turn ) + { + _eligible.push_back( wid ); + } + _turns += _ineligible_no_turn.size(); + _ineligible_no_turn.clear(); + for( const auto& item : _ineligible_waiting_for_token ) + { + _eligible.push_back( item.first ); + _turns += (item.second ? 0 : 1); + } + _tokens += _ineligible_waiting_for_token.size(); + _ineligible_waiting_for_token.clear(); + if( debug ) check_invariant(); + + auto it = std::find( _eligible.begin(), _eligible.end(), first_witness ); + assert( it != _eligible.end() ); + + _schedule.push_back( *it ); + _ineligible_waiting_for_token.emplace_back( *it, false ); + _eligible.erase( it ); + _turns--; + _tokens--; + if( debug ) check_invariant(); + return; + } + // keep track of total turns / tokens in existence CountType _turns = 0; CountType _tokens = 0; diff --git a/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp b/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp index 4c3e0509..e7467010 100644 --- a/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp +++ b/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp @@ -38,7 +38,7 @@ class nullary_rng * The sha256_ctr_rng generates bits using SHA256 in counter (CTR) * mode. */ -template< class HashClass, int SeedLength=32 > +template< class HashClass, int SeedLength=sizeof(secret_hash_type) > class hash_ctr_rng { public: diff --git a/libraries/chain/include/graphene/chain/worker_evaluator.hpp b/libraries/chain/include/graphene/chain/worker_evaluator.hpp index 34f83651..d22843b5 100644 --- a/libraries/chain/include/graphene/chain/worker_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/worker_evaluator.hpp @@ -17,17 +17,144 @@ */ #pragma once #include -#include namespace graphene { namespace chain { + /** + * @defgroup worker_types Implementations of the various worker types in the system + * + * The system has various worker types, which do different things with the money they are paid. These worker types + * and their semantics are specified here. + * + * All worker types exist as a struct containing the data this worker needs to evaluate, as well as a method + * pay_worker, which takes a pay amount and a non-const database reference, and applies the worker's specific pay + * semantics to the worker_type struct and/or the database. Furthermore, all worker types have an initializer, + * which is a struct containing the data needed to create that kind of worker. + * + * Each initializer type has a method, init, which takes a non-const database reference, a const reference to the + * worker object being created, and a non-const reference to the specific *_worker_type object to initialize. The + * init method creates any further objects, and initializes the worker_type object as necessary according to the + * semantics of that particular worker type. + * + * To create a new worker type, define a my_new_worker_type struct with a pay_worker method which updates the + * my_new_worker_type object and/or the database. Create a my_new_worker_type::initializer struct with an init + * method and any data members necessary to create a new worker of this type. Reflect my_new_worker_type and + * my_new_worker_type::initializer into FC's type system, and add them to @ref worker_type and @ref + * worker_initializer respectively. Make sure the order of types in @ref worker_type and @ref worker_initializer + * remains the same. + * @{ + */ + /** + * @brief A worker who returns all of his pay to the reserve + * + * This worker type pays everything he receives back to the network's reserve funds pool. + */ + struct refund_worker_type + { + /// Record of how much this worker has burned in his lifetime + share_type total_burned; + + void pay_worker(share_type pay, database&); + }; + + /** + * @brief A worker who sends his pay to a vesting balance + * + * This worker type takes all of his pay and places it into a vesting balance + */ + struct vesting_balance_worker_type + { + /// The balance this worker pays into + vesting_balance_id_type balance; + + void pay_worker(share_type pay, database& db); + }; + + /** + * @brief A worker who permanently destroys all of his pay + * + * This worker sends all pay he receives to the null account. + */ + struct burn_worker_type + { + /// Record of how much this worker has burned in his lifetime + share_type total_burned; + + void pay_worker(share_type pay, database&); + }; + ///@} + + // The ordering of types in these two static variants MUST be the same. + typedef static_variant< + refund_worker_type, + vesting_balance_worker_type, + burn_worker_type + > worker_type; + + + /** + * @brief Worker object contains the details of a blockchain worker. See @ref workers for details. + */ + class worker_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = worker_object_type; + + /// ID of the account which owns this worker + account_id_type worker_account; + /// Time at which this worker begins receiving pay, if elected + time_point_sec work_begin_date; + /// Time at which this worker will cease to receive pay. Worker will be deleted at this time + time_point_sec work_end_date; + /// Amount in CORE this worker will be paid each day + share_type daily_pay; + /// ID of this worker's pay balance + worker_type worker; + /// Human-readable name for the worker + string name; + /// URL to a web page representing this worker + string url; + + /// Voting ID which represents approval of this worker + vote_id_type vote_for; + /// Voting ID which represents disapproval of this worker + vote_id_type vote_against; + + bool is_active(fc::time_point_sec now)const { + return now >= work_begin_date && now <= work_end_date; + } + share_type approving_stake(const vector& stake_vote_tallies)const { + return stake_vote_tallies[vote_for] - stake_vote_tallies[vote_against]; + } + }; + + typedef flat_index worker_index; + + class worker_create_evaluator : public evaluator { public: typedef worker_create_operation operation_type; - object_id_type do_evaluate( const operation_type& o ); + void_result do_evaluate( const operation_type& o ); object_id_type do_apply( const operation_type& o ); }; } } // graphene::chain + +FC_REFLECT( graphene::chain::refund_worker_type, (total_burned) ) +FC_REFLECT( graphene::chain::vesting_balance_worker_type, (balance) ) +FC_REFLECT( graphene::chain::burn_worker_type, (total_burned) ) +FC_REFLECT_TYPENAME( graphene::chain::worker_type ) +FC_REFLECT_DERIVED( graphene::chain::worker_object, (graphene::db::object), + (worker_account) + (work_begin_date) + (work_end_date) + (daily_pay) + (worker) + (vote_for) + (vote_against) + (name) + (url) + ) diff --git a/libraries/chain/include/graphene/chain/worker_object.hpp b/libraries/chain/include/graphene/chain/worker_object.hpp deleted file mode 100644 index 5306d41d..00000000 --- a/libraries/chain/include/graphene/chain/worker_object.hpp +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once -#include -#include - -#include - -#include - -namespace graphene { namespace chain { - using namespace graphene::db; - class database; - - /** - * @defgroup worker_types Implementations of the various worker types in the system - * - * The system has various worker types, which do different things with the money they are paid. These worker types - * and their semantics are specified here. - * - * All worker types exist as a struct containing the data this worker needs to evaluate, as well as a method - * pay_worker, which takes a pay amount and a non-const database reference, and applies the worker's specific pay - * semantics to the worker_type struct and/or the database. Furthermore, all worker types have an initializer, - * which is a struct containing the data needed to create that kind of worker. - * - * Each initializer type has a method, init, which takes a non-const database reference, a const reference to the - * worker object being created, and a non-const reference to the specific *_worker_type object to initialize. The - * init method creates any further objects, and initializes the worker_type object as necessary according to the - * semantics of that particular worker type. - * - * To create a new worker type, define a my_new_worker_type struct with a pay_worker method which updates the - * my_new_worker_type object and/or the database. Create a my_new_worker_type::initializer struct with an init - * method and any data members necessary to create a new worker of this type. Reflect my_new_worker_type and - * my_new_worker_type::initializer into FC's type system, and add them to @ref worker_type and @ref - * worker_initializer respectively. Make sure the order of types in @ref worker_type and @ref worker_initializer - * remains the same. - * @{ - */ - /** - * @brief A worker who burns all of his pay - * - * This worker type burns all pay he receives, paying it back to the network's reserve funds pool. - */ - struct refund_worker_type - { - /// Record of how much this worker has burned in his lifetime - share_type total_burned; - - void pay_worker(share_type pay, database&); - - struct initializer - { - void init(database&, const worker_object&, refund_worker_type&)const - {} - }; - }; - - /** - * @brief A worker who sends his pay to a vesting balance - * - * This worker type takes all of his pay and places it into a vesting balance - */ - struct vesting_balance_worker_type - { - /// The balance this worker pays into - vesting_balance_id_type balance; - - void pay_worker(share_type pay, database& db); - - struct initializer - { - initializer(uint16_t vesting_period = 0) - : pay_vesting_period_days(vesting_period) {} - - void init(database& db, const worker_object& obj, vesting_balance_worker_type& worker)const; - - uint16_t pay_vesting_period_days; - }; - }; - ///@} - - // The ordering of types in these two static variants MUST be the same. - typedef static_variant< - refund_worker_type, - vesting_balance_worker_type - > worker_type; - typedef static_variant< - refund_worker_type::initializer, - vesting_balance_worker_type::initializer - > worker_initializer; - - /// @brief A visitor for @ref worker_type which initializes the worker within - struct worker_initialize_visitor - { - private: - const worker_object& worker_obj; - const worker_initializer& initializer; - database& db; - - public: - worker_initialize_visitor(const worker_object& worker, const worker_initializer& initializer, database& db) - : worker_obj(worker),initializer(initializer),db(db) {} - - typedef void result_type; - template - void operator()( WorkerType& worker)const - { - static_assert(worker_type::tag::value == - worker_initializer::tag::value, - "Tag values for worker_type and worker_initializer do not match! " - "Are the types in these static_variants in the same order?"); - initializer.get().init(db, worker_obj, worker); - } - }; - - /// @brief A visitor for @ref worker_type which calls pay_worker on the worker within - struct worker_pay_visitor - { - private: - share_type pay; - database& db; - - public: - worker_pay_visitor(share_type pay, database& db) - : pay(pay), db(db) {} - - typedef void result_type; - template - void operator()(W& worker)const - { - worker.pay_worker(pay, db); - } - }; - - /** - * @brief Worker object contains the details of a blockchain worker. See @ref workers for details. - */ - class worker_object : public abstract_object - { - public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = worker_object_type; - - /// ID of the account which owns this worker - account_id_type worker_account; - /// Time at which this worker begins receiving pay, if elected - time_point_sec work_begin_date; - /// Time at which this worker will cease to receive pay. Worker will be deleted at this time - time_point_sec work_end_date; - /// Amount in CORE this worker will be paid each day - share_type daily_pay; - /// ID of this worker's pay balance - worker_type worker; - - /// Voting ID which represents approval of this worker - vote_id_type vote_for; - /// Voting ID which represents disapproval of this worker - vote_id_type vote_against; - - bool is_active(fc::time_point_sec now)const { - return now >= work_begin_date && now <= work_end_date; - } - share_type approving_stake(const vector& stake_vote_tallies)const { - return stake_vote_tallies[vote_for] - stake_vote_tallies[vote_against]; - } - }; - - typedef flat_index worker_index; - -} } // graphene::chain - -FC_REFLECT( graphene::chain::refund_worker_type, (total_burned) ) -FC_REFLECT( graphene::chain::refund_worker_type::initializer, ) -FC_REFLECT( graphene::chain::vesting_balance_worker_type, (balance) ) -FC_REFLECT( graphene::chain::vesting_balance_worker_type::initializer, (pay_vesting_period_days) ) -FC_REFLECT_DERIVED( graphene::chain::worker_object, (graphene::db::object), - (worker_account) - (work_begin_date) - (work_end_date) - (daily_pay) - (worker) - (vote_for) - (vote_against) - ) diff --git a/libraries/chain/limit_order_evaluator.cpp b/libraries/chain/limit_order_evaluator.cpp deleted file mode 100644 index d3429567..00000000 --- a/libraries/chain/limit_order_evaluator.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include -#include -#include - -namespace graphene { namespace chain { -object_id_type limit_order_create_evaluator::do_evaluate( const limit_order_create_operation& op ) -{ - database& d = db(); - - FC_ASSERT( op.expiration >= d.head_block_time() ); - - _seller = this->fee_paying_account; - _sell_asset = &op.amount_to_sell.asset_id(d); - _receive_asset = &op.min_to_receive.asset_id(d); - - if( _sell_asset->options.whitelist_markets.size() ) - FC_ASSERT( _sell_asset->options.whitelist_markets.find( _receive_asset->id ) != _sell_asset->options.whitelist_markets.end() ); - if( _sell_asset->options.blacklist_markets.size() ) - FC_ASSERT( _sell_asset->options.blacklist_markets.find( _receive_asset->id ) == _sell_asset->options.blacklist_markets.end() ); - - if( _sell_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset ) ); - if( _receive_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset ) ); - - FC_ASSERT( d.get_balance( _seller, _sell_asset ) >= op.amount_to_sell, "insufficient balance", - ("balance",d.get_balance(_seller,_sell_asset))("amount_to_sell",op.amount_to_sell) ); - - return object_id_type(); -} -template -std::reverse_iterator reverse( const I& itr ) { return std::reverse_iterator(itr); } - -object_id_type limit_order_create_evaluator::do_apply( const limit_order_create_operation& op ) -{ - const auto& seller_stats = _seller->statistics(db()); - db().modify( seller_stats, [&]( account_statistics_object& bal ){ - if( op.amount_to_sell.asset_id == asset_id_type() ) - { - bal.total_core_in_orders += op.amount_to_sell.amount; - } - }); - - db().adjust_balance(op.seller, -op.amount_to_sell); - - const auto& new_order_object = db().create( [&]( limit_order_object& obj ){ - obj.seller = _seller->id; - obj.for_sale = op.amount_to_sell.amount; - obj.sell_price = op.get_price(); - obj.expiration = op.expiration; - }); - limit_order_id_type result = new_order_object.id; // save this because we may remove the object by filling it - - // Possible optimization: We only need to check calls if both are true: - // - The new order is at the front of the book - // - The new order is below the call limit price - bool called_some = db().check_call_orders(*_sell_asset); - called_some |= db().check_call_orders(*_receive_asset); - if( called_some && !db().find(result) ) // then we were filled by call order - return result; - - const auto& limit_order_idx = db().get_index_type(); - const auto& limit_price_idx = limit_order_idx.indices().get(); - - // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to - // determine whether or not this order has "changed the book" in a way that requires us to - // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs - // constant time check. Potential optimization. - - auto max_price = ~op.get_price(); //op.min_to_receive / op.amount_to_sell; - auto limit_itr = limit_price_idx.lower_bound( max_price.max() ); - auto limit_end = limit_price_idx.upper_bound( max_price ); - - for( auto tmp = limit_itr; tmp != limit_end; ++tmp ) - { - assert( tmp != limit_price_idx.end() ); - } - - bool filled = false; - //if( new_order_object.amount_to_receive().asset_id(db()).is_market_issued() ) - if( _receive_asset->is_market_issued() ) - { // then we may also match against shorts - if( _receive_asset->bitasset_data(db()).options.short_backing_asset == asset_id_type() ) - { - bool converted_some = db().convert_fees( *_receive_asset ); - // just incase the new order was completely filled from fees - if( converted_some && !db().find(result) ) // then we were filled by call order - return result; - } - const auto& short_order_idx = db().get_index_type(); - const auto& sell_price_idx = short_order_idx.indices().get(); - - FC_ASSERT( max_price.max() >= max_price ); - auto short_itr = sell_price_idx.lower_bound( max_price.max() ); - auto short_end = sell_price_idx.upper_bound( max_price ); - - while( !filled ) - { - if( limit_itr != limit_end ) - { - if( short_itr != short_end && limit_itr->sell_price < short_itr->sell_price ) - { - auto old_short_itr = short_itr; - ++short_itr; - filled = (db().match( new_order_object, *old_short_itr, old_short_itr->sell_price ) != 2 ); - } - else - { - auto old_limit_itr = limit_itr; - ++limit_itr; - filled = (db().match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); - } - } - else if( short_itr != short_end ) - { - auto old_short_itr = short_itr; - ++short_itr; - filled = (db().match( new_order_object, *old_short_itr, old_short_itr->sell_price ) != 2 ); - } - else break; - } - } - else while( !filled && limit_itr != limit_end ) - { - auto old_itr = limit_itr; - ++limit_itr; - filled = (db().match( new_order_object, *old_itr, old_itr->sell_price ) != 2); - } - - //Possible optimization: only check calls if the new order completely filled some old order - //Do I need to check both assets? - db().check_call_orders(*_sell_asset); - db().check_call_orders(*_receive_asset); - - FC_ASSERT( !op.fill_or_kill || db().find_object(result) == nullptr ); - - return result; -} // limit_order_evaluator::do_apply - -asset limit_order_cancel_evaluator::do_evaluate( const limit_order_cancel_operation& o ) -{ - database& d = db(); - - _order = &o.order(d); - FC_ASSERT( _order->seller == o.fee_paying_account ); - auto refunded = _order->amount_for_sale(); - //adjust_balance( fee_paying_account, &refunded.asset_id(d), refunded.amount ); - - return refunded; -} - -asset limit_order_cancel_evaluator::do_apply( const limit_order_cancel_operation& o ) -{ - database& d = db(); - - auto base_asset = _order->sell_price.base.asset_id; - auto quote_asset = _order->sell_price.quote.asset_id; - auto refunded = _order->amount_for_sale(); - - db().cancel_order( *_order, false /* don't create a virtual op*/ ); - - // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. - // Do I need to check calls in both assets? - db().check_call_orders(base_asset(d)); - db().check_call_orders(quote_asset(d)); - - return refunded; -} -} } // graphene::chain diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp new file mode 100644 index 00000000..2867e866 --- /dev/null +++ b/libraries/chain/market_evaluator.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include + +namespace graphene { namespace chain { +void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op) +{ try { + database& d = db(); + + FC_ASSERT( op.expiration >= d.head_block_time() ); + + _seller = this->fee_paying_account; + _sell_asset = &op.amount_to_sell.asset_id(d); + _receive_asset = &op.min_to_receive.asset_id(d); + + if( _sell_asset->options.whitelist_markets.size() ) + FC_ASSERT( _sell_asset->options.whitelist_markets.find(_receive_asset->id) != _sell_asset->options.whitelist_markets.end() ); + if( _sell_asset->options.blacklist_markets.size() ) + FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() ); + + if( _sell_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset ) ); + if( _receive_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset ) ); + + FC_ASSERT( d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell, "insufficient balance", + ("balance",d.get_balance(*_seller,*_sell_asset))("amount_to_sell",op.amount_to_sell) ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) +{ try { + const auto& seller_stats = _seller->statistics(db()); + db().modify(seller_stats, [&](account_statistics_object& bal) { + if( op.amount_to_sell.asset_id == asset_id_type() ) + { + bal.total_core_in_orders += op.amount_to_sell.amount; + } + }); + + db().adjust_balance(op.seller, -op.amount_to_sell); + + const auto& new_order_object = db().create([&](limit_order_object& obj){ + obj.seller = _seller->id; + obj.for_sale = op.amount_to_sell.amount; + obj.sell_price = op.get_price(); + obj.expiration = op.expiration; + }); + limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it + bool filled = db().apply_order(new_order_object); + + FC_ASSERT( !op.fill_or_kill || filled ); + + return order_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o) +{ try { + database& d = db(); + + _order = &o.order(d); + FC_ASSERT( _order->seller == o.fee_paying_account ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) +{ try { + database& d = db(); + + auto base_asset = _order->sell_price.base.asset_id; + auto quote_asset = _order->sell_price.quote.asset_id; + auto refunded = _order->amount_for_sale(); + + db().cancel_order(*_order, false /* don't create a virtual op*/); + + // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. + // Do I need to check calls in both assets? + db().check_call_orders(base_asset(d)); + db().check_call_orders(quote_asset(d)); + + return refunded; +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o) +{ try { + database& d = db(); + + _paying_account = &o.funding_account(d); + _debt_asset = &o.delta_debt.asset_id(d); + FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", + ("sym", _debt_asset->symbol) ); + + _bitasset_data = &_debt_asset->bitasset_data(d); + + /// if there is a settlement for this asset, then no further margin positions may be taken and + /// all existing margin positions should have been closed va database::globally_settle_asset + FC_ASSERT( !_bitasset_data->has_settlement() ); + + FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset ); + + if( _bitasset_data->is_prediction_market ) + FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount ); + else if( _bitasset_data->current_feed.settlement_price.is_null() ) + FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed."); + + if( o.delta_debt.amount < 0 ) + { + FC_ASSERT( d.get_balance(*_paying_account, *_debt_asset) >= o.delta_debt, + "Cannot cover by ${c} when payer only has ${b}", + ("c", o.delta_debt.amount)("b", d.get_balance(*_paying_account, *_debt_asset).amount) ); + } + + if( o.delta_collateral.amount > 0 ) + { + FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.delta_collateral, + "Cannot increase collateral by ${c} when payer only has ${b}", ("c", o.delta_collateral.amount) + ("b", d.get_balance(*_paying_account, o.delta_collateral.asset_id(d)).amount) ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + +void_result call_order_update_evaluator::do_apply(const call_order_update_operation& o) +{ try { + database& d = db(); + + if( o.delta_debt.amount != 0 ) + { + d.adjust_balance( o.funding_account, o.delta_debt ); + + // Deduct the debt paid from the total supply of the debt asset. + d.modify(_debt_asset->dynamic_asset_data_id(d), [&](asset_dynamic_data_object& dynamic_asset) { + dynamic_asset.current_supply += o.delta_debt.amount; + assert(dynamic_asset.current_supply >= 0); + }); + } + + if( o.delta_collateral.amount != 0 ) + { + d.adjust_balance( o.funding_account, -o.delta_collateral ); + + // Adjust the total core in orders accodingly + if( o.delta_collateral.asset_id == asset_id_type() ) + { + d.modify(_paying_account->statistics(d), [&](account_statistics_object& stats) { + stats.total_core_in_orders += o.delta_collateral.amount; + }); + } + } + + + auto& call_idx = d.get_index_type().indices().get(); + auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); + const call_order_object* call_obj = nullptr; + + if( itr == call_idx.end() ) + { + FC_ASSERT( o.delta_collateral.amount > 0 ); + FC_ASSERT( o.delta_debt.amount > 0 ); + + call_obj = &d.create( [&](call_order_object& call ){ + call.borrower = o.funding_account; + call.collateral = o.delta_collateral.amount; + call.debt = o.delta_debt.amount; + call.call_price = price::call_price(o.delta_debt, o.delta_collateral, + _bitasset_data->current_feed.maintenance_collateral_ratio); + }); + } + else + { + call_obj = &*itr; + + d.modify( *call_obj, [&]( call_order_object& call ){ + call.collateral += o.delta_collateral.amount; + call.debt += o.delta_debt.amount; + if( call.debt > 0 ) + { + call.call_price = price::call_price(call.get_debt(), call.get_collateral(), + _bitasset_data->current_feed.maintenance_collateral_ratio); + } + }); + } + + auto debt = call_obj->get_debt(); + if( debt.amount == 0 ) + { + FC_ASSERT( call_obj->collateral == 0 ); + d.remove( *call_obj ); + return void_result(); + } + + FC_ASSERT(call_obj->collateral > 0 && call_obj->debt > 0); + + // then we must check for margin calls and other issues + if( !_bitasset_data->is_prediction_market ) + { + // Check that the order's debt per collateral is less than the system's minimum debt per collateral. + FC_ASSERT( ~call_obj->call_price <= _bitasset_data->current_feed.settlement_price, + "Insufficient collateral for debt.", + ("a", ~call_obj->call_price)("b", _bitasset_data->current_feed.settlement_price)); + + auto call_order_id = call_obj->id; + + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be + // limit orders available that could be used to fill the order. + if( d.check_call_orders( *_debt_asset, false ) ) + { + FC_ASSERT( !d.find_object( call_order_id ), "If updating the call order triggers a margin call, then it must completely cover the order" ); + } + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +} } // graphene::chain diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp deleted file mode 100644 index c08027e2..00000000 --- a/libraries/chain/operations.cpp +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include - -namespace graphene { namespace chain { - -/** - * Valid symbols have between 3 and 17 upper case characters - * with at most a single "." that is not the first or last character. - */ -bool is_valid_symbol( const string& symbol ) -{ - if( symbol.size() > 17 ) return false; - if( symbol.size() < 3 ) return false; - int dot_count = 0; - for( auto c : symbol ) - { - if( c == '.' ) ++dot_count; - else if( c < 'A' || c > 'Z' ) return false; - } - if( symbol[0] == '.' || symbol[symbol.size()-1] == '.' ) - return false; - return dot_count <= 1; -} - -/** - * Valid names are all lower case, start with [a-z] and may - * have "." or "-" in the name along with a single '/'. The - * next character after a "/", "." or "-" cannot be [0-9] or - * another '.', '-'. - * - */ -bool is_valid_name( const string& s ) -{ - if( s.size() < 2 ) return false; - if( s.size() >= 64 ) return false; - - int num_slash = 0; - char prev = ' '; - for( auto c : s ) - { - if( c >= 'a' && c <= 'z' ){} - else if( c >= '0' && c <= '9' ) - { - if( prev == ' ' || prev == '.' || prev == '/' ) return false; - } - else switch( c ) - { - case '/': - if( ++num_slash > 1 ) return false; - case '.': - case '-': - if( prev == ' ' || prev == '/' || prev == '.' || prev == '-' ) return false; - break; - default: - return false; - } - prev = c; - } - switch( s.back() ) - { - case '/': case '-': case '.': - return false; - default: - return true; - } -} - -bool is_cheap_name( const string& n ) -{ - bool v = false; - for( auto c : n ) - { - if( c >= '0' && c <= '9' ) return true; - if( c == '.' || c == '-' || c == '/' ) return true; - switch( c ) - { - case 'a': - case 'e': - case 'i': - case 'o': - case 'u': - case 'y': - v = true; - } - } - if( !v ) - return true; - return false; -} - -share_type account_create_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - auto core_fee_required = schedule.account_create_fee; - - uint32_t s = name.size(); - if( is_cheap_name( name ) ) s = 63; - - FC_ASSERT( s >= 2 ); - - if( s == 8 ) - core_fee_required = schedule.account_len8_fee; - else if( s == 7 ) - core_fee_required = schedule.account_len7_fee; - else if( s == 6 ) - core_fee_required = schedule.account_len6_fee; - else if( s == 5 ) - core_fee_required = schedule.account_len5_fee; - else if( s == 4 ) - core_fee_required = schedule.account_len4_fee; - else if( s == 3 ) - core_fee_required = schedule.account_len3_fee; - else if( s == 2 ) - core_fee_required = schedule.account_len2_fee; - - return core_fee_required; -} -share_type account_update_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - return schedule.account_create_fee; -} -void account_update_operation::get_required_auth(flat_set& active_auth_set, - flat_set& owner_auth_set) const -{ - if( owner || active ) - owner_auth_set.insert( account ); - else - active_auth_set.insert( account ); -} - -void account_update_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( account != account_id_type() ); - FC_ASSERT( owner || active || voting_account || memo_key || vote ); -} - - -share_type asset_create_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - auto core_fee_required = schedule.asset_create_fee; - - uint32_t s = symbol.size(); - while( s <= 6 ) { core_fee_required *= 30; ++s; } - - return core_fee_required; -} - -share_type transfer_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - share_type core_fee_required = schedule.transfer_fee; - if( memo ) - { - core_fee_required += share_type((memo->message.size() * schedule.data_fee)/1024); - } - return core_fee_required; -} - -struct key_data_validate -{ - typedef void result_type; - void operator()( const address& a )const { FC_ASSERT( a != address() ); } - void operator()( const public_key_type& a )const { FC_ASSERT( a != public_key_type() ); } -}; -void key_create_operation::get_required_auth(flat_set& active_auth_set, - flat_set&) const -{ - active_auth_set.insert(fee_paying_account); -} - -void key_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - key_data.visit( key_data_validate() ); -} - -void account_create_operation::get_required_auth(flat_set& active_auth_set, - flat_set&) const -{ - active_auth_set.insert(registrar); -} - -void account_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( is_valid_name( name ) ); - FC_ASSERT( referrer_percent >= 0 ); - FC_ASSERT( referrer_percent <= 100 ); - FC_ASSERT( !owner.auths.empty() ); - auto pos = name.find( '/' ); - if( pos != string::npos ) - { - FC_ASSERT( owner.weight_threshold == 1 ); - FC_ASSERT( owner.auths.size() == 1 ); - } - FC_ASSERT( num_witness + num_committee >= num_witness ); // no overflow - FC_ASSERT( num_witness + num_committee <= vote.size() ); - // FC_ASSERT( (num_witness == 0) || (num_witness&0x01) == 0, "must be odd number" ); - // FC_ASSERT( (num_committee == 0) || (num_committee&0x01) == 0, "must be odd number" ); -} - - -share_type asset_publish_feed_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - return schedule.publish_feed_fee; -} - -void asset_publish_feed_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - feed.validate(); -} - -void transfer_operation::get_required_auth(flat_set& active_auth_set, - flat_set&) const -{ - active_auth_set.insert( from ); -} - -void transfer_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( from != to ); - FC_ASSERT( amount.amount > 0 ); -} - -void asset_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(issuer); -} - -void asset_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( is_valid_symbol( symbol ) ); - common_options.validate(); - if( common_options.issuer_permissions & (disable_force_settle|global_settle) ) - FC_ASSERT( bitasset_options.valid() ); - if( is_prediction_market ) - { - FC_ASSERT( bitasset_options.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); - FC_ASSERT( common_options.issuer_permissions & global_settle ); - } - if( bitasset_options ) bitasset_options->validate(); - - asset dummy = asset(1) * common_options.core_exchange_rate; - FC_ASSERT(dummy.asset_id == asset_id_type(1)); - FC_ASSERT(precision <= 12); -} - -asset_update_operation::asset_update_operation(const asset_object& old) -{ - issuer = old.issuer; - asset_to_update = old.get_id(); - new_options = old.options; -} - -void asset_update_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(issuer); -} - -void asset_update_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - if( new_issuer ) - FC_ASSERT(issuer != *new_issuer); - new_options.validate(); - - asset dummy = asset(1, asset_to_update) * new_options.core_exchange_rate; - FC_ASSERT(dummy.asset_id == asset_id_type()); -} - -share_type asset_update_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.asset_update_fee; -} - -void asset_burn_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(payer); -} - -void asset_burn_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount_to_burn.amount.value <= GRAPHENE_BLOCKCHAIN_MAX_SHARES ); - FC_ASSERT( amount_to_burn.amount.value > 0 ); -} - -share_type asset_burn_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.asset_issue_fee; -} - -void asset_issue_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(issuer); -} - -void asset_issue_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( asset_to_issue.amount.value <= GRAPHENE_BLOCKCHAIN_MAX_SHARES ); - FC_ASSERT( asset_to_issue.amount.value > 0 ); - FC_ASSERT( asset_to_issue.asset_id != 0 ); -} - -share_type asset_issue_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.asset_issue_fee; -} - -share_type delegate_create_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.delegate_create_fee ; -} - -void delegate_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(delegate_account); -} - -void delegate_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -void asset_fund_fee_pool_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(from_account); -} - -void asset_fund_fee_pool_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( fee.asset_id == asset_id_type() ); - FC_ASSERT( amount > 0 ); -} - -share_type asset_fund_fee_pool_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.asset_fund_fee_pool_fee; -} - -void limit_order_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(seller); -} - -void limit_order_create_operation::validate()const -{ - FC_ASSERT( amount_to_sell.asset_id != min_to_receive.asset_id ); - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount_to_sell.amount > 0 ); - FC_ASSERT( min_to_receive.amount > 0 ); -} - -share_type limit_order_create_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.limit_order_fee; -} - -void limit_order_cancel_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(fee_paying_account); -} - -void limit_order_cancel_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -share_type limit_order_cancel_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.limit_order_fee; -} - -void short_order_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(seller); -} - -void short_order_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO ); - FC_ASSERT( initial_collateral_ratio > maintenance_collateral_ratio ); - FC_ASSERT( initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); -} - -share_type short_order_create_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.short_order_fee; -} -void short_order_cancel_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(fee_paying_account); -} - -void short_order_cancel_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -share_type short_order_cancel_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.short_order_fee; -} - -void call_order_update_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(funding_account); -} - -void call_order_update_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( collateral_to_add.amount > 0 || amount_to_cover.amount > 0 || maintenance_collateral_ratio > 0 ); - if( amount_to_cover.amount == 0 ) FC_ASSERT( collateral_to_add.amount >= 0 ); - if( collateral_to_add.amount.value <= 0 ) FC_ASSERT( amount_to_cover.amount.value > 0 ); - - FC_ASSERT( amount_to_cover.amount >= 0 ); - FC_ASSERT( amount_to_cover.asset_id != collateral_to_add.asset_id ); - FC_ASSERT( maintenance_collateral_ratio == 0 || maintenance_collateral_ratio >= 1000 ); -} - -share_type call_order_update_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.short_order_fee; -} - -proposal_create_operation proposal_create_operation::genesis_proposal(const database& db) -{ - auto global_params = db.get_global_properties().parameters; - proposal_create_operation op = {account_id_type(), asset(), {}, - db.head_block_time() + global_params.maximum_proposal_lifetime, - global_params.genesis_proposal_review_period}; - op.fee = op.calculate_fee(global_params.current_fees); - return op; -} - -void proposal_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(fee_paying_account); -} - -void proposal_create_operation::validate() const -{ - FC_ASSERT( !proposed_ops.empty() ); - for( const auto& op : proposed_ops ) op.validate(); -} - -void proposal_update_operation::get_required_auth(flat_set& active_auth_set, - flat_set& owner_auth_set) const -{ - active_auth_set.insert(fee_paying_account); - for( auto id : active_approvals_to_add ) - active_auth_set.insert(id); - for( auto id : active_approvals_to_remove ) - active_auth_set.insert(id); - for( auto id : owner_approvals_to_add ) - owner_auth_set.insert(id); - for( auto id : owner_approvals_to_remove ) - owner_auth_set.insert(id); -} - -void proposal_update_operation::validate() const -{ - FC_ASSERT(fee.amount >= 0); - FC_ASSERT(!(active_approvals_to_add.empty() && active_approvals_to_remove.empty() && - owner_approvals_to_add.empty() && owner_approvals_to_remove.empty() && - key_approvals_to_add.empty() && key_approvals_to_remove.empty())); - for( auto a : active_approvals_to_add ) - { - FC_ASSERT(active_approvals_to_remove.find(a) == active_approvals_to_remove.end(), - "Cannot add and remove approval at the same time."); - } - for( auto a : owner_approvals_to_add ) - { - FC_ASSERT(owner_approvals_to_remove.find(a) == owner_approvals_to_remove.end(), - "Cannot add and remove approval at the same time."); - } - for( auto a : key_approvals_to_add ) - { - FC_ASSERT(key_approvals_to_remove.find(a) == key_approvals_to_remove.end(), - "Cannot add and remove approval at the same time."); - } -} - -void proposal_delete_operation::get_required_auth(flat_set& active_auth_set, - flat_set& owner_auth_set) const -{ - if( using_owner_authority ) - owner_auth_set.insert(fee_paying_account); - else - active_auth_set.insert(fee_paying_account); -} - -void account_transfer_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -void account_transfer_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert( account_id ); -} - -share_type account_transfer_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.transfer_fee; -} - - -void proposal_delete_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -void witness_withdraw_pay_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(to_account); -} - -void witness_withdraw_pay_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount >= 0 ); -} - -share_type witness_withdraw_pay_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.witness_withdraw_pay_fee; -} - -void account_whitelist_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(authorizing_account); -} - -void global_parameters_update_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - new_parameters.validate(); -} - -share_type global_parameters_update_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.global_parameters_update_fee; -} - -void witness_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(witness_account); -} - -void witness_create_operation::validate() const -{ - FC_ASSERT(fee.amount >= 0); -} - -share_type witness_create_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.delegate_create_fee; -} - -void withdraw_permission_update_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert( withdraw_from_account ); -} - -void withdraw_permission_update_operation::validate()const -{ - FC_ASSERT( withdrawal_limit.amount > 0 ); - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( withdrawal_period_sec > 0 ); - FC_ASSERT( withdraw_from_account != authorized_account ); - FC_ASSERT( periods_until_expiration > 0 ); -} - -share_type withdraw_permission_update_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - return schedule.withdraw_permission_update_fee; -} - -void withdraw_permission_claim_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert( withdraw_to_account ); -} - -void withdraw_permission_claim_operation::validate()const -{ - FC_ASSERT( withdraw_to_account != withdraw_from_account ); - FC_ASSERT( amount_to_withdraw.amount > 0 ); - FC_ASSERT( fee.amount >= 0 ); -} - -share_type withdraw_permission_claim_operation::calculate_fee( const fee_schedule_type& schedule )const -{ - share_type core_fee_required = schedule.transfer_fee; - if( memo ) - core_fee_required += share_type((memo->message.size() * schedule.data_fee)/1024); - return core_fee_required; -} - -void withdraw_permission_delete_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(withdraw_from_account); -} - -void withdraw_permission_delete_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( withdraw_from_account != authorized_account ); -} - -share_type withdraw_permission_delete_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.withdraw_permission_update_fee; -} - -void withdraw_permission_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(withdraw_from_account); -} - -void withdraw_permission_create_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( withdraw_from_account != authorized_account ); - FC_ASSERT( withdrawal_limit.amount > 0 ); - //TODO: better bounds checking on these values - FC_ASSERT( withdrawal_period_sec > 0 ); - FC_ASSERT( periods_until_expiration > 0 ); -} - -share_type withdraw_permission_create_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.withdraw_permission_update_fee; -} - - -void asset_global_settle_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert( fee_payer() ); - -} - -void asset_global_settle_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( asset_to_settle == settle_price.base.asset_id ); -} - -share_type asset_global_settle_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.global_settle_fee; -} - -void asset_settle_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert( account ); -} - -void asset_settle_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount.amount >= 0 ); -} - -share_type asset_settle_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.asset_settle_fee; -} - - -void graphene::chain::asset_publish_feed_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(publisher); -} - -void asset_update_bitasset_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(issuer); -} - -void asset_update_bitasset_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); - new_options.validate(); -} - -share_type asset_update_bitasset_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.asset_update_fee; -} - -void asset_update_feed_producers_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -void vesting_balance_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - // owner's authorization isn't needed since this is effectively a transfer of value TO the owner - active_auth_set.insert( creator ); -} - -share_type vesting_balance_create_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.vesting_balance_create_fee; -} - -void vesting_balance_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount.amount > 0 ); - FC_ASSERT( vesting_seconds > 0 ); -} - -void vesting_balance_withdraw_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert( owner ); -} - -void vesting_balance_withdraw_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0 ); - FC_ASSERT( amount.amount > 0 ); -} - -share_type vesting_balance_withdraw_operation::calculate_fee( const fee_schedule_type& k )const -{ - return k.vesting_balance_withdraw_fee; -} - -void memo_data::set_message( const fc::ecc::private_key& priv, - const fc::ecc::public_key& pub, const string& msg ) -{ - if( from ) - { - uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0]; - entropy <<= 32; - entropy &= 0xff00000000000000; - nonce = (fc::time_point::now().time_since_epoch().count() & 0x00ffffffffffffff) | entropy; - auto secret = priv.get_shared_secret(pub); - auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str()); - string text = memo_message(digest_type::hash(msg)._hash[0], msg).serialize(); - message = fc::aes_encrypt( nonce_plus_secret, vector(text.begin(), text.end()) ); - } - else - { - auto text = memo_message( 0, msg ).serialize(); - message = vector(text.begin(), text.end()); - } -} - -string memo_data::get_message( const fc::ecc::private_key& priv, - const fc::ecc::public_key& pub )const -{ - if( from ) - { - auto secret = priv.get_shared_secret(pub); - auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str()); - auto plain_text = fc::aes_decrypt( nonce_plus_secret, message ); - auto result = memo_message::deserialize(string(plain_text.begin(), plain_text.end())); - FC_ASSERT( result.checksum == uint32_t(digest_type::hash(result.text)._hash[0]) ); - return result.text; - } - else - { - return memo_message::deserialize(string(message.begin(), message.end())).text; - } -} - -void custom_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const -{ - active_auth_set.insert(required_auths.begin(), required_auths.end()); -} -void custom_operation::validate()const -{ - FC_ASSERT( fee.amount > 0 ); -} -share_type custom_operation::calculate_fee( const fee_schedule_type& k )const -{ - return (data.size() * k.data_fee)/1024; -} - -void worker_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const -{ - active_auth_set.insert(owner); -} - -void worker_create_operation::validate() const -{ - FC_ASSERT(fee.amount >= 0); - FC_ASSERT(work_end_date > work_begin_date); - FC_ASSERT(daily_pay > 0); - FC_ASSERT(daily_pay < GRAPHENE_BLOCKCHAIN_MAX_SHARES); -} - -share_type worker_create_operation::calculate_fee(const fee_schedule_type& k) const -{ - return k.worker_create_fee; -} - -string memo_message::serialize() const -{ - auto serial_checksum = string(sizeof(checksum), ' '); - (uint32_t&)(*serial_checksum.data()) = htonl(checksum); - return serial_checksum + text; -} - -memo_message memo_message::deserialize(const string& serial) -{ - memo_message result; - FC_ASSERT( serial.size() >= sizeof(result.checksum) ); - result.checksum = ntohl((uint32_t&)(*serial.data())); - result.text = serial.substr(sizeof(result.checksum)); - return result; -} - -void account_upgrade_operation::validate() const -{ - FC_ASSERT( fee.amount >= 0 ); -} - -share_type account_upgrade_operation::calculate_fee(const fee_schedule_type& k) const -{ - if( upgrade_to_lifetime_member ) - return k.membership_lifetime_fee; - return k.membership_annual_fee; -} - -} } // namespace graphene::chain diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index f365263f..86e6ee04 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -18,12 +18,13 @@ #include #include #include -#include +#include +#include namespace graphene { namespace chain { -object_id_type proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) -{ +void_result proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) +{ try { const database& d = db(); const auto& global_parameters = d.get_global_properties().parameters; @@ -34,24 +35,43 @@ object_id_type proposal_create_evaluator::do_evaluate(const proposal_create_oper "Proposal review period must be less than its overall lifetime." ); { - // If we're dealing with the genesis authority, make sure this transaction has a sufficient review period. + // If we're dealing with the committee authority, make sure this transaction has a sufficient review period. flat_set auths; + vector other; for( auto& op : o.proposed_ops ) - op.op.visit(operation_get_required_auths(auths, auths)); + { + operation_get_required_authorities(op.op, auths, auths, other); + } + + FC_ASSERT( other.size() == 0 ); // TODO: what about other??? + if( auths.find(account_id_type()) != auths.end() ) - FC_ASSERT( o.review_period_seconds - && *o.review_period_seconds >= global_parameters.genesis_proposal_review_period ); + { + GRAPHENE_ASSERT( + o.review_period_seconds.valid(), + proposal_create_review_period_required, + "Review period not given, but at least ${min} required", + ("min", global_parameters.committee_proposal_review_period) + ); + GRAPHENE_ASSERT( + *o.review_period_seconds >= global_parameters.committee_proposal_review_period, + proposal_create_review_period_insufficient, + "Review period of ${t} required, but at least ${min} required", + ("t", *o.review_period_seconds) + ("min", global_parameters.committee_proposal_review_period) + ); + } } for( const op_wrapper& op : o.proposed_ops ) _proposed_trx.operations.push_back(op.op); _proposed_trx.validate(); - return object_id_type(); -} + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } object_id_type proposal_create_evaluator::do_apply(const proposal_create_operation& o) -{ +{ try { database& d = db(); const proposal_object& proposal = d.create([&](proposal_object& proposal) { @@ -62,7 +82,12 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati //Populate the required approval sets flat_set required_active; - _proposed_trx.visit(operation_get_required_auths(required_active, proposal.required_owner_approvals)); + vector other; + + // TODO: consider caching values from evaluate? + for( auto& op : _proposed_trx.operations ) + operation_get_required_authorities(op, required_active, proposal.required_owner_approvals, other); + //All accounts which must provide both owner and active authority should be omitted from the active authority set; //owner authority approval implies active authority approval. std::set_difference(required_active.begin(), required_active.end(), @@ -71,10 +96,10 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati }); return proposal.id; -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result proposal_update_evaluator::do_evaluate(const proposal_update_operation& o) -{ +{ try { database& d = db(); _proposal = &o.proposal(d); @@ -93,20 +118,26 @@ void_result proposal_update_evaluator::do_evaluate(const proposal_update_operati FC_ASSERT( _proposal->available_owner_approvals.find(id) != _proposal->available_owner_approvals.end(), "", ("id", id)("available", _proposal->available_owner_approvals) ); } - for( key_id_type id : o.key_approvals_to_add ) + + /* All authority checks happen outside of evaluators, TODO: verify this is checked elsewhere + */ + if( (d.get_node_properties().skip_flags & database::skip_authority_check) == 0 ) { - FC_ASSERT( trx_state->signed_by(id) || trx_state->_skip_authority_check ); - } - for( key_id_type id : o.key_approvals_to_remove ) - { - FC_ASSERT( trx_state->signed_by(id) || trx_state->_skip_authority_check ); + for( const auto& id : o.key_approvals_to_add ) + { + FC_ASSERT( trx_state->signed_by(id) ); + } + for( const auto& id : o.key_approvals_to_remove ) + { + FC_ASSERT( trx_state->signed_by(id) ); + } } return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result proposal_update_evaluator::do_apply(const proposal_update_operation& o) -{ +{ try { database& d = db(); // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal skip @@ -119,9 +150,9 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& p.available_active_approvals.erase(id); for( account_id_type id : o.owner_approvals_to_remove ) p.available_owner_approvals.erase(id); - for( key_id_type id : o.key_approvals_to_add ) + for( const auto& id : o.key_approvals_to_add ) p.available_key_approvals.insert(id); - for( key_id_type id : o.key_approvals_to_remove ) + for( const auto& id : o.key_approvals_to_remove ) p.available_key_approvals.erase(id); }); @@ -130,7 +161,7 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& if( _proposal->review_period_time ) return void_result(); - if( _proposal->is_authorized_to_execute(&d) ) + if( _proposal->is_authorized_to_execute(d) ) { // All required approvals are satisfied. Execute! _executed_proposal = true; @@ -144,10 +175,10 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& } return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } void_result proposal_delete_evaluator::do_evaluate(const proposal_delete_operation& o) -{ +{ try { database& d = db(); _proposal = &o.proposal(d); @@ -159,13 +190,13 @@ void_result proposal_delete_evaluator::do_evaluate(const proposal_delete_operati ("provided", o.fee_paying_account)("required", *required_approvals)); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } -void_result proposal_delete_evaluator::do_apply(const proposal_delete_operation&) -{ +void_result proposal_delete_evaluator::do_apply(const proposal_delete_operation& o) +{ try { db().remove(*_proposal); return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } } } // graphene::chain diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index cc148526..ae7aa559 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -21,9 +21,9 @@ namespace graphene { namespace chain { -bool proposal_object::is_authorized_to_execute(database* db) const +bool proposal_object::is_authorized_to_execute(database& db) const { - transaction_evaluation_state dry_run_eval(db); + transaction_evaluation_state dry_run_eval(&db); dry_run_eval._is_proposed_trx = true; std::transform(available_active_approvals.begin(), available_active_approvals.end(), std::inserter(dry_run_eval.approved_by, dry_run_eval.approved_by.end()), [](object_id_type id) { @@ -36,21 +36,64 @@ bool proposal_object::is_authorized_to_execute(database* db) const signed_transaction tmp; dry_run_eval._trx = &tmp; + for( auto key_id : available_key_approvals ) - tmp.signatures[key_id] = fc::ecc::compact_signature(); + dry_run_eval._sigs.insert( std::make_pair(key_id,true) ); //insert into dry_run_eval->_trx.signatures //dry_run_eval.signed_by.insert(available_key_approvals.begin(), available_key_approvals.end()); // Check all required approvals. If any of them are unsatisfied, return false. for( const auto& id : required_active_approvals ) - if( !dry_run_eval.check_authority(id(*db), authority::active) ) + if( !dry_run_eval.check_authority(id(db), authority::active) ) return false; for( const auto& id : required_owner_approvals ) - if( !dry_run_eval.check_authority(id(*db), authority::owner) ) + if( !dry_run_eval.check_authority(id(db), authority::owner) ) return false; return true; } + +void required_approval_index::object_inserted( const object& obj ) +{ + assert( dynamic_cast(&obj) ); + const proposal_object& p = static_cast(obj); + + for( const auto& a : p.required_active_approvals ) + _account_to_proposals[a].insert( p.id ); + for( const auto& a : p.required_owner_approvals ) + _account_to_proposals[a].insert( p.id ); + for( const auto& a : p.available_active_approvals ) + _account_to_proposals[a].insert( p.id ); + for( const auto& a : p.available_owner_approvals ) + _account_to_proposals[a].insert( p.id ); +} + +void required_approval_index::remove( account_id_type a, proposal_id_type p ) +{ + auto itr = _account_to_proposals.find(a); + if( itr != _account_to_proposals.end() ) + { + itr->second.erase( p ); + if( itr->second.empty() ) + _account_to_proposals.erase( itr->first ); + } +} + +void required_approval_index::object_removed( const object& obj ) +{ + assert( dynamic_cast(&obj) ); + const proposal_object& p = static_cast(obj); + + for( const auto& a : p.required_active_approvals ) + remove( a, p.id ); + for( const auto& a : p.required_owner_approvals ) + remove( a, p.id ); + for( const auto& a : p.available_active_approvals ) + remove( a, p.id ); + for( const auto& a : p.available_owner_approvals ) + remove( a, p.id ); +} + } } // graphene::chain diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp new file mode 100644 index 00000000..d8d9100f --- /dev/null +++ b/libraries/chain/protocol/account.cpp @@ -0,0 +1,199 @@ +#include + +namespace graphene { namespace chain { + +/** + * Names must comply with the following grammar (RFC 1035): + * ::= | " " + * ::=