diff --git a/AshDB b/AshDB index f11c7b8..83c7413 160000 --- a/AshDB +++ b/AshDB @@ -1 +1 @@ -Subproject commit f11c7b8403cd5d42aa112a1bc91fd0964064efe7 +Subproject commit 83c7413729af8ac3e2627d1c6bffb92223766079 diff --git a/CMake/conan.cmake b/CMake/conan.cmake new file mode 100644 index 0000000..cabb352 --- /dev/null +++ b/CMake/conan.cmake @@ -0,0 +1,902 @@ +# The MIT License (MIT) + +# Copyright (c) 2018 JFrog + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + + +# This file comes from: https://github.com/conan-io/cmake-conan. Please refer +# to this repository for issues and documentation. + +# Its purpose is to wrap and launch Conan C/C++ Package Manager when cmake is called. +# It will take CMake current settings (os, compiler, compiler version, architecture) +# and translate them to conan settings for installing and retrieving dependencies. + +# It is intended to facilitate developers building projects that have conan dependencies, +# but it is only necessary on the end-user side. It is not necessary to create conan +# packages, in fact it shouldn't be use for that. Check the project documentation. + +# version: 0.17.0-dev + +include(CMakeParseArguments) + +function(_get_msvc_ide_version result) + set(${result} "" PARENT_SCOPE) + if(NOT MSVC_VERSION VERSION_LESS 1400 AND MSVC_VERSION VERSION_LESS 1500) + set(${result} 8 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1500 AND MSVC_VERSION VERSION_LESS 1600) + set(${result} 9 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1600 AND MSVC_VERSION VERSION_LESS 1700) + set(${result} 10 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1700 AND MSVC_VERSION VERSION_LESS 1800) + set(${result} 11 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1800 AND MSVC_VERSION VERSION_LESS 1900) + set(${result} 12 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1900 AND MSVC_VERSION VERSION_LESS 1910) + set(${result} 14 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1910 AND MSVC_VERSION VERSION_LESS 1920) + set(${result} 15 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1920 AND MSVC_VERSION VERSION_LESS 1930) + set(${result} 16 PARENT_SCOPE) + else() + message(FATAL_ERROR "Conan: Unknown MSVC compiler version [${MSVC_VERSION}]") + endif() +endfunction() + +macro(_conan_detect_build_type) + conan_parse_arguments(${ARGV}) + + if(ARGUMENTS_BUILD_TYPE) + set(_CONAN_SETTING_BUILD_TYPE ${ARGUMENTS_BUILD_TYPE}) + elseif(CMAKE_BUILD_TYPE) + set(_CONAN_SETTING_BUILD_TYPE ${CMAKE_BUILD_TYPE}) + else() + message(FATAL_ERROR "Please specify in command line CMAKE_BUILD_TYPE (-DCMAKE_BUILD_TYPE=Release)") + endif() + + string(TOUPPER ${_CONAN_SETTING_BUILD_TYPE} _CONAN_SETTING_BUILD_TYPE_UPPER) + if (_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "DEBUG") + set(_CONAN_SETTING_BUILD_TYPE "Debug") + elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "RELEASE") + set(_CONAN_SETTING_BUILD_TYPE "Release") + elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "RELWITHDEBINFO") + set(_CONAN_SETTING_BUILD_TYPE "RelWithDebInfo") + elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "MINSIZEREL") + set(_CONAN_SETTING_BUILD_TYPE "MinSizeRel") + endif() +endmacro() + +macro(_conan_check_system_name) + #handle -s os setting + if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") + #use default conan os setting if CMAKE_SYSTEM_NAME is not defined + set(CONAN_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CONAN_SYSTEM_NAME Macos) + endif() + if(${CMAKE_SYSTEM_NAME} STREQUAL "QNX") + set(CONAN_SYSTEM_NAME Neutrino) + endif() + set(CONAN_SUPPORTED_PLATFORMS Windows Linux Macos Android iOS FreeBSD WindowsStore WindowsCE watchOS tvOS FreeBSD SunOS AIX Arduino Emscripten Neutrino) + list (FIND CONAN_SUPPORTED_PLATFORMS "${CONAN_SYSTEM_NAME}" _index) + if (${_index} GREATER -1) + #check if the cmake system is a conan supported one + set(_CONAN_SETTING_OS ${CONAN_SYSTEM_NAME}) + else() + message(FATAL_ERROR "cmake system ${CONAN_SYSTEM_NAME} is not supported by conan. Use one of ${CONAN_SUPPORTED_PLATFORMS}") + endif() + endif() +endmacro() + +macro(_conan_check_language) + get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if (";${_languages};" MATCHES ";CXX;") + set(LANGUAGE CXX) + set(USING_CXX 1) + elseif (";${_languages};" MATCHES ";C;") + set(LANGUAGE C) + set(USING_CXX 0) + else () + message(FATAL_ERROR "Conan: Neither C or C++ was detected as a language for the project. Unabled to detect compiler version.") + endif() +endmacro() + +macro(_conan_detect_compiler) + + conan_parse_arguments(${ARGV}) + + if(ARGUMENTS_ARCH) + set(_CONAN_SETTING_ARCH ${ARGUMENTS_ARCH}) + endif() + + if (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL GNU) + # using GCC + # TODO: Handle other params + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(COMPILER_VERSION ${MAJOR}.${MINOR}) + if(${MAJOR} GREATER 4) + set(COMPILER_VERSION ${MAJOR}) + endif() + set(_CONAN_SETTING_COMPILER gcc) + set(_CONAN_SETTING_COMPILER_VERSION ${COMPILER_VERSION}) + if (USING_CXX) + conan_cmake_detect_unix_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL Intel) + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(COMPILER_VERSION ${MAJOR}.${MINOR}) + set(_CONAN_SETTING_COMPILER intel) + set(_CONAN_SETTING_COMPILER_VERSION ${COMPILER_VERSION}) + if (USING_CXX) + conan_cmake_detect_unix_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL AppleClang) + # using AppleClang + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(_CONAN_SETTING_COMPILER apple-clang) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + if (USING_CXX) + conan_cmake_detect_unix_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL Clang) + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(_CONAN_SETTING_COMPILER clang) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + if(APPLE) + cmake_policy(GET CMP0025 APPLE_CLANG_POLICY) + if(NOT APPLE_CLANG_POLICY STREQUAL NEW) + message(STATUS "Conan: APPLE and Clang detected. Assuming apple-clang compiler. Set CMP0025 to avoid it") + set(_CONAN_SETTING_COMPILER apple-clang) + endif() + endif() + if(${_CONAN_SETTING_COMPILER} STREQUAL clang AND ${MAJOR} GREATER 7) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}) + endif() + if (USING_CXX) + conan_cmake_detect_unix_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + elseif(${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL MSVC) + set(_VISUAL "Visual Studio") + _get_msvc_ide_version(_VISUAL_VERSION) + if("${_VISUAL_VERSION}" STREQUAL "") + message(FATAL_ERROR "Conan: Visual Studio not recognized") + else() + set(_CONAN_SETTING_COMPILER ${_VISUAL}) + set(_CONAN_SETTING_COMPILER_VERSION ${_VISUAL_VERSION}) + endif() + + if(NOT _CONAN_SETTING_ARCH) + if (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "64") + set(_CONAN_SETTING_ARCH x86_64) + elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "^ARM") + message(STATUS "Conan: Using default ARM architecture from MSVC") + set(_CONAN_SETTING_ARCH armv6) + elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "86") + set(_CONAN_SETTING_ARCH x86) + else () + message(FATAL_ERROR "Conan: Unknown MSVC architecture [${MSVC_${LANGUAGE}_ARCHITECTURE_ID}]") + endif() + endif() + + conan_cmake_detect_vs_runtime(_vs_runtime ${ARGV}) + message(STATUS "Conan: Detected VS runtime: ${_vs_runtime}") + set(_CONAN_SETTING_COMPILER_RUNTIME ${_vs_runtime}) + + if (CMAKE_GENERATOR_TOOLSET) + set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) + elseif(CMAKE_VS_PLATFORM_TOOLSET AND (CMAKE_GENERATOR STREQUAL "Ninja")) + set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) + endif() + else() + message(FATAL_ERROR "Conan: compiler setup not recognized") + endif() + +endmacro() + +function(conan_cmake_settings result) + #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER}) + #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER_ID}) + #message(STATUS "VERSION " ${CMAKE_CXX_COMPILER_VERSION}) + #message(STATUS "FLAGS " ${CMAKE_LANG_FLAGS}) + #message(STATUS "LIB ARCH " ${CMAKE_CXX_LIBRARY_ARCHITECTURE}) + #message(STATUS "BUILD TYPE " ${CMAKE_BUILD_TYPE}) + #message(STATUS "GENERATOR " ${CMAKE_GENERATOR}) + #message(STATUS "GENERATOR WIN64 " ${CMAKE_CL_64}) + + message(STATUS "Conan: Automatic detection of conan settings from cmake") + + conan_parse_arguments(${ARGV}) + + _conan_detect_build_type(${ARGV}) + + _conan_check_system_name() + + _conan_check_language() + + _conan_detect_compiler(${ARGV}) + + # If profile is defined it is used + if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND ARGUMENTS_DEBUG_PROFILE) + set(_APPLIED_PROFILES ${ARGUMENTS_DEBUG_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "Release" AND ARGUMENTS_RELEASE_PROFILE) + set(_APPLIED_PROFILES ${ARGUMENTS_RELEASE_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND ARGUMENTS_RELWITHDEBINFO_PROFILE) + set(_APPLIED_PROFILES ${ARGUMENTS_RELWITHDEBINFO_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel" AND ARGUMENTS_MINSIZEREL_PROFILE) + set(_APPLIED_PROFILES ${ARGUMENTS_MINSIZEREL_PROFILE}) + elseif(ARGUMENTS_PROFILE) + set(_APPLIED_PROFILES ${ARGUMENTS_PROFILE}) + endif() + + foreach(ARG ${_APPLIED_PROFILES}) + set(_SETTINGS ${_SETTINGS} -pr=${ARG}) + endforeach() + foreach(ARG ${ARGUMENTS_PROFILE_BUILD}) + conan_check(VERSION 1.24.0 REQUIRED DETECT_QUIET) + set(_SETTINGS ${_SETTINGS} -pr:b=${ARG}) + endforeach() + + if(NOT _SETTINGS OR ARGUMENTS_PROFILE_AUTO STREQUAL "ALL") + set(ARGUMENTS_PROFILE_AUTO arch build_type compiler compiler.version + compiler.runtime compiler.libcxx compiler.toolset) + endif() + + # remove any manually specified settings from the autodetected settings + foreach(ARG ${ARGUMENTS_SETTINGS}) + string(REGEX MATCH "[^=]*" MANUAL_SETTING "${ARG}") + message(STATUS "Conan: ${MANUAL_SETTING} was added as an argument. Not using the autodetected one.") + list(REMOVE_ITEM ARGUMENTS_PROFILE_AUTO "${MANUAL_SETTING}") + endforeach() + + # Automatic from CMake + foreach(ARG ${ARGUMENTS_PROFILE_AUTO}) + string(TOUPPER ${ARG} _arg_name) + string(REPLACE "." "_" _arg_name ${_arg_name}) + if(_CONAN_SETTING_${_arg_name}) + set(_SETTINGS ${_SETTINGS} -s ${ARG}=${_CONAN_SETTING_${_arg_name}}) + endif() + endforeach() + + foreach(ARG ${ARGUMENTS_SETTINGS}) + set(_SETTINGS ${_SETTINGS} -s ${ARG}) + endforeach() + + message(STATUS "Conan: Settings= ${_SETTINGS}") + + set(${result} ${_SETTINGS} PARENT_SCOPE) +endfunction() + + +function(conan_cmake_detect_unix_libcxx result) + # Take into account any -stdlib in compile options + get_directory_property(compile_options DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_OPTIONS) + string(GENEX_STRIP "${compile_options}" compile_options) + + # Take into account any _GLIBCXX_USE_CXX11_ABI in compile definitions + get_directory_property(defines DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_DEFINITIONS) + string(GENEX_STRIP "${defines}" defines) + + foreach(define ${defines}) + if(define MATCHES "_GLIBCXX_USE_CXX11_ABI") + if(define MATCHES "^-D") + set(compile_options ${compile_options} "${define}") + else() + set(compile_options ${compile_options} "-D${define}") + endif() + endif() + endforeach() + + # add additional compiler options ala cmRulePlaceholderExpander::ExpandRuleVariable + set(EXPAND_CXX_COMPILER ${CMAKE_CXX_COMPILER}) + if(CMAKE_CXX_COMPILER_ARG1) + # CMake splits CXX="foo bar baz" into CMAKE_CXX_COMPILER="foo", CMAKE_CXX_COMPILER_ARG1="bar baz" + # without this, ccache, winegcc, or other wrappers might lose all their arguments + separate_arguments(SPLIT_CXX_COMPILER_ARG1 NATIVE_COMMAND ${CMAKE_CXX_COMPILER_ARG1}) + list(APPEND EXPAND_CXX_COMPILER ${SPLIT_CXX_COMPILER_ARG1}) + endif() + + if(CMAKE_CXX_COMPILE_OPTIONS_TARGET AND CMAKE_CXX_COMPILER_TARGET) + # without --target= we may be calling the wrong underlying GCC + list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_TARGET}${CMAKE_CXX_COMPILER_TARGET}") + endif() + + if(CMAKE_CXX_COMPILE_OPTIONS_EXTERNAL_TOOLCHAIN AND CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN) + list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_EXTERNAL_TOOLCHAIN}${CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN}") + endif() + + if(CMAKE_CXX_COMPILE_OPTIONS_SYSROOT) + # without --sysroot= we may find the wrong #include + if(CMAKE_SYSROOT_COMPILE) + list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_SYSROOT}${CMAKE_SYSROOT_COMPILE}") + elseif(CMAKE_SYSROOT) + list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_SYSROOT}${CMAKE_SYSROOT}") + endif() + endif() + + separate_arguments(SPLIT_CXX_FLAGS NATIVE_COMMAND ${CMAKE_CXX_FLAGS}) + + if(CMAKE_OSX_SYSROOT) + set(xcode_sysroot_option "--sysroot=${CMAKE_OSX_SYSROOT}") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} -E echo "#include " + COMMAND ${EXPAND_CXX_COMPILER} ${SPLIT_CXX_FLAGS} -x c++ ${xcode_sysroot_option} ${compile_options} -E -dM - + OUTPUT_VARIABLE string_defines + ) + + if(string_defines MATCHES "#define __GLIBCXX__") + # Allow -D_GLIBCXX_USE_CXX11_ABI=ON/OFF as argument to cmake + if(DEFINED _GLIBCXX_USE_CXX11_ABI) + if(_GLIBCXX_USE_CXX11_ABI) + set(${result} libstdc++11 PARENT_SCOPE) + return() + else() + set(${result} libstdc++ PARENT_SCOPE) + return() + endif() + endif() + + if(string_defines MATCHES "#define _GLIBCXX_USE_CXX11_ABI 1\n") + set(${result} libstdc++11 PARENT_SCOPE) + else() + # Either the compiler is missing the define because it is old, and so + # it can't use the new abi, or the compiler was configured to use the + # old abi by the user or distro (e.g. devtoolset on RHEL/CentOS) + set(${result} libstdc++ PARENT_SCOPE) + endif() + else() + set(${result} libc++ PARENT_SCOPE) + endif() +endfunction() + +function(conan_cmake_detect_vs_runtime result) + + conan_parse_arguments(${ARGV}) + if(ARGUMENTS_BUILD_TYPE) + set(build_type "${ARGUMENTS_BUILD_TYPE}") + elseif(CMAKE_BUILD_TYPE) + set(build_type "${CMAKE_BUILD_TYPE}") + else() + message(FATAL_ERROR "Please specify in command line CMAKE_BUILD_TYPE (-DCMAKE_BUILD_TYPE=Release)") + endif() + + if(build_type) + string(TOUPPER "${build_type}" build_type) + endif() + set(variables CMAKE_CXX_FLAGS_${build_type} CMAKE_C_FLAGS_${build_type} CMAKE_CXX_FLAGS CMAKE_C_FLAGS) + foreach(variable ${variables}) + if(NOT "${${variable}}" STREQUAL "") + string(REPLACE " " ";" flags "${${variable}}") + foreach (flag ${flags}) + if("${flag}" STREQUAL "/MD" OR "${flag}" STREQUAL "/MDd" OR "${flag}" STREQUAL "/MT" OR "${flag}" STREQUAL "/MTd") + string(SUBSTRING "${flag}" 1 -1 runtime) + set(${result} "${runtime}" PARENT_SCOPE) + return() + endif() + endforeach() + endif() + endforeach() + if("${build_type}" STREQUAL "DEBUG") + set(${result} "MDd" PARENT_SCOPE) + else() + set(${result} "MD" PARENT_SCOPE) + endif() +endfunction() + +function(_collect_settings result) + set(ARGUMENTS_PROFILE_AUTO arch build_type compiler compiler.version + compiler.runtime compiler.libcxx compiler.toolset) + foreach(ARG ${ARGUMENTS_PROFILE_AUTO}) + string(TOUPPER ${ARG} _arg_name) + string(REPLACE "." "_" _arg_name ${_arg_name}) + if(_CONAN_SETTING_${_arg_name}) + set(detected_setings ${detected_setings} ${ARG}=${_CONAN_SETTING_${_arg_name}}) + endif() + endforeach() + set(${result} ${detected_setings} PARENT_SCOPE) +endfunction() + +function(conan_cmake_autodetect detected_settings ${ARGV}) + _conan_detect_build_type(${ARGV}) + _conan_check_system_name() + _conan_check_language() + _conan_detect_compiler(${ARGV}) + _collect_settings(collected_settings) + set(${detected_settings} ${collected_settings} PARENT_SCOPE) +endfunction() + +macro(conan_parse_arguments) + set(options BASIC_SETUP CMAKE_TARGETS UPDATE KEEP_RPATHS NO_LOAD NO_OUTPUT_DIRS OUTPUT_QUIET NO_IMPORTS SKIP_STD) + set(oneValueArgs CONANFILE ARCH BUILD_TYPE INSTALL_FOLDER CONAN_COMMAND) + set(multiValueArgs DEBUG_PROFILE RELEASE_PROFILE RELWITHDEBINFO_PROFILE MINSIZEREL_PROFILE + PROFILE REQUIRES OPTIONS IMPORTS SETTINGS BUILD ENV GENERATORS PROFILE_AUTO + INSTALL_ARGS CONFIGURATION_TYPES PROFILE_BUILD BUILD_REQUIRES) + cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) +endmacro() + +function(old_conan_cmake_install) + # Calls "conan install" + # Argument BUILD is equivalant to --build={missing, PkgName,...} or + # --build when argument is 'BUILD all' (which builds all packages from source) + # Argument CONAN_COMMAND, to specify the conan path, e.g. in case of running from source + # cmake does not identify conan as command, even if it is +x and it is in the path + conan_parse_arguments(${ARGV}) + + if(CONAN_CMAKE_MULTI) + set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake_multi) + else() + set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake) + endif() + + set(CONAN_BUILD_POLICY "") + foreach(ARG ${ARGUMENTS_BUILD}) + if(${ARG} STREQUAL "all") + set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build) + break() + else() + set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build=${ARG}) + endif() + endforeach() + if(ARGUMENTS_CONAN_COMMAND) + set(CONAN_CMD ${ARGUMENTS_CONAN_COMMAND}) + else() + conan_check(REQUIRED) + endif() + set(CONAN_OPTIONS "") + if(ARGUMENTS_CONANFILE) + if(IS_ABSOLUTE ${ARGUMENTS_CONANFILE}) + set(CONANFILE ${ARGUMENTS_CONANFILE}) + else() + set(CONANFILE ${CMAKE_CURRENT_SOURCE_DIR}/${ARGUMENTS_CONANFILE}) + endif() + else() + set(CONANFILE ".") + endif() + foreach(ARG ${ARGUMENTS_OPTIONS}) + set(CONAN_OPTIONS ${CONAN_OPTIONS} -o=${ARG}) + endforeach() + if(ARGUMENTS_UPDATE) + set(CONAN_INSTALL_UPDATE --update) + endif() + if(ARGUMENTS_NO_IMPORTS) + set(CONAN_INSTALL_NO_IMPORTS --no-imports) + endif() + set(CONAN_INSTALL_FOLDER "") + if(ARGUMENTS_INSTALL_FOLDER) + set(CONAN_INSTALL_FOLDER -if=${ARGUMENTS_INSTALL_FOLDER}) + endif() + foreach(ARG ${ARGUMENTS_GENERATORS}) + set(CONAN_GENERATORS ${CONAN_GENERATORS} -g=${ARG}) + endforeach() + foreach(ARG ${ARGUMENTS_ENV}) + set(CONAN_ENV_VARS ${CONAN_ENV_VARS} -e=${ARG}) + endforeach() + set(conan_args install ${CONANFILE} ${settings} ${CONAN_ENV_VARS} ${CONAN_GENERATORS} ${CONAN_BUILD_POLICY} ${CONAN_INSTALL_UPDATE} ${CONAN_INSTALL_NO_IMPORTS} ${CONAN_OPTIONS} ${CONAN_INSTALL_FOLDER} ${ARGUMENTS_INSTALL_ARGS}) + + string (REPLACE ";" " " _conan_args "${conan_args}") + message(STATUS "Conan executing: ${CONAN_CMD} ${_conan_args}") + + if(ARGUMENTS_OUTPUT_QUIET) + execute_process(COMMAND ${CONAN_CMD} ${conan_args} + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_output + ERROR_VARIABLE conan_output + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + else() + execute_process(COMMAND ${CONAN_CMD} ${conan_args} + RESULT_VARIABLE return_code + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan install failed='${return_code}'") + endif() + +endfunction() + +function(conan_cmake_install) + if(DEFINED CONAN_COMMAND) + set(CONAN_CMD ${CONAN_COMMAND}) + else() + conan_check(REQUIRED) + endif() + + set(installOptions UPDATE NO_IMPORTS OUTPUT_QUIET ERROR_QUIET) + set(installOneValueArgs PATH_OR_REFERENCE REFERENCE REMOTE LOCKFILE LOCKFILE_OUT LOCKFILE_NODE_ID INSTALL_FOLDER) + set(installMultiValueArgs GENERATOR BUILD ENV ENV_HOST ENV_BUILD OPTIONS_HOST OPTIONS OPTIONS_BUILD PROFILE + PROFILE_HOST PROFILE_BUILD SETTINGS SETTINGS_HOST SETTINGS_BUILD) + cmake_parse_arguments(ARGS "${installOptions}" "${installOneValueArgs}" "${installMultiValueArgs}" ${ARGN}) + foreach(arg ${installOptions}) + if(ARGS_${arg}) + set(${arg} ${${arg}} ${ARGS_${arg}}) + endif() + endforeach() + foreach(arg ${installOneValueArgs}) + if(DEFINED ARGS_${arg}) + if("${arg}" STREQUAL "REMOTE") + set(flag "--remote") + elseif("${arg}" STREQUAL "LOCKFILE") + set(flag "--lockfile") + elseif("${arg}" STREQUAL "LOCKFILE_OUT") + set(flag "--lockfile-out") + elseif("${arg}" STREQUAL "LOCKFILE_NODE_ID") + set(flag "--lockfile-node-id") + elseif("${arg}" STREQUAL "INSTALL_FOLDER") + set(flag "--install-folder") + endif() + set(${arg} ${${arg}} ${flag} ${ARGS_${arg}}) + endif() + endforeach() + foreach(arg ${installMultiValueArgs}) + if(DEFINED ARGS_${arg}) + if("${arg}" STREQUAL "GENERATOR") + set(flag "--generator") + elseif("${arg}" STREQUAL "BUILD") + set(flag "--build") + elseif("${arg}" STREQUAL "ENV") + set(flag "--env") + elseif("${arg}" STREQUAL "ENV_HOST") + set(flag "--env:host") + elseif("${arg}" STREQUAL "ENV_BUILD") + set(flag "--env:build") + elseif("${arg}" STREQUAL "OPTIONS") + set(flag "--options") + elseif("${arg}" STREQUAL "OPTIONS_HOST") + set(flag "--options:host") + elseif("${arg}" STREQUAL "OPTIONS_BUILD") + set(flag "--options:build") + elseif("${arg}" STREQUAL "PROFILE") + set(flag "--profile") + elseif("${arg}" STREQUAL "PROFILE_HOST") + set(flag "--profile:host") + elseif("${arg}" STREQUAL "PROFILE_BUILD") + set(flag "--profile:build") + elseif("${arg}" STREQUAL "SETTINGS") + set(flag "--settings") + elseif("${arg}" STREQUAL "SETTINGS_HOST") + set(flag "--settings:host") + elseif("${arg}" STREQUAL "SETTINGS_BUILD") + set(flag "--settings:build") + endif() + list(LENGTH ARGS_${arg} numargs) + foreach(item ${ARGS_${arg}}) + if(${item} STREQUAL "all" AND ${arg} STREQUAL "BUILD") + set(${arg} "--build") + break() + endif() + set(${arg} ${${arg}} ${flag} ${item}) + endforeach() + endif() + endforeach() + if(DEFINED UPDATE) + set(UPDATE --update) + endif() + if(DEFINED NO_IMPORTS) + set(NO_IMPORTS --no-imports) + endif() + set(install_args install ${PATH_OR_REFERENCE} ${REFERENCE} ${UPDATE} ${NO_IMPORTS} ${REMOTE} ${LOCKFILE} ${LOCKFILE_OUT} ${LOCKFILE_NODE_ID} ${INSTALL_FOLDER} + ${GENERATOR} ${BUILD} ${ENV} ${ENV_HOST} ${ENV_BUILD} ${OPTIONS} ${OPTIONS_HOST} ${OPTIONS_BUILD} + ${PROFILE} ${PROFILE_HOST} ${PROFILE_BUILD} ${SETTINGS} ${SETTINGS_HOST} ${SETTINGS_BUILD}) + + string(REPLACE ";" " " _install_args "${install_args}") + message(STATUS "Conan executing: ${CONAN_CMD} ${_install_args}") + + if(ARGS_OUTPUT_QUIET) + set(OUTPUT_OPT OUTPUT_QUIET) + endif() + if(ARGS_ERROR_QUIET) + set(ERROR_OPT ERROR_QUIET) + endif() + + execute_process(COMMAND ${CONAN_CMD} ${install_args} + RESULT_VARIABLE return_code + ${OUTPUT_OPT} + ${ERROR_OPT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if(NOT "${return_code}" STREQUAL "0") + if (ARGS_ERROR_QUIET) + message(WARNING "Conan install failed='${return_code}'") + else() + message(FATAL_ERROR "Conan install failed='${return_code}'") + endif() + endif() + +endfunction() + +function(conan_cmake_setup_conanfile) + conan_parse_arguments(${ARGV}) + if(ARGUMENTS_CONANFILE) + get_filename_component(_CONANFILE_NAME ${ARGUMENTS_CONANFILE} NAME) + # configure_file will make sure cmake re-runs when conanfile is updated + configure_file(${ARGUMENTS_CONANFILE} ${CMAKE_CURRENT_BINARY_DIR}/${_CONANFILE_NAME}.junk COPYONLY) + file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${_CONANFILE_NAME}.junk) + else() + conan_cmake_generate_conanfile(ON ${ARGV}) + endif() +endfunction() + +function(conan_cmake_configure) + conan_cmake_generate_conanfile(OFF ${ARGV}) +endfunction() + +# Generate, writing in disk a conanfile.txt with the requires, options, and imports +# specified as arguments +# This will be considered as temporary file, generated in CMAKE_CURRENT_BINARY_DIR) +function(conan_cmake_generate_conanfile DEFAULT_GENERATOR) + + conan_parse_arguments(${ARGV}) + + set(_FN "${CMAKE_CURRENT_BINARY_DIR}/conanfile.txt") + file(WRITE ${_FN} "") + + if(DEFINED ARGUMENTS_REQUIRES) + file(APPEND ${_FN} "[requires]\n") + foreach(REQUIRE ${ARGUMENTS_REQUIRES}) + file(APPEND ${_FN} ${REQUIRE} "\n") + endforeach() + endif() + + if (DEFAULT_GENERATOR OR DEFINED ARGUMENTS_GENERATORS) + file(APPEND ${_FN} "[generators]\n") + if (DEFAULT_GENERATOR) + file(APPEND ${_FN} "cmake\n") + endif() + if (DEFINED ARGUMENTS_GENERATORS) + foreach(GENERATOR ${ARGUMENTS_GENERATORS}) + file(APPEND ${_FN} ${GENERATOR} "\n") + endforeach() + endif() + endif() + + if(DEFINED ARGUMENTS_BUILD_REQUIRES) + file(APPEND ${_FN} "[build_requires]\n") + foreach(BUILD_REQUIRE ${ARGUMENTS_BUILD_REQUIRES}) + file(APPEND ${_FN} ${BUILD_REQUIRE} "\n") + endforeach() + endif() + + if(DEFINED ARGUMENTS_IMPORTS) + file(APPEND ${_FN} "[imports]\n") + foreach(IMPORTS ${ARGUMENTS_IMPORTS}) + file(APPEND ${_FN} ${IMPORTS} "\n") + endforeach() + endif() + + if(DEFINED ARGUMENTS_OPTIONS) + file(APPEND ${_FN} "[options]\n") + foreach(OPTION ${ARGUMENTS_OPTIONS}) + file(APPEND ${_FN} ${OPTION} "\n") + endforeach() + endif() + +endfunction() + + +macro(conan_load_buildinfo) + if(CONAN_CMAKE_MULTI) + set(_CONANBUILDINFO conanbuildinfo_multi.cmake) + else() + set(_CONANBUILDINFO conanbuildinfo.cmake) + endif() + if(ARGUMENTS_INSTALL_FOLDER) + set(_CONANBUILDINFOFOLDER ${ARGUMENTS_INSTALL_FOLDER}) + else() + set(_CONANBUILDINFOFOLDER ${CMAKE_CURRENT_BINARY_DIR}) + endif() + # Checks for the existence of conanbuildinfo.cmake, and loads it + # important that it is macro, so variables defined at parent scope + if(EXISTS "${_CONANBUILDINFOFOLDER}/${_CONANBUILDINFO}") + message(STATUS "Conan: Loading ${_CONANBUILDINFO}") + include(${_CONANBUILDINFOFOLDER}/${_CONANBUILDINFO}) + else() + message(FATAL_ERROR "${_CONANBUILDINFO} doesn't exist in ${CMAKE_CURRENT_BINARY_DIR}") + endif() +endmacro() + + +macro(conan_cmake_run) + conan_parse_arguments(${ARGV}) + + if(ARGUMENTS_CONFIGURATION_TYPES AND NOT CMAKE_CONFIGURATION_TYPES) + message(WARNING "CONFIGURATION_TYPES should only be specified for multi-configuration generators") + elseif(ARGUMENTS_CONFIGURATION_TYPES AND ARGUMENTS_BUILD_TYPE) + message(WARNING "CONFIGURATION_TYPES and BUILD_TYPE arguments should not be defined at the same time.") + endif() + + if(CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE AND NOT CONAN_EXPORTED + AND NOT ARGUMENTS_BUILD_TYPE) + set(CONAN_CMAKE_MULTI ON) + if (NOT ARGUMENTS_CONFIGURATION_TYPES) + set(ARGUMENTS_CONFIGURATION_TYPES "Release;Debug") + endif() + message(STATUS "Conan: Using cmake-multi generator") + else() + set(CONAN_CMAKE_MULTI OFF) + endif() + + if(NOT CONAN_EXPORTED) + conan_cmake_setup_conanfile(${ARGV}) + if(CONAN_CMAKE_MULTI) + foreach(CMAKE_BUILD_TYPE ${ARGUMENTS_CONFIGURATION_TYPES}) + set(ENV{CONAN_IMPORT_PATH} ${CMAKE_BUILD_TYPE}) + conan_cmake_settings(settings ${ARGV}) + old_conan_cmake_install(SETTINGS ${settings} ${ARGV}) + endforeach() + set(CMAKE_BUILD_TYPE) + else() + conan_cmake_settings(settings ${ARGV}) + old_conan_cmake_install(SETTINGS ${settings} ${ARGV}) + endif() + endif() + + if (NOT ARGUMENTS_NO_LOAD) + conan_load_buildinfo() + endif() + + if(ARGUMENTS_BASIC_SETUP) + foreach(_option CMAKE_TARGETS KEEP_RPATHS NO_OUTPUT_DIRS SKIP_STD) + if(ARGUMENTS_${_option}) + if(${_option} STREQUAL "CMAKE_TARGETS") + list(APPEND _setup_options "TARGETS") + else() + list(APPEND _setup_options ${_option}) + endif() + endif() + endforeach() + conan_basic_setup(${_setup_options}) + endif() +endmacro() + +macro(conan_check) + # Checks conan availability in PATH + # Arguments REQUIRED, DETECT_QUIET and VERSION are optional + # Example usage: + # conan_check(VERSION 1.0.0 REQUIRED) + set(options REQUIRED DETECT_QUIET) + set(oneValueArgs VERSION) + cmake_parse_arguments(CONAN "${options}" "${oneValueArgs}" "" ${ARGN}) + if(NOT CONAN_DETECT_QUIET) + message(STATUS "Conan: checking conan executable") + endif() + + find_program(CONAN_CMD conan) + if(NOT CONAN_CMD AND CONAN_REQUIRED) + message(FATAL_ERROR "Conan executable not found! Please install conan.") + endif() + if(NOT CONAN_DETECT_QUIET) + message(STATUS "Conan: Found program ${CONAN_CMD}") + endif() + execute_process(COMMAND ${CONAN_CMD} --version + RESULT_VARIABLE return_code + OUTPUT_VARIABLE CONAN_VERSION_OUTPUT + ERROR_VARIABLE CONAN_VERSION_OUTPUT) + + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan --version failed='${return_code}'") + endif() + + if(NOT CONAN_DETECT_QUIET) + string(STRIP "${CONAN_VERSION_OUTPUT}" _CONAN_VERSION_OUTPUT) + message(STATUS "Conan: Version found ${_CONAN_VERSION_OUTPUT}") + endif() + + if(DEFINED CONAN_VERSION) + string(REGEX MATCH ".*Conan version ([0-9]+\\.[0-9]+\\.[0-9]+)" FOO + "${CONAN_VERSION_OUTPUT}") + if(${CMAKE_MATCH_1} VERSION_LESS ${CONAN_VERSION}) + message(FATAL_ERROR "Conan outdated. Installed: ${CMAKE_MATCH_1}, \ + required: ${CONAN_VERSION}. Consider updating via 'pip \ + install conan==${CONAN_VERSION}'.") + endif() + endif() +endmacro() + +function(conan_add_remote) + # Adds a remote + # Arguments URL and NAME are required, INDEX, COMMAND and VERIFY_SSL are optional + # Example usage: + # conan_add_remote(NAME bincrafters INDEX 1 + # URL https://api.bintray.com/conan/bincrafters/public-conan + # VERIFY_SSL True) + set(oneValueArgs URL NAME INDEX COMMAND VERIFY_SSL) + cmake_parse_arguments(CONAN "" "${oneValueArgs}" "" ${ARGN}) + + if(DEFINED CONAN_INDEX) + set(CONAN_INDEX_ARG "-i ${CONAN_INDEX}") + endif() + if(DEFINED CONAN_COMMAND) + set(CONAN_CMD ${CONAN_COMMAND}) + else() + conan_check(REQUIRED DETECT_QUIET) + endif() + set(CONAN_VERIFY_SSL_ARG "True") + if(DEFINED CONAN_VERIFY_SSL) + set(CONAN_VERIFY_SSL_ARG ${CONAN_VERIFY_SSL}) + endif() + message(STATUS "Conan: Adding ${CONAN_NAME} remote repository (${CONAN_URL}) verify ssl (${CONAN_VERIFY_SSL_ARG})") + execute_process(COMMAND ${CONAN_CMD} remote add ${CONAN_NAME} ${CONAN_INDEX_ARG} -f ${CONAN_URL} ${CONAN_VERIFY_SSL_ARG} + RESULT_VARIABLE return_code) + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan remote failed='${return_code}'") + endif() +endfunction() + +macro(conan_config_install) + # install a full configuration from a local or remote zip file + # Argument ITEM is required, arguments TYPE, SOURCE, TARGET and VERIFY_SSL are optional + # Example usage: + # conan_config_install(ITEM https://github.com/conan-io/cmake-conan.git + # TYPE git SOURCE source-folder TARGET target-folder VERIFY_SSL false) + set(oneValueArgs ITEM TYPE SOURCE TARGET VERIFY_SSL) + set(multiValueArgs ARGS) + cmake_parse_arguments(CONAN "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + find_program(CONAN_CMD conan) + if(NOT CONAN_CMD AND CONAN_REQUIRED) + message(FATAL_ERROR "Conan executable not found!") + endif() + + if(DEFINED CONAN_VERIFY_SSL) + set(CONAN_VERIFY_SSL_ARG "--verify-ssl=${CONAN_VERIFY_SSL}") + endif() + + if(DEFINED CONAN_TYPE) + set(CONAN_TYPE_ARG "--type=${CONAN_TYPE}") + endif() + + if(DEFINED CONAN_ARGS) + set(CONAN_ARGS_ARGS "--args=\"${CONAN_ARGS}\"") + endif() + + if(DEFINED CONAN_SOURCE) + set(CONAN_SOURCE_ARGS "--source-folder=${CONAN_SOURCE}") + endif() + + if(DEFINED CONAN_TARGET) + set(CONAN_TARGET_ARGS "--target-folder=${CONAN_TARGET}") + endif() + + set (CONAN_CONFIG_INSTALL_ARGS ${CONAN_VERIFY_SSL_ARG} + ${CONAN_TYPE_ARG} + ${CONAN_ARGS_ARGS} + ${CONAN_SOURCE_ARGS} + ${CONAN_TARGET_ARGS}) + + message(STATUS "Conan: Installing config from ${CONAN_ITEM}") + execute_process(COMMAND ${CONAN_CMD} config install ${CONAN_ITEM} ${CONAN_CONFIG_INSTALL_ARGS} + RESULT_VARIABLE return_code) + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan config failed='${return_code}'") + endif() +endmacro() diff --git a/CMakeLists.txt b/CMakeLists.txt index b0db019..dacc4b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ set(CMAKE_CXX_STANDARD 20) # initialize conan libs include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup(NO_OUTPUT_DIRS KEEP_RPATHS) - set(CMAKE_INCLUDE_CURRENT_DIR ON) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake) @@ -67,6 +66,12 @@ endif() add_subdirectory(Simple-Web-Server) include_directories(Simple-Web-Server) +add_subdirectory(Simple-WebSocket-Server) +include_directories(Simple-WebSocket-Server) + +add_subdirectory(AshDB) +include_directories(AshDB/include) + add_subdirectory(src) if (BUILD_ASH_TESTS) diff --git a/README.md b/README.md index 8ca9315..d177ef0 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ To create a `.sln` file, run the following commands from the root source directo mkdir build cd build conan install .. -s build_type=Debug --build missing -cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_ASH_TESTS=On +cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_ASH_TESTS=On -DASHDB_BUILD_CONAN=Off ``` ### macos @@ -58,7 +58,7 @@ This project has been compiled on macos 10.15 using Apple clang 11.0. I have bee mkdir build cd build conan install .. -s build_type=Debug --build missing -cmake .. "-GNinja" -DCMAKE_BUILD_TYPE=Debug -DBUILD_ASH_TESTS=On +cmake .. "-GNinja" -DCMAKE_BUILD_TYPE=Debug -DBUILD_ASH_TESTS=On -DASHDB_BUILD_CONAN=Off ``` If there are no errors then the executable can be built with: diff --git a/src/Block.cpp b/src/Block.cpp index 9927be1..8dffe85 100644 --- a/src/Block.cpp +++ b/src/Block.cpp @@ -40,6 +40,22 @@ void from_json(const nl::json& j, Block& b) BlockTime{std::chrono::milliseconds{j["time"].get()}}; } +void to_json(nl::json& j, const BlockList& b) +{ + for (const auto& block : b) + { + j.push_back(block); + } +} + +void from_json(const nl::json& j, BlockList& b) +{ + for (const auto& jblock : j.items()) + { + b.push_back(jblock.value().get()); + } +} + bool ValidHash(const Block& block) { const auto computedHash = CalculateBlockHash(block); @@ -106,7 +122,6 @@ std::string CalculateBlockHash(const Block& block) } Block::Block(std::uint64_t index, std::string_view prevHash, Transactions&& txs) - : _logger(ash::initializeLogger("Block")) { _hashed._index = index; _hashed._nonce = 0; diff --git a/src/Block.h b/src/Block.h index 04d9b6f..593acee 100644 --- a/src/Block.h +++ b/src/Block.h @@ -17,10 +17,14 @@ namespace ash class Block; using BlockSharedPtr = std::shared_ptr; using BlockUniquePtr = std::unique_ptr; +using BlockList = std::vector; void to_json(nl::json& j, const Block& b); void from_json(const nl::json& j, Block& b); +void to_json(nl::json& j, const BlockList& b); +void from_json(const nl::json& j, BlockList& b); + bool ValidHash(const Block& block); bool ValidNewBlock(const Block& block, const Block& prevblock); @@ -36,13 +40,18 @@ std::string CalculateBlockHash( class Block { - friend void read_block(std::istream& stream, Block& block); - friend void write_block(std::ostream& stream, const Block& block); + friend void ashdb_read(std::istream& stream, Block& block); friend void from_json(const nl::json& j, Block& b); friend class Miner; public: Block() = default; + Block(std::uint64_t index, std::string_view prevHash) + : Block(index, prevHash, {}) + { + // nothing to do + } + Block(std::uint64_t index, std::string_view prevHash, Transactions&& tx); bool operator==(const Block& other) const; @@ -62,10 +71,20 @@ class Block std::string previousHash() const { return _hashed._prev; } const Transactions& transactions() const { return _hashed._txs; } - Transactions& transactions() + void add_transaction(Transaction&& tx) { - return const_cast( - (static_cast(this))->transactions()); + _hashed._txs.push_back(std::move(tx)); + } + + [[maybe_unused]] bool update_transaction(std::size_t index, const Transaction& tx) + { + if (index >= _hashed._txs.size()) + { + return false; + } + + _hashed._txs.at(index) = tx; + return true; } std::string hash() const { return _hash; } @@ -97,7 +116,6 @@ class Block std::string _hash; std::string _miner; - SpdLogPtr _logger; }; } // namespace ash diff --git a/src/Blockchain.cpp b/src/Blockchain.cpp index bbfe943..36b223d 100644 --- a/src/Blockchain.cpp +++ b/src/Blockchain.cpp @@ -8,6 +8,7 @@ #include "CryptoUtils.h" #include "Blockchain.h" +#include "ChainDatabase.h" namespace nl = nlohmann; @@ -16,16 +17,18 @@ namespace ash void to_json(nl::json& j, const Blockchain& b) { - for (const auto& block : b._blocks) - { - j.push_back(block); - } + // for (const auto& block : b._blocks) + // { + // j.push_back(block); + // } + // for (const auto& block : this->_db) + // { + + // } } void from_json(const nl::json& j, Blockchain& b) { - b.clear(); - for (const auto& jblock : j.items()) { b._blocks.push_back(jblock.value().get()); @@ -68,6 +71,13 @@ void from_json(const nlohmann::json& j, AddressLedger& ledger) } } +void to_json(nl::json& j, const ChainSummary& summary) +{ + j["first"] = summary.first; + j["last"] = summary.last; + j["cummdiff"] = summary.cumdiff; +} + UnspentTxOuts GetUnspentTxOuts(const Blockchain& chain, const std::string& address) { auto cmp = @@ -78,55 +88,55 @@ UnspentTxOuts GetUnspentTxOuts(const Blockchain& chain, const std::string& addre std::set outs(cmp); - for (const auto& block : chain) - { - for (const auto& txitem : block.transactions() | boost::adaptors::indexed()) - { - const auto& tx = txitem.value(); - - assert(tx.txIns().size() > 0); - assert(tx.txOuts().size() > 0); - - for (const auto& txoutitem : tx.txOuts() | boost::adaptors::indexed()) - { - const auto& txout = txoutitem.value(); - if (address.size() > 0 && txout.address() != address) - { - continue; - } - - outs.insert({ - block.index(), - static_cast(txitem.index()), - static_cast(txoutitem.index()), - txout.address(), - txout.amount()}); - } - - // if this is a coinbase transaction then we do not need - // to process the TxIns - if (tx.isCoinbase()) - { - continue; - } - - for (const auto& txin : tx.txIns()) - { - auto it = std::find_if(outs.begin(), outs.end(), - [txin = txin](const UnspentTxOut& utxout) - { - return (txin.txOutPt().blockIndex == utxout.blockIndex) - && (txin.txOutPt().txIndex == utxout.txIndex) - && (txin.txOutPt().txOutIndex == utxout.txOutIndex); - }); - - if (it != outs.end()) - { - outs.erase(it); - } - } - } - } + // for (const auto& block : chain) + // { + // for (const auto& txitem : block.transactions() | boost::adaptors::indexed()) + // { + // const auto& tx = txitem.value(); + + // assert(tx.txIns().size() > 0); + // assert(tx.txOuts().size() > 0); + + // for (const auto& txoutitem : tx.txOuts() | boost::adaptors::indexed()) + // { + // const auto& txout = txoutitem.value(); + // if (address.size() > 0 && txout.address() != address) + // { + // continue; + // } + + // outs.insert({ + // block.index(), + // static_cast(txitem.index()), + // static_cast(txoutitem.index()), + // txout.address(), + // txout.amount()}); + // } + + // // if this is a coinbase transaction then we do not need + // // to process the TxIns + // if (tx.isCoinbase()) + // { + // continue; + // } + + // for (const auto& txin : tx.txIns()) + // { + // auto it = std::find_if(outs.begin(), outs.end(), + // [txin = txin](const UnspentTxOut& utxout) + // { + // return (txin.txOutPt().blockIndex == utxout.blockIndex) + // && (txin.txOutPt().txIndex == utxout.txIndex) + // && (txin.txOutPt().txOutIndex == utxout.txOutIndex); + // }); + + // if (it != outs.end()) + // { + // outs.erase(it); + // } + // } + // } + // } UnspentTxOuts retval; std::move(outs.begin(), outs.end(), std::back_inserter(retval)); @@ -138,47 +148,47 @@ AddressLedger GetAddressLedger(const Blockchain& chain, const std::string& addre { ash::AddressLedger ledger; - for (const auto& block : chain) - { - const auto fullblock = ash::GetBlockDetails(chain, block.index()); - for (const auto& tx : fullblock.transactions()) - { - bool debit = false; - assert(tx.txOuts().size() > 0); + // for (const auto& block : chain) + // { + // const auto fullblock = ash::GetBlockDetails(chain, block.index()); + // for (const auto& tx : fullblock.transactions()) + // { + // bool debit = false; + // assert(tx.txOuts().size() > 0); - for (const auto& txout : tx.txOuts()) - { - if (txout.address() == address) - { - ledger.push_back( - LedgerInfo{ block.index(), tx.id(), block.time(), txout.amount() }); - - break; - } - } - - // this is a debit for the address so pop the last ledger entry, accumulate - // the total of all the TxIns and put it back to the ledger - assert(tx.txIns().size() > 0); - assert(tx.txIns().at(0).txOutPt().address.has_value()); - - if (!tx.isCoinbase() - && *(tx.txIns().at(0).txOutPt().address) == address) - { - double txInTotal = std::accumulate( - tx.txIns().begin(), tx.txIns().end(), 0.0, - [](auto accum, const ash::TxIn& txin) - { - return accum + *(txin.txOutPt().amount); - }); - - auto amount = txInTotal - ledger.back().amount; - ledger.pop_back(); - ledger.push_back( - LedgerInfo{ block.index(), tx.id(), block.time(), amount * -1.0 }); - } - } - } + // for (const auto& txout : tx.txOuts()) + // { + // if (txout.address() == address) + // { + // ledger.push_back( + // LedgerInfo{ block.index(), tx.id(), block.time(), txout.amount() }); + + // break; + // } + // } + + // // this is a debit for the address so pop the last ledger entry, accumulate + // // the total of all the TxIns and put it back to the ledger + // assert(tx.txIns().size() > 0); + // assert(tx.txIns().at(0).txOutPt().address.has_value()); + + // if (!tx.isCoinbase() + // && *(tx.txIns().at(0).txOutPt().address) == address) + // { + // double txInTotal = std::accumulate( + // tx.txIns().begin(), tx.txIns().end(), 0.0, + // [](auto accum, const ash::TxIn& txin) + // { + // return accum + *(txin.txOutPt().amount); + // }); + + // auto amount = txInTotal - ledger.back().amount; + // ledger.pop_back(); + // ledger.push_back( + // LedgerInfo{ block.index(), tx.id(), block.time(), amount * -1.0 }); + // } + // } + // } return ledger; } @@ -257,17 +267,17 @@ std::tuple CreateTransaction(Blockchain& chain, std: // perhaps with a persisted index or something std::optional FindTransaction(const Blockchain& chain, std::string_view txid) { - for (const auto& block: chain) - { - for (const auto& txitem : block.transactions() | boost::adaptors::indexed()) - { - const auto& tx = txitem.value(); - if (tx.id() == txid) - { - return TxPoint{ block.index(), txitem.index() }; - } - } - } + // for (const auto& block: chain) + // { + // for (const auto& txitem : block.transactions() | boost::adaptors::indexed()) + // { + // const auto& tx = txitem.value(); + // if (tx.id() == txid) + // { + // return TxPoint{ block.index(), txitem.index() }; + // } + // } + // } return {}; } @@ -279,8 +289,11 @@ Block GetBlockDetails(const Blockchain& chain, std::size_t index) // loops through the transactions of the block we're // interested in - for (auto& tx : retblock.transactions()) + for (auto transactions = retblock.transactions(); + auto item : transactions | boost::adaptors::indexed()) { + auto tx = item.value(); + // for each TxIn of the block we want to fill in // some missing information for (auto& txin : tx.txIns()) @@ -297,16 +310,48 @@ Block GetBlockDetails(const Blockchain& chain, std::size_t index) txin.txOutPt().address = txout.address(); txin.txOutPt().amount = txout.amount(); } + + retblock.update_transaction(item.index(), tx); } return retblock; } +bool ValidBlockchain(const BlockList& blocklist) +{ + if (blocklist.size() == 0 || blocklist.size() == 1) + { + return true; + } + + for (auto idx = 1u; idx < blocklist.size(); ++idx) + { + const auto& current = blocklist.at(idx); + const auto& prev = blocklist.at(idx - 1); + + if ((current.index() != prev.index() + 1) + || (current.previousHash() != prev.hash()) + || (CalculateBlockHash(current) != current.hash())) + { + return false; + } + } + + return true; +} + //*** Blockchain -Blockchain::Blockchain() - : _logger(ash::initializeLogger("Blockchain")) +Blockchain::Blockchain(IChainDatabasePtr ptr) + : _db(std::move(ptr)), + _logger(ash::initializeLogger("Blockchain")) +{ + _db->initialize(*this); +} + +auto Blockchain::begin() const { - // nothing to do + return BlockList::const_iterator{}; + // return _db->read(0); } bool Blockchain::addNewBlock(const Block& block) @@ -327,6 +372,7 @@ bool Blockchain::addNewBlock(const Block& block, bool checkPreviousBlock) } _blocks.push_back(block); + _db->write(block); return true; } @@ -453,4 +499,25 @@ BlockUniquePtr Blockchain::createUnminedBlock(const std::string& coinbasewallet) return std::make_unique(newblockidx, this->back().hash(), std::move(txs)); } +std::size_t Blockchain::size() const +{ + return _db->size(); +} + +void Blockchain::replace_blocks(const BlockList& block) +{ + throw std::runtime_error("replace_blocks not implemented"); +} + +void Blockchain::initialize(Blockchain::GenesisCallback gcb) +{ + assert(_db->opened()); + if (_db->size()==0) + { + Block block{ gcb() }; + _db->write(block); + _logger->info("created gensis block with data '{}'", block.data()); + } +} + } // namespace \ No newline at end of file diff --git a/src/Blockchain.h b/src/Blockchain.h index ac54327..bfe68fe 100644 --- a/src/Blockchain.h +++ b/src/Blockchain.h @@ -8,6 +8,7 @@ #include "Settings.h" #include "Block.h" #include "AshLogger.h" +//#include "AshChainDatabase.h" namespace ash { @@ -23,6 +24,9 @@ constexpr auto BLOCK_INTERVAL = 10u; // in blocks class Blockchain; using BlockChainPtr = std::unique_ptr; +class IChainDatabase; +using IChainDatabasePtr = std::unique_ptr; + struct LedgerInfo; using AddressLedger = std::vector; @@ -51,6 +55,8 @@ std::optional FindTransaction(const Blockchain& chain, std::string_view // fills in the TxIn TxPoint info for all the Transactions in the Block Block GetBlockDetails(const Blockchain& chain, std::size_t index); +bool ValidBlockchain(const BlockList& blocklist); + struct LedgerInfo { std::uint64_t blockIdx; @@ -70,36 +76,50 @@ struct LedgerInfo } }; -//! This class is not thread safe and assumes that the -// client handles synchronization +struct ChainSummary +{ + std::uint64_t first = 0; + std::uint64_t last = 0; + std::uint64_t cumdiff = 0; +}; + +//! This class is not thread safe and assumes that the client +//! handles synchronization class Blockchain final { - std::vector _blocks; + IChainDatabasePtr _db; + BlockList _blocks; // TODO: refactor away UnspentTxOuts _unspentTxOuts; - std::queue _txQueue; // transactions waiting to be mined by this miner + std::queue _txQueue; // transactions waiting to be mined by this miner SpdLogPtr _logger; - friend class ChainDatabase; + friend class AshChainDatabase; friend void to_json(nl::json& j, const Blockchain& b); friend void from_json(const nl::json& j, Blockchain& b); public: - using iterator = std::vector::iterator; + using GenesisCallback = std::function; +// using iterator = std::vector::iterator; - Blockchain(); + Blockchain(IChainDatabasePtr ptr); - Blockchain(Blockchain&&) = default; - Blockchain& operator=(const Blockchain&) = default; - - auto begin() const -> decltype(_blocks.begin()) - { - return _blocks.begin(); - } + Blockchain(Blockchain&&) = delete; + Blockchain& operator=(const Blockchain&) = delete; - auto end() const -> decltype(_blocks.end()) - { - return _blocks.end(); - } + void initialize(GenesisCallback gcb); + + // TODO: everything below this is fair game to be refactored out! +// auto begin() const +// { +// return _db->read(0); +// } + +// auto begin() const -> decltype(_blocks.begin()) +// { +// return _blocks.begin(); +// } + auto begin() const; + auto end() const; auto rbegin() const -> decltype(_blocks.rbegin()) { @@ -121,19 +141,11 @@ class Blockchain final return _blocks.back(); } - std::size_t size() const - { - return _blocks.size(); - } - - void clear() - { - _blocks.clear(); - } + std::size_t size() const; void resize(std::size_t size) { - _blocks.resize(size); + throw std::runtime_error("Blockchain::resize() not implemented yet"); } auto at(std::size_t index) const -> decltype(_blocks.at(index)) @@ -148,12 +160,6 @@ class Blockchain final return at(blockIndex).transactions().at(txIndex); } - auto& txAt(std::size_t blockIndex, std::size_t txIndex) - { - return const_cast( - static_cast(*this).txAt(blockIndex,txIndex)); - } - bool addNewBlock(const Block& block); bool addNewBlock(const Block& block, bool checkPreviousBlock); BlockUniquePtr createUnminedBlock(const std::string& coinbasewallet); @@ -168,6 +174,8 @@ class Blockchain final void queueTransaction(Transaction&& tx); std::size_t transactionQueueSize() const noexcept; std::size_t reQueueTransactions(Block& block); + + void replace_blocks(const BlockList& block); }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b868f6..d9575b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -74,6 +74,7 @@ add_executable(ash target_link_libraries(ash PUBLIC simple-web-server + AshDBLib ${CONAN_LIBS} ${COREFOUNDATION_LIBRARY} ${CARBON_LIBRARY} diff --git a/src/ChainDatabase.cpp b/src/ChainDatabase.cpp index a9a6d1c..768c40d 100644 --- a/src/ChainDatabase.cpp +++ b/src/ChainDatabase.cpp @@ -1,3 +1,10 @@ +#include + +#include + +#include +#include + #include "Transactions.h" #include "Blockchain.h" #include "ChainDatabase.h" @@ -5,170 +12,13 @@ namespace ash { -void write_data(std::ostream& stream, const TxOutPoint& pt) -{ - ash::db::write_data(stream, pt.blockIndex); - ash::db::write_data(stream, pt.txIndex); - ash::db::write_data(stream, pt.txOutIndex); -} - -void write_data(std::ostream& stream, const TxIn& tx) +AshChainDatabase::AshChainDatabase(std::string_view folder) + : _path{ boost::filesystem::path { folder.data()} }, + _logger(ash::initializeLogger("AshChainDatabase")) { - ash::write_data(stream, tx.txOutPt()); - ash::db::write_data(stream, tx.signature()); } -void write_data(std::ostream& stream, const TxOut& tx) -{ - ash::db::write_data(stream, tx.address()); - ash::db::write_data(stream, tx.amount()); -} - -void write_data(std::ostream& stream, const Transaction& tx) -{ - ash::db::write_data(stream, tx.id()); - - { - const auto txins = tx.txIns(); - auto txinsize = static_cast(txins.size()); - ash::db::write_data(stream, txinsize); - for (const auto& txin : txins) - { - write_data(stream, txin); - } - } - - { - const auto txouts = tx.txOuts(); - auto txoutsize = static_cast(txouts.size()); - ash::db::write_data(stream, txoutsize); - for (const auto& txout : txouts) - { - write_data(stream, txout); - } - } -} - -void write_block(std::ostream& stream, const Block& block) -{ - ash::db::write_data(stream, block.index()); - ash::db::write_data(stream, block.nonce()); - ash::db::write_data(stream, block.difficulty()); - ash::db::write_data(stream, block.data()); - - std::uint64_t dtime = - static_cast(block.time().time_since_epoch().count()); - ash::db::write_data(stream, dtime); - - ash::db::write_data(stream, block.hash()); - ash::db::write_data(stream, block.previousHash()); - ash::db::write_data(stream, block.miner()); - - const auto& txs = block.transactions(); - auto txsize = static_cast(txs.size()); - ash::db::write_data(stream, txsize); - - for (const auto& tx : txs) - { - write_data(stream, tx); - } -} - -void read_data(std::istream& stream, TxOutPoint& pt) -{ - ash::db::read_data(stream, pt.blockIndex); - ash::db::read_data(stream, pt.txIndex); - ash::db::read_data(stream, pt.txOutIndex); -} - -void read_data(std::istream& stream, TxIn& txin) -{ - ash::read_data(stream, txin.txOutPt()); - ash::db::read_data(stream, txin._signature); -} - -void read_data(std::istream& stream, TxOut& txout) -{ - ash::db::read_data(stream, txout._address); - ash::db::read_data(stream, txout._amount); -} - -void read_data(std::istream& stream, Transaction& tx) -{ - ash::db::read_data(stream, tx._id); - - { - ash::db::StrLenType txincount; - ash::db::read_data(stream, txincount); - auto& txins = tx.txIns(); - for (ash::db::StrLenType x = 0; x < txincount; x++) - { - TxIn txin; - read_data(stream, txin); - txins.push_back(txin); - } - } - - { - ash::db::StrLenType txoutcount; - ash::db::read_data(stream, txoutcount); - auto& txouts = tx.txOuts(); - for (ash::db::StrLenType x = 0; x < txoutcount; x++) - { - TxOut txout; - read_data(stream, txout); - txouts.push_back(txout); - } - } -} - -void read_block(std::istream& stream, Block& block) -{ - ash::db::read_data(stream, block._hashed._index); - ash::db::read_data(stream, block._hashed._nonce); - ash::db::read_data(stream, block._hashed._difficulty); - ash::db::read_data(stream, block._hashed._data); - - std::uint64_t dtime; - ash::db::read_data(stream, dtime); - block._hashed._time = - BlockTime{std::chrono::milliseconds{dtime}}; - - ash::db::read_data(stream, block._hash); - ash::db::read_data(stream, block._hashed._prev); - ash::db::read_data(stream, block._miner); - - auto& txs = block.transactions(); - auto txcount = static_cast(txs.size()); - ash::db::read_data(stream, txcount); - - for (ash::db::StrLenType x = 0; x < txcount; x++) - { - Transaction tx; - read_data(stream, tx); - txs.push_back(tx); - } -} - -constexpr std::string_view DatabaseFile = "chain.ashdb"; - -ChainDatabase::ChainDatabase(std::string_view folder) - : _folder{ folder }, - _path{ boost::filesystem::path { _folder.data()} }, - _dbfile { _path / DatabaseFile.data()}, - _logger(ash::initializeLogger("ChainDatabase")) -{ -} - -ChainDatabase::~ChainDatabase() -{ - if (_txIndex) - { - delete _txIndex; - } -} - -void ChainDatabase::initialize(Blockchain& blockchain, GenesisCallback gcb) +void AshChainDatabase::initialize(Blockchain& blockchain) { if (!boost::filesystem::exists(_path)) { @@ -176,64 +26,47 @@ void ChainDatabase::initialize(Blockchain& blockchain, GenesisCallback gcb) boost::filesystem::create_directories(_path); } - if (!boost::filesystem::exists(_dbfile)) + _logger->debug("opening blockchain database"); + ashdb::Options options; + options.filesize_max = 1024 * 1024 * 5; // five megs + options.prefix = "blocks"; + options.extension = "dat"; + _db = std::make_unique(_path.string(), options); + + if (_db->open() != ashdb::OpenStatus::OK) { - _logger->warn("creating genesis block, starting new chain"); - assert(gcb); - write(gcb()); + throw std::logic_error("could not open blockchain database"); } - _logger->info("loading blockchain from {}", _dbfile.string()); + _logger->info("loading blockchain from {}", _path.string()); - std::ifstream ifs(_dbfile.c_str(), std::ios_base::binary); - while (ifs.peek() != EOF) - { - Block block; - read_block(ifs, block); - blockchain._blocks.push_back(block); - } if (!blockchain.isValidChain()) { throw std::logic_error("invalid chain"); } - boost::filesystem::path txidx { _path / "txinindx" }; - leveldb::Options options; - options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, txidx.string(), &_txIndex); - if (!status.ok()) - { - throw std::logic_error(fmt::format("could not open txin index: {}", status.ToString())); - } - _logger->info("loaded {} blocks from saved chain", blockchain.size()); } -void ChainDatabase::write(const Block& block) +void AshChainDatabase::write(const Block& block) { - std::ofstream ofs(_dbfile.c_str(), std::ios::app | std::ios::out | std::ios::binary); - write_block(ofs, block); + _db->write(block); } -void ChainDatabase::writeChain(const Blockchain& chain) +void AshChainDatabase::writeChain(const Blockchain& chain) { - _logger->debug("writing {} blocks to file {}", chain.size(), _dbfile.string()); - std::ofstream ofs(_dbfile.c_str(), std::ios::app | std::ios::out | std::ios::binary); - for (const auto& block : chain) - { - write_block(ofs, block); - } +// _logger->debug("writing {} blocks to file {}", chain.size(), _dbfile.string()); +// std::ofstream ofs(_dbfile.c_str(), std::ios::app | std::ios::out | std::ios::binary); +// for (const auto& block : chain) +// { +// write_block(ofs, block); +// } } -void ChainDatabase::reset() +std::optional AshChainDatabase::read(std::size_t index) const { - _logger->debug("deleting datbase file {}", _dbfile.string()); - - if (boost::filesystem::exists(_dbfile)) - { - boost::filesystem::remove(_dbfile); - } + return _db->read(index); } } // namespace diff --git a/src/ChainDatabase.h b/src/ChainDatabase.h index afd7908..d348c59 100644 --- a/src/ChainDatabase.h +++ b/src/ChainDatabase.h @@ -1,95 +1,228 @@ #pragma once #include #include +#include +#include +#include #include +#include + #include +#include #include "Block.h" +#include "Blockchain.h" #include "AshLogger.h" namespace ash { -namespace db +inline void write_data(std::ostream& stream, const TxOutPoint& pt) { + ashdb::ashdb_write(stream, pt.blockIndex); + ashdb::ashdb_write(stream, pt.txIndex); + ashdb::ashdb_write(stream, pt.txOutIndex); +} -using LevelDBPtr = std::unique_ptr; +inline void write_txin(std::ostream& stream, const TxIn& tx) +{ + ash::write_data(stream, tx.txOutPt()); + ashdb::ashdb_write(stream, tx.signature()); +} -using ValueType = unsigned char; -using Container = std::vector; -using PointerType = char*; -using StrLenType = std::uint32_t; +inline void write_txout(std::ostream& stream, const TxOut& tx) +{ + ashdb::ashdb_write(stream, tx.address()); + ashdb::ashdb_write(stream, tx.amount()); +} -template ::value)>::type> -inline void write_data(std::ostream& stream, T value) +inline void write_tx(std::ostream& stream, const Transaction& tx) { - stream.write(reinterpret_cast(&value), sizeof(value)); + ashdb::ashdb_write(stream, tx.id()); + + { + const auto txins = tx.txIns(); + auto txinsize = static_cast(txins.size()); + ashdb::ashdb_write(stream, txinsize); + for (const auto& txin : txins) + { + write_txin(stream, txin); + } + } + + { + const auto txouts = tx.txOuts(); + auto txoutsize = static_cast(txouts.size()); + ashdb::ashdb_write(stream, txoutsize); + for (const auto& txout : txouts) + { + write_txout(stream, txout); + } + } } -inline void write_data(std::ostream& stream, double val) +inline void ashdb_write(std::ostream& stream, const Block& block) { - stream.write(reinterpret_cast(&val), sizeof(double)); + ashdb::ashdb_write(stream, block.index()); + ashdb::ashdb_write(stream, block.nonce()); + ashdb::ashdb_write(stream, block.difficulty()); + ashdb::ashdb_write(stream, block.data()); + + std::uint64_t dtime = + static_cast(block.time().time_since_epoch().count()); + ashdb::ashdb_write(stream, dtime); + + ashdb::ashdb_write(stream, block.hash()); + ashdb::ashdb_write(stream, block.previousHash()); + ashdb::ashdb_write(stream, block.miner()); + + const auto& txs = block.transactions(); + const auto txsize = static_cast(txs.size()); + ashdb::ashdb_write(stream, txsize); + + for (const auto& tx : txs) + { + write_tx(stream, tx); + } } -inline void write_data(std::ostream& stream, std::string_view data) +inline void read_data(std::istream& stream, TxOutPoint& pt) { - auto size = static_cast(data.size()); - write_data(stream, size); - stream.write(data.data(), size); + ashdb::ashdb_read(stream, pt.blockIndex); + ashdb::ashdb_read(stream, pt.txIndex); + ashdb::ashdb_read(stream, pt.txOutIndex); } -template::value)>::type> -inline void read_data(std::istream& stream, T& value) +inline void read_txin(std::istream& stream, TxIn& txin) { - stream.read(reinterpret_cast(&value), sizeof(value)); + ash::read_data(stream, txin.txOutPt()); + ashdb::ashdb_read(stream, txin._signature); } -inline void read_data(std::istream& stream, double& val) +inline void read_txout(std::istream& stream, TxOut& txout) { - stream.read(reinterpret_cast(&val), sizeof(double)); + ashdb::ashdb_read(stream, txout._address); + ashdb::ashdb_read(stream, txout._amount); } -inline void read_data(std::istream& stream, std::string& data) +inline void read_tx(std::istream& stream, Transaction& tx) { - StrLenType len; - read_data(stream, len); + ashdb::ashdb_read(stream, tx._id); + + { + std::uint64_t txincount; + ashdb::ashdb_read(stream, txincount); + auto& txins = tx.txIns(); + for (std::uint64_t x = 0; x < txincount; x++) + { + TxIn txin; + read_txin(stream, txin); + txins.push_back(txin); + } + } + + { + std::uint64_t txoutcount; + ashdb::ashdb_read(stream, txoutcount); + auto& txouts = tx.txOuts(); + for (std::uint64_t x = 0; x < txoutcount; x++) + { + TxOut txout; + read_txout(stream, txout); + txouts.push_back(txout); + } + } +} - data.resize(len); - stream.read(reinterpret_cast(data.data()), len); +inline void ashdb_read(std::istream& stream, Block& block) +{ + ashdb::ashdb_read(stream, block._hashed._index); + ashdb::ashdb_read(stream, block._hashed._nonce); + ashdb::ashdb_read(stream, block._hashed._difficulty); + ashdb::ashdb_read(stream, block._hashed._data); + + std::uint64_t dtime; + ashdb::ashdb_read(stream, dtime); + block._hashed._time = + BlockTime{std::chrono::milliseconds{dtime}}; + + ashdb::ashdb_read(stream, block._hash); + ashdb::ashdb_read(stream, block._hashed._prev); + ashdb::ashdb_read(stream, block._miner); + + auto& txs = block.transactions(); + auto txcount = static_cast(txs.size()); + ashdb::ashdb_read(stream, txcount); + + for (std::uint64_t x = 0; x < txcount; x++) + { + Transaction tx; + read_tx(stream, tx); + block.add_transaction(std::move(tx)); + } } -} // namespace ash::db +class IChainDatabase; +using IChainDatabasePtr = std::unique_ptr; -class ChainDatabase; -using ChainDatabasePtr = std::unique_ptr; +class AshChainDatabase; +using ChainDatabasePtr = std::unique_ptr; -class ChainDatabase final +class IChainDatabase { public: + IChainDatabase() = default; + virtual ~IChainDatabase() = default; + + // TODO: this should not be in the base class using GenesisCallback = std::function; + virtual void initialize(Blockchain& chain) = 0; - ChainDatabase(std::string_view folder); - ~ChainDatabase(); + virtual bool opened() const = 0; - void write(const Block& block); - void writeChain(const Blockchain& chain); + virtual std::optional read(std::size_t index) const = 0; + virtual std::size_t size() = 0; - void initialize(Blockchain& chain, GenesisCallback gcb); - void reset(); + virtual void write(const Block& block) = 0; + virtual void writeChain(const Blockchain& chain) = 0; +}; + +class AshChainDatabase final : public IChainDatabase +{ + +public: + using DBType = ashdb::AshDB; + + AshChainDatabase(std::string_view folder); + ~AshChainDatabase() override = default; + + void write(const Block& block) override; + void writeChain(const Blockchain& chain) override; + + void initialize(Blockchain& chain) override; + + std::optional read(std::size_t index) const override; + + std::size_t size() override + { + return static_cast(_db->size()); + } + + bool opened() const override + { + return _db->opened(); + } private: - std::string _folder; + std::uint64_t _size = 0; + + std::unique_ptr _db; + // TODO: fair game to be refactored boost::filesystem::path _path; - boost::filesystem::path _dbfile; - // ash::db::LevelDBPtr _txInIndex; - leveldb::DB* _txIndex = nullptr; - SpdLogPtr _logger; }; diff --git a/src/IChainDatabase.h b/src/IChainDatabase.h new file mode 100644 index 0000000..b38d722 --- /dev/null +++ b/src/IChainDatabase.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include + + +namespace ash +{ + +class IChainDatabase +{ + std::string _datafolder; + +public: + using GenesisCallback = std::function; + + IChainDatabase(const std::string& datafolder) + : _datafolder(datafolder) + { + // nothing to do + } + + virtual ~IChainDatabase() = default; + + std::string datafolder() const { return _datafolder; } + + virtual void initialize(Blockchain& chain) = 0; + virtual bool opened() const = 0; + + virtual std::optional read(std::size_t index) const = 0; + virtual std::size_t size() = 0; + + virtual void write(const Block& block) = 0; + virtual void writeChain(const Blockchain& chain) = 0; +}; + +} // namespace diff --git a/src/MinerApp.cpp b/src/MinerApp.cpp index 27a9018..071587c 100644 --- a/src/MinerApp.cpp +++ b/src/MinerApp.cpp @@ -49,8 +49,8 @@ MinerApp::MinerApp(SettingsPtr settings) _logger->debug("target block generation interval is {} seconds", TARGET_TIMESPAN); _logger->debug("difficulty adjustment interval is every {} blocks", BLOCK_INTERVAL); - _blockchain = std::make_unique(); - _database = std::make_unique(dbfolder); + auto db = std::make_unique(dbfolder); + _blockchain = std::make_unique(std::move(db)); } MinerApp::~MinerApp() @@ -631,31 +631,27 @@ void MinerApp::run() return; } - initHttp(); - initWebSocket(); - initPeers(); - - auto genesisBlockCallback = + auto genesisBlockCallback = [this]() -> Block { - Transactions txs; + ash::Transactions txs; txs.push_back(ash::CreateCoinbaseTransaction(0, _rewardAddress)); Block gen{ 0, "", std::move(txs) }; gen.setMiner(this->_uuid); std::time_t t = std::time(nullptr); - const auto gendata = - fmt::format("Gensis Block created {:%Y-%m-%d %H:%M:%S %Z}", *std::localtime(&t)); + const auto gendata = + fmt::format("Genesis Block created {:%Y-%m-%d %H:%M:%S %Z}", *std::localtime(&t)); gen.setData(gendata); - _logger->debug("creating gensis block with data '{}'", gendata); - return gen; }; - // maybe it's ok if the blockchain has some concept of - // a persistence object? - _database->initialize(*_blockchain, genesisBlockCallback); + _blockchain->initialize(genesisBlockCallback); + + initHttp(); + initWebSocket(); + initPeers(); _httpThread = std::thread( [this]() @@ -694,9 +690,8 @@ void MinerApp::runMineThread() [this](std::uint64_t index) -> bool { std::lock_guard lock{_chainMutex}; - return !_tempchain - || _tempchain->size() == 0 - || _tempchain->back().index() < index; + return _tempblocklist.size() == 0 + || _tempblocklist.back().index() < index; }; while (!_miningDone && !_done) @@ -735,16 +730,14 @@ void MinerApp::runMineThread() // point on // append the block to the chain - if (!_blockchain->addNewBlock(*newblock)) + if (std::lock_guard lock{_chainMutex}; + !_blockchain->addNewBlock(*newblock)) { _logger->error("could not add new block #{} to blockchain, stopping mining", newblock->index()); _miningDone = true; break; } - // write the block to the database - _database->write(*newblock); - // see if there's an update waiting for the local // copy of the chain if (!syncBlockchain()) @@ -775,53 +768,57 @@ bool MinerApp::syncBlockchain() { bool retval = false; if (std::lock_guard lock{_chainMutex}; - _tempchain) + _tempblocklist.size() > 0) { - if (_tempchain->front().index() == 0) + if (_tempblocklist.front().index() == 0) { // we're replacing the full chain - _blockchain.swap(_tempchain); - _database->reset(); - _database->writeChain(*_blockchain); + _blockchain->replace_blocks(_tempblocklist); +// _database->reset(); +// _database->writeChain(*_blockchain); retval = true; } - else if (_tempchain->front().index() <= _blockchain->back().index()) + else if (_tempblocklist.front().index() <= _blockchain->back().index()) { - auto startIdx = _tempchain->front().index(); - _blockchain->resize(startIdx); - for (const auto& block : *_tempchain) - { - // add up until a point of failure (if there - // is one) - if (!_blockchain->addNewBlock(block)) - { - _logger->warn("failed to add block while updating chain at index"); - } - } - - _database->reset(); - _database->writeChain(*_blockchain); + _blockchain->replace_blocks(_tempblocklist); + // TODO: this can probably be refactored into `Blockchain::replace_blocks()` +// auto startIdx = _tempblocklist.front().index(); +// _blockchain->resize(startIdx); +// for (const auto& block : _tempblocklist) +// { +// // add up until a point of failure (if there +// // is one) +// if (!_blockchain->addNewBlock(block)) +// { +// _logger->warn("failed to add block while updating chain at index"); +// } +// } +// +// _database->reset(); +// _database->writeChain(*_blockchain); retval = true; } - else if (_tempchain->front().index() == _blockchain->back().index() + 1) + else if (_tempblocklist.front().index() == _blockchain->back().index() + 1) { - for (const auto& block : *_tempchain) - { - if (_blockchain->addNewBlock(block)) - { - _database->write(block); - } - } + // TODO: this can probably be refactored into `Blockchain::replace_blocks()` + _blockchain->replace_blocks(_tempblocklist); +// for (const auto& block : _tempblocklist) +// { +// if (_blockchain->addNewBlock(block)) +// { +// _database->write(block); +// } +// } retval = true; } else { _logger->warn("temp chain is too far ahead with blocks {}-{} and local chain {}-{}", - _tempchain->front().index(), _tempchain->back().index(), - _blockchain->front().index(), _blockchain->back().index()); + _tempblocklist.front().index(), _tempblocklist.back().index(), + _blockchain->front().index(), _blockchain->back().index()); } - _tempchain.reset(); + _tempblocklist.clear(); } return retval; @@ -954,8 +951,8 @@ void MinerApp::handleResponse(HcConnectionPtr connection, const nl::json& json) // if we need to replace/update the chain, but we only // do those checks in 'summary'. We should do the thing // in the 'chain' command. - const auto& genesis = _tempchain ? _tempchain->front() : _blockchain->front(); - const auto& lastblock = _tempchain ? _tempchain->back() : _blockchain->back(); + const auto& genesis = _tempblocklist.size() > 0 ? _tempblocklist.front() : _blockchain->front(); + const auto& lastblock = _tempblocklist.size() > 0 ? _tempblocklist.back() : _blockchain->back(); if (genesis != remote_gen) { @@ -986,19 +983,25 @@ void MinerApp::handleResponse(HcConnectionPtr connection, const nl::json& json) } else if (message == "chain") { - if (const auto tempchain = json["blocks"].get(); - tempchain.size() <= 0 || !tempchain.isValidChain()) + const auto blocklist = json["blocks"].get(); + if (blocklist.size() <= 0) { - _logger->info("received invalid chain from connection {}", + _logger->info("received empty chain from connection {}", static_cast(connection.get())); return; } - else + + if (!ash::ValidBlockchain(blocklist)) { - std::lock_guard lock(_chainMutex); - handleChainResponse(connection, tempchain); - } + _logger->info("received invalid chain from connection {}", + static_cast(connection.get())); + + return; + } + + std::lock_guard lock(_chainMutex); + handleChainResponse(connection, std::move(blocklist)); } if (this->_miningDone) @@ -1007,15 +1010,15 @@ void MinerApp::handleResponse(HcConnectionPtr connection, const nl::json& json) } } -void MinerApp::handleChainResponse(HcConnectionPtr connection, const Blockchain& tempchain) +void MinerApp::handleChainResponse(HcConnectionPtr connection, BlockList tempchain) { if (tempchain.front().index() == 0) { _logger->info("queuing replacement for local chain with with blocks {}-{}", tempchain.front().index(), tempchain.back().index()); - _tempchain = std::make_unique(); - *_tempchain = std::move(tempchain); + _tempblocklist = std::move(tempchain); + } else if (tempchain.front().index() > _blockchain->back().index() + 1) { @@ -1041,8 +1044,7 @@ void MinerApp::handleChainResponse(HcConnectionPtr connection, const Blockchain& _logger->info("caching update for local chain with remote blocks {}-{}", tempchain.front().index(), tempchain.back().index()); - _tempchain = std::make_unique(); - *_tempchain = tempchain; + _tempblocklist = std::move(tempchain); } else { diff --git a/src/MinerApp.h b/src/MinerApp.h index 1c9610d..794546e 100644 --- a/src/MinerApp.h +++ b/src/MinerApp.h @@ -35,12 +35,6 @@ using HttpResponsePtr = std::shared_ptr; using WsServer = SimpleWeb::SocketServer; -struct UpdatePackage -{ - BlockChainPtr tempchain; - std::uint64_t tempcumdiff; -}; - class MinerApp { @@ -79,7 +73,7 @@ class MinerApp void dispatchRequest(HcConnectionPtr, const nl::json& json); void handleResponse(HcConnectionPtr, const nl::json& json); - void handleChainResponse(HcConnectionPtr, const Blockchain&); + void handleChainResponse(HcConnectionPtr, BlockList); void handleError(HcConnectionPtr, const nl::json&); void servePage(HttpResponsePtr response, @@ -90,12 +84,10 @@ class MinerApp std::string _uuid; std::string _rewardAddress; - ChainDatabasePtr _database; - - std::mutex _chainMutex; // chain mutex + std::mutex _chainMutex; BlockChainPtr _blockchain; - BlockChainPtr _tempchain; + BlockList _tempblocklist; SettingsPtr _settings; PeerManager _peers; diff --git a/src/Transactions.h b/src/Transactions.h index f578a12..9e0dd09 100644 --- a/src/Transactions.h +++ b/src/Transactions.h @@ -132,8 +132,8 @@ class TxIn final TxOutPoint _txOutPt; std::string _signature; - friend void read_data(std::istream& stream, TxIn& txin); friend void from_json(const nl::json& j, TxIn& txin); + friend void read_txin(std::istream& stream, TxIn& txin); public: TxIn() = default; @@ -204,8 +204,9 @@ class TxOut final double amount() const noexcept { return _amount; } private: - friend void read_data(std::istream& stream, TxOut& txout); + friend void from_json(const nl::json& j, TxOut& txout); + friend void read_txout(std::istream& stream, TxOut& txout); std::string _address; // public-key/address of receiver double _amount; @@ -240,6 +241,8 @@ class Transaction final friend Transaction CreateCoinbaseTransaction(std::uint64_t blockIdx, std::string_view address); friend void from_json(const nl::json& j, Transaction& tx); friend void read_data(std::istream& stream, Transaction& tx); + friend void read_tx(std::istream& stream, Transaction& tx); + public: diff --git a/src/main.cpp b/src/main.cpp index 662be6c..f316184 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,6 +135,8 @@ int main(int argc, char* argv[]) ("version,v", "print version string") ("config,c",po::value(), "config file") ("createwallet", "create a wallet") + ("summary", "get summary of current blockchain") + ("printblock",po::value(), "print block id") ; po::variables_map vm; @@ -167,6 +169,34 @@ int main(int argc, char* argv[]) } auto settings = initSettings(configFile); + +// if (vm.count("summary")) +// { +// const std::string dbfolder = settings.value("database.folder", ""); +// auto chaindb = std::make_unique(dbfolder); +// auto chain = std::make_unique(std::move(chaindb)); +// +// } + + if (vm.count("printblock") > 0) + { + const auto index = vm["printblock"].as(); + + const std::string dbfolder = settings->value("database.folder", ""); + auto chaindb = std::make_unique(dbfolder); + ash::Blockchain chain{std::move(chaindb)}; + + if (index > chain.size() - 1) + { + std::cerr << "invalid block number " << index << '\n'; + return 1; + } + + nl::json json { chain.at(index) }; + std::cout << json.dump(4) << '\n'; + return 0; + } + initializeLogs(settings); ash::rootLogger()->info("using setting file {}", configFile); diff --git a/src/test_block.cpp b/src/test_block.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e61c4d2..adea386 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,7 +36,12 @@ set(ASH_FILES ../src/CryptoUtils.cpp ../src/CryptoUtils.h + + ../src/ChainDatabase.cpp + ../src/ChainDatabase.h ) +create_test("block" "${ASH_FILES}") create_test("blockchain" "${ASH_FILES}") create_test("crypto" "${ASH_FILES}") +create_test("db" "${ASH_FILES}") diff --git a/tests/Test.h b/tests/Test.h new file mode 100644 index 0000000..c8bcbb7 --- /dev/null +++ b/tests/Test.h @@ -0,0 +1,46 @@ +#pragma once +#include + +#include +#include + +#include + +namespace ash +{ + +std::string LoadFile(std::string_view filename) +{ + std::ifstream t(filename.data()); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + return str; +} + +ash::Blockchain LoadBlockchain(std::string_view chainfile) +{ + const std::string filename = fmt::format("{}/tests/data/{}", ASH_SRC_DIRECTORY, chainfile); + const std::string rawjson = LoadFile(filename); + nl::json json = nl::json::parse(rawjson, nullptr, false); + BOOST_TEST(!json.is_discarded()); + return json["blocks"].get(); +} + +boost::filesystem::path tempFolder() +{ + auto temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("ash%%%%%%"); + temp /= std::to_string(boost::unit_test::framework::current_test_case().p_id); + boost::filesystem::create_directories(temp); + return temp; +} + +boost::filesystem::path tempFolder(const std::string& subfolder) +{ + auto temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("ash%%%%%%"); + temp /= subfolder; + temp /= std::to_string(boost::unit_test::framework::current_test_case().p_id); + boost::filesystem::create_directories(temp); + return temp; +} + +} \ No newline at end of file diff --git a/tests/test_block.cpp b/tests/test_block.cpp new file mode 100644 index 0000000..4caf421 --- /dev/null +++ b/tests/test_block.cpp @@ -0,0 +1,40 @@ +#include +#include + +//#include + +#include + +#include "../src/Block.h" +#include "../src/Transactions.h" +#include "../src/ChainDatabase.h" + +#include "Test.h" + +namespace nl = nlohmann; +namespace data = boost::unit_test::data; + +using namespace std::string_view_literals; + +BOOST_AUTO_TEST_SUITE(block) + +BOOST_AUTO_TEST_CASE(empty_blockcopy) +{ + ash::Block block{ 123, "data123" }; + auto block_copy = block; + BOOST_TEST(block.index() == block_copy.index()); + BOOST_TEST(block.data() == block_copy.data()); +} + +BOOST_AUTO_TEST_CASE(block_with_transaction_copy) +{ + ash::Transaction tx; + tx.txIns().emplace_back(1,2,3,"signature"); + tx.txOuts().emplace_back("senderAddress", 12.34); + + ash::Block block{ 123, "data123" }; +// block.transactions() + +} + +BOOST_AUTO_TEST_SUITE_END() // db \ No newline at end of file diff --git a/tests/test_blockchain.cpp b/tests/test_blockchain.cpp index 0785b30..70ea548 100644 --- a/tests/test_blockchain.cpp +++ b/tests/test_blockchain.cpp @@ -12,8 +12,6 @@ #include -#include - #include "../src/Block.h" #include "../src/Blockchain.h" #include "../src/Miner.h" @@ -21,6 +19,8 @@ #include "../src/Transactions.h" #include "../src/Miner.h" +#include "Test.h" + namespace nl = nlohmann; namespace data = boost::unit_test::data; @@ -82,23 +82,6 @@ std::ostream& operator<<(std::ostream& out, const ash::AddressLedger& ledger) } -std::string LoadFile(std::string_view filename) -{ - std::ifstream t(filename.data()); - std::string str((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - return str; -} - -ash::Blockchain LoadBlockchain(std::string_view chainfile) -{ - const std::string filename = fmt::format("{}/tests/data/{}", ASH_SRC_DIRECTORY, chainfile); - const std::string rawjson = LoadFile(filename); - nl::json json = nl::json::parse(rawjson, nullptr, false); - BOOST_TEST(!json.is_discarded()); - return json["blocks"].get(); -} - BOOST_AUTO_TEST_SUITE(block) BOOST_AUTO_TEST_CASE(LoadChainFromJson) diff --git a/tests/test_db.cpp b/tests/test_db.cpp new file mode 100644 index 0000000..6ddec8e --- /dev/null +++ b/tests/test_db.cpp @@ -0,0 +1,162 @@ +#include +#include + +//#include + +#include + +#include "../src/Block.h" +#include "../src/Transactions.h" +#include "../src/ChainDatabase.h" + +#include "Test.h" + +namespace nl = nlohmann; +namespace data = boost::unit_test::data; + +using namespace std::string_view_literals; + +BOOST_AUTO_TEST_SUITE(db) + +std::vector CreateBlockStore() +{ + std::vector retval; + retval.emplace_back(0, "prevhash1"); + retval.back().setData("data1"); + + retval.emplace_back(1, "prevhash2"); + retval.back().setData("data2"); + + retval.emplace_back(2, "prevhash3"); + retval.back().setData("data3"); + + return retval; +} + +std::string CreateBlockDB(const std::string& test_name) +{ + auto path = ash::tempFolder(test_name); + { + ash::ChainLevelDB db(path.string()); + for (auto blocks = CreateBlockStore(); auto block : blocks) + { + db.write(block); + } + } + + return path.string(); +} + +ash::Block createGenesisBlock() +{ + ash::Transactions txs; + txs.push_back(ash::CreateCoinbaseTransaction(0, "REWARDADDRESS")); + ash::Block gen{ 0, "", std::move(txs) }; + +// std::time_t t = std::time(nullptr); +// const auto gendata = +// fmt::format("Gensis Block created {:%Y-%m-%d %H:%M:%S %Z}", *std::localtime(&t)); + + gen.setData("GENESISDATA"); + return gen; +} + +// add blocks in a random order and make sure that our custom +// comparator sorts the blocks in leveldb in numeric order +BOOST_AUTO_TEST_CASE(test_comparator) +{ + auto path = ash::tempFolder("test_comparator"); + ash::ChainLevelDB db(path.string()); + + ash::Block block1 { 0, "", {} }; + db.write(block1); + + ash::Block block2 { 1975, "", {} }; + db.write(block2); + + ash::Block block3 { 57, "", {} }; + db.write(block3); + + ash::Block block4 { 575757, "", {} }; + db.write(block4); + + std::unique_ptr itptr; + itptr.reset(db.leveldb()->NewIterator(leveldb::ReadOptions())); + + itptr->SeekToFirst(); + BOOST_TEST(itptr->Valid()); + auto szptr = reinterpret_cast(itptr->key().data()); + BOOST_TEST(*szptr == 0); + + itptr->Next(); + BOOST_TEST(itptr->Valid()); + szptr = reinterpret_cast(itptr->key().data()); + BOOST_TEST(*szptr == 57); + + itptr->Next(); + BOOST_TEST(itptr->Valid()); + szptr = reinterpret_cast(itptr->key().data()); + BOOST_TEST(*szptr == 1975); + + itptr->Next(); + BOOST_TEST(itptr->Valid()); + szptr = reinterpret_cast(itptr->key().data()); + BOOST_TEST(*szptr == 575757); + + itptr->Next(); + BOOST_TEST(!itptr->Valid()); +} + +// write a couple blocks to the db and make sure we get them +// back how we want +BOOST_AUTO_TEST_CASE(test_write_read) +{ + auto db_name = CreateBlockDB("test_write_read"); + ash::ChainLevelDB db{ db_name }; + + auto itptr = db.const_iteraor(); + std::size_t count = 0; + + // this is probably bad practice since we're iterating the db and already + // have the value during our iteration, but that's ok since we want to + // test our read function too and this is a unit test + for (itptr->SeekToFirst(); itptr->Valid(); itptr->Next(), count++) + { + auto index = *(reinterpret_cast(itptr->key().data())); + auto block = db.read(index); + BOOST_TEST(block.has_value()); + BOOST_TEST(block->index() == count); + BOOST_TEST(block->previousHash() == ("prevhash" + std::to_string(count + 1))); + BOOST_TEST(block->data() == ("data" + std::to_string(count + 1))); + } +} + +BOOST_AUTO_TEST_CASE(test_update_block) +{ + auto db_name = CreateBlockDB("test_update_block"); + ash::ChainLevelDB db{db_name}; + +// ash::Block& block1 { db.read(0) }; +// block1.setData("updated1") + +} + +// write and entire blockchain to the database and make +// sure we can read it how we expect +BOOST_AUTO_TEST_CASE(test_write_read_blockchain) +{ + +} + +BOOST_AUTO_TEST_CASE(test_size) +{ + auto db_name = CreateBlockDB("test_write_read"); + ash::ChainLevelDB db{ db_name }; + BOOST_TEST(db.size() == 3); + + ash::Block newblock1 { db.size(), ""}; + + +} + +BOOST_AUTO_TEST_SUITE_END() // db \ No newline at end of file