aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2018-07-16 04:13:57 -0400
committerDeterminant <[email protected]>2018-07-16 04:13:57 -0400
commitefd49718dfbb6c1f329e72026770b07a67348168 (patch)
tree4f29a41480c6ad42ad0fcf2b861c7acc7368b937
init
-rw-r--r--.gitignore23
-rw-r--r--.gitmodules6
-rw-r--r--CMakeLists.txt85
-rw-r--r--TODO.rst1
-rw-r--r--doc/.gitignore1
-rw-r--r--doc/doxygen.conf2482
-rw-r--r--hotstuff-sec0.conf2
-rw-r--r--hotstuff-sec1.conf2
-rw-r--r--hotstuff-sec2.conf2
-rw-r--r--hotstuff-sec3.conf2
-rw-r--r--hotstuff.conf4
-rwxr-xr-xrun_client.sh2
-rwxr-xr-xrun_replicas.sh6
m---------salticidae0
m---------secp256k10
-rw-r--r--src/client.cpp43
-rw-r--r--src/client.h66
-rw-r--r--src/core.cpp723
-rw-r--r--src/core.h631
-rw-r--r--src/crypto.cpp25
-rw-r--r--src/crypto.h386
-rw-r--r--src/entity.cpp35
-rw-r--r--src/entity.h309
-rw-r--r--src/hotstuff.cpp287
-rw-r--r--src/hotstuff_client.cpp181
-rw-r--r--src/hotstuff_keygen.cpp33
-rw-r--r--src/promise.hpp745
-rw-r--r--src/type.h46
-rw-r--r--src/util.cpp7
-rw-r--r--src/util.h119
-rw-r--r--test/CMakeLists.txt4
-rw-r--r--test/test_secp256k1.cpp33
32 files changed, 6291 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f502a81
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+CMakeFiles/
+cmake_install.cmake
+CMakeDoxygenDefaults.cmake
+CMakeDoxyfile.in
+CMakeCache.txt
+cmake-build-debug/
+libsecp256k1-prefix/
+hotstuff-app
+hotstuff-client
+hotstuff-keygen
+libhotstuff.a
+src/*.swo
+src/*.swp
+*.a
+*.o
+*.la
+*.lo
+*.so
+*.gch
+/Makefile
+/test/Makefile
+test_secp256k1
+core
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..834ffa4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "secp256k1"]
+ path = secp256k1
+ url = https://github.com/bitcoin-core/secp256k1
+[submodule "salticidae"]
+ path = salticidae
+ url = https://github.com/Determinant/salticidae.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e3cc3cc
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,85 @@
+cmake_minimum_required(VERSION 3.9)
+project(hotstuff)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/salticidae/cmake/Modules/")
+
+add_subdirectory(salticidae)
+include_directories(salticidae/include)
+
+find_package(Libevent REQUIRED)
+find_package(OpenSSL REQUIRED)
+
+include(ExternalProject)
+include_directories(secp256k1/include)
+ExternalProject_Add(libsecp256k1
+ SOURCE_DIR secp256k1
+ CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/secp256k1/autogen.sh
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/secp256k1/configure --disable-shared --with-pic --with-bignum=no --enable-module-recovery
+ BUILD_COMMAND make
+ INSTALL_COMMAND ""
+ BUILD_IN_SOURCE 1)
+
+add_library(secp256k1 STATIC IMPORTED)
+set_target_properties(
+ secp256k1
+ PROPERTIES IMPORTED_LOCATION
+ ${CMAKE_CURRENT_SOURCE_DIR}/secp256k1/.libs/libsecp256k1.a)
+add_dependencies(secp256k1 libsecp256k1)
+
+# add libraries
+
+add_library(hotstuff
+ src/entity.cpp
+ src/core.cpp
+ src/client.cpp
+ src/crypto.cpp
+ src/util.cpp)
+target_link_libraries(hotstuff salticidae secp256k1 crypto)
+
+add_subdirectory(test)
+
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Release")
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ add_definitions(-DHOTSTUFF_DEBUG_LOG)
+ add_definitions(-DSALTICIDAE_DEBUG_LOG)
+elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
+ add_definitions(-DHOTSTUFF_NORMAL_LOG)
+ add_definitions(-DSALTICIDAE_NORMAL_LOG)
+endif()
+
+# add executables
+
+add_executable(hotstuff-app
+ src/hotstuff.cpp)
+target_link_libraries(hotstuff-app hotstuff)
+
+add_executable(hotstuff-client
+ src/hotstuff_client.cpp)
+target_link_libraries(hotstuff-client hotstuff)
+
+add_executable(hotstuff-keygen
+ src/hotstuff_keygen.cpp)
+target_link_libraries(hotstuff-keygen hotstuff)
+
+find_package(Doxygen)
+if (DOXYGEN_FOUND)
+ add_custom_target(doc
+ ${DOXYGEN_EXECUTABLE} doc/doxygen.conf WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endif(DOXYGEN_FOUND)
+
+#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -no-pie -pg")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -W -Wall -Wextra -pedantic -Wsuggest-override")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -W -Wall -Wextra -pedantic -Wsuggest-override")
+
+macro(remove_cxx_flag flag)
+ string(REPLACE "${flag}" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
+endmacro()
+
+remove_cxx_flag("-DNDEBUG")
+#message(${CMAKE_CXX_FLAGS_RELEASE})
+#remove_cxx_flag("-O3")
+#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
+#message(${CMAKE_CXX_FLAGS_RELEASE})
diff --git a/TODO.rst b/TODO.rst
new file mode 100644
index 0000000..b9d7020
--- /dev/null
+++ b/TODO.rst
@@ -0,0 +1 @@
+- leave parent selection to PaceMaker
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..5ccff1a
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1 @@
+html/
diff --git a/doc/doxygen.conf b/doc/doxygen.conf
new file mode 100644
index 0000000..0fdbb9b
--- /dev/null
+++ b/doc/doxygen.conf
@@ -0,0 +1,2482 @@
+# Doxyfile 1.8.14
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "HotStuff"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc/
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = ./src/
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.idl \
+ *.ddl \
+ *.odl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.cs \
+ *.d \
+ *.php \
+ *.php4 \
+ *.php5 \
+ *.phtml \
+ *.inc \
+ *.m \
+ *.markdown \
+ *.md \
+ *.mm \
+ *.dox \
+ *.py \
+ *.pyw \
+ *.f90 \
+ *.f95 \
+ *.f03 \
+ *.f08 \
+ *.f \
+ *.for \
+ *.tcl \
+ *.vhd \
+ *.vhdl \
+ *.ucf \
+ *.qsf
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via Javascript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have Javascript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/hotstuff-sec0.conf b/hotstuff-sec0.conf
new file mode 100644
index 0000000..97fef97
--- /dev/null
+++ b/hotstuff-sec0.conf
@@ -0,0 +1,2 @@
+privkey = ee9dd39a8f269918ed9a27789bb4d5ddabe572e5d6397b01c643141a4568c83b
+idx = 0
diff --git a/hotstuff-sec1.conf b/hotstuff-sec1.conf
new file mode 100644
index 0000000..e36849f
--- /dev/null
+++ b/hotstuff-sec1.conf
@@ -0,0 +1,2 @@
+privkey = 06cf6276fa4353a1c54a9f762bb827e016f5ed4cf659010b0689cf667eda54eb
+idx = 1
diff --git a/hotstuff-sec2.conf b/hotstuff-sec2.conf
new file mode 100644
index 0000000..9827647
--- /dev/null
+++ b/hotstuff-sec2.conf
@@ -0,0 +1,2 @@
+privkey = 2d0d2f77fa8dc3dd590e3a4c7cd5669de0aaccd0c172c50263205a8ea29b495d
+idx = 2
diff --git a/hotstuff-sec3.conf b/hotstuff-sec3.conf
new file mode 100644
index 0000000..1789083
--- /dev/null
+++ b/hotstuff-sec3.conf
@@ -0,0 +1,2 @@
+privkey = d5e41b168dd1c1703d6e6dc69db475daacae413e86e9db31afdbeea0fd1c45d4
+idx = 3
diff --git a/hotstuff.conf b/hotstuff.conf
new file mode 100644
index 0000000..4823340
--- /dev/null
+++ b/hotstuff.conf
@@ -0,0 +1,4 @@
+replica = 127.0.0.1:2234;22234, 028a1caf2c503a1e9b0b3ddf1d1df30253facdd50b93add05ebc7f708db00c11e4
+replica = 127.0.0.1:2235;22235, 034ca53338e69321c1bc83e2fa76b1b00d68f64911074221abda88aac8af9d2b53
+replica = 127.0.0.1:2236;22236, 0340f9d12dd1532968f7d8a99f95c3cd03992346487e15bd43265a3f273558ff2e
+replica = 127.0.0.1:2237;22237, 02735def87faba2667d1a5db32b6dd50bb0c2ce875935846b3db121def62f34e83
diff --git a/run_client.sh b/run_client.sh
new file mode 100755
index 0000000..0efc198
--- /dev/null
+++ b/run_client.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+./hotstuff-client --idx 0 --ntx -1
diff --git a/run_replicas.sh b/run_replicas.sh
new file mode 100755
index 0000000..c106d42
--- /dev/null
+++ b/run_replicas.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in {0..3}; do
+ #valgrind ./hotstuff-app --conf hotstuff-sec${i}.conf > log${i} 2>&1 &
+ ./hotstuff-app --conf hotstuff-sec${i}.conf > log${i} 2>&1 &
+done
+wait
diff --git a/salticidae b/salticidae
new file mode 160000
+Subproject 71874620e59d2ccf1d5a4f1c39c06bd2b5b0068
diff --git a/secp256k1 b/secp256k1
new file mode 160000
+Subproject 1e6f1f5ad5e7f1e3ef79313ec02023902bf8175
diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644
index 0000000..f787003
--- /dev/null
+++ b/src/client.cpp
@@ -0,0 +1,43 @@
+#include "client.h"
+
+namespace hotstuff {
+
+uint64_t CommandDummy::cnt = 0;
+
+void MsgClient::gen_reqcmd(const Command &cmd) {
+ DataStream s;
+ set_opcode(REQ_CMD);
+ s << cmd;
+ set_payload(std::move(s));
+}
+
+void MsgClient::parse_reqcmd(CommandDummy &cmd) const {
+ DataStream s(get_payload());
+ s >> cmd;
+}
+
+void MsgClient::gen_respcmd(const uint256_t &cmd_hash, const Finality &fin) {
+ DataStream s;
+ set_opcode(RESP_CMD);
+ s << cmd_hash << fin;
+ set_payload(std::move(s));
+}
+
+void MsgClient::parse_respcmd(uint256_t &cmd_hash, Finality &fin) const {
+ DataStream s(get_payload());
+ s >> cmd_hash >> fin;
+}
+
+void MsgClient::gen_chkcmd(const uint256_t &cmd_hash) {
+ DataStream s;
+ set_opcode(CHK_CMD);
+ s << cmd_hash;
+ set_payload(std::move(s));
+}
+
+void MsgClient::parse_chkcmd(uint256_t &cmd_hash) const {
+ DataStream s(get_payload());
+ s >> cmd_hash;
+}
+
+}
diff --git a/src/client.h b/src/client.h
new file mode 100644
index 0000000..dd1cfee
--- /dev/null
+++ b/src/client.h
@@ -0,0 +1,66 @@
+#ifndef _HOTSTUFF_CLIENT_H
+#define _HOTSTUFF_CLIENT_H
+
+#include "type.h"
+#include "salticidae/msg.h"
+#include "entity.h"
+
+namespace hotstuff {
+
+enum {
+ REQ_CMD = 0x4,
+ RESP_CMD = 0x5,
+ CHK_CMD = 0x6
+};
+
+class CommandDummy: public Command {
+ static uint64_t cnt;
+ uint64_t n;
+ uint256_t hash;
+
+ public:
+
+ CommandDummy() {}
+
+ ~CommandDummy() override {}
+
+ CommandDummy(uint64_t n):
+ n(n), hash(salticidae::get_hash(*this)) {}
+
+ static command_t make_cmd() {
+ return new CommandDummy(cnt++);
+ }
+
+ void serialize(DataStream &s) const override {
+ s << n;
+ }
+
+ void unserialize(DataStream &s) override {
+ s >> n;
+ hash = salticidae::get_hash(*this);
+ }
+
+ const uint256_t &get_hash() const override {
+ return hash;
+ }
+
+ bool verify() const override {
+ return true;
+ }
+};
+
+struct MsgClient: public salticidae::MsgBase<> {
+ using MsgBase::MsgBase;
+ void gen_reqcmd(const Command &cmd);
+ void parse_reqcmd(CommandDummy &cmd) const;
+
+ void gen_respcmd(const uint256_t &cmd_hash, const Finality &fin);
+ void parse_respcmd(uint256_t &cmd_hash, Finality &fin) const;
+
+ void gen_chkcmd(const uint256_t &cmd_hash);
+ void parse_chkcmd(uint256_t &cmd_hash) const;
+};
+
+}
+
+#endif
diff --git a/src/core.cpp b/src/core.cpp
new file mode 100644
index 0000000..d6a4cc7
--- /dev/null
+++ b/src/core.cpp
@@ -0,0 +1,723 @@
+#include <stack>
+#include "core.h"
+
+using salticidae::DataStream;
+using salticidae::static_pointer_cast;
+using salticidae::get_hash;
+
+#define LOG_INFO HOTSTUFF_LOG_INFO
+#define LOG_DEBUG HOTSTUFF_LOG_DEBUG
+#define LOG_WARN HOTSTUFF_LOG_WARN
+
+namespace hotstuff {
+
+void MsgHotStuff::gen_propose(const Proposal &proposal) {
+ DataStream s;
+ set_opcode(PROPOSE);
+ s << proposal;
+ set_payload(std::move(s));
+}
+
+void MsgHotStuff::parse_propose(Proposal &proposal) const {
+ DataStream(get_payload()) >> proposal;
+}
+
+void MsgHotStuff::gen_vote(const Vote &vote) {
+ DataStream s;
+ set_opcode(VOTE);
+ s << vote;
+ set_payload(std::move(s));
+}
+
+void MsgHotStuff::parse_vote(Vote &vote) const {
+ DataStream(get_payload()) >> vote;
+}
+
+void MsgHotStuff::gen_qfetchblk(const std::vector<uint256_t> &blk_hashes) {
+ DataStream s;
+ set_opcode(QUERY_FETCH_BLK);
+ gen_hash_list(s, blk_hashes);
+ set_payload(std::move(s));
+}
+
+void MsgHotStuff::parse_qfetchblk(std::vector<uint256_t> &blk_hashes) const {
+ DataStream s(get_payload());
+ parse_hash_list(s, blk_hashes);
+}
+
+void MsgHotStuff::gen_rfetchblk(const std::vector<block_t> &blks) {
+ DataStream s;
+ set_opcode(RESP_FETCH_BLK);
+ s << htole((uint32_t)blks.size());
+ for (auto blk: blks) s << *blk;
+ set_payload(std::move(s));
+}
+
+void MsgHotStuff::parse_rfetchblk(std::vector<block_t> &blks, HotStuffCore *hsc) const {
+ DataStream s;
+ uint32_t size;
+ s >> size;
+ size = letoh(size);
+ blks.resize(size);
+ for (auto &blk: blks)
+ {
+ Block _blk;
+ _blk.unserialize(s, hsc);
+ if (!_blk.verify(hsc->get_config()))
+ blk = hsc->storage->add_blk(std::move(_blk));
+ else
+ {
+ blk = nullptr;
+ LOG_WARN("block is invalid");
+ }
+ }
+}
+
+/* The core logic of HotStuff, is farily simple :) */
+/*** begin HotStuff protocol logic ***/
+HotStuffCore::HotStuffCore(ReplicaID id,
+ privkey_bt &&priv_key,
+ int32_t parent_limit):
+ b0(new Block(true, 1)),
+ bqc(b0),
+ bexec(b0),
+ vheight(0),
+ priv_key(std::move(priv_key)),
+ tails{bqc},
+ id(id),
+ parent_limit(parent_limit),
+ storage(new EntityStorage()) {
+ storage->add_blk(b0);
+ b0->qc_ref = b0;
+}
+
+void HotStuffCore::sanity_check_delivered(const block_t &blk) {
+ if (!blk->delivered)
+ throw std::runtime_error("block not delivered");
+}
+
+block_t HotStuffCore::sanity_check_delivered(const uint256_t &blk_hash) {
+ block_t blk = storage->find_blk(blk_hash);
+ if (blk == nullptr || !blk->delivered)
+ throw std::runtime_error("block not delivered");
+ return std::move(blk);
+}
+
+bool HotStuffCore::on_deliver_blk(const block_t &blk) {
+ if (blk->delivered)
+ {
+ LOG_WARN("attempt to deliver a block twice");
+ return false;
+ }
+ blk->parents.clear();
+ for (const auto &hash: blk->parent_hashes)
+ {
+ block_t p = sanity_check_delivered(hash);
+ blk->parents.push_back(p);
+ }
+ blk->height = blk->parents[0]->height + 1;
+ for (const auto &cmd: blk->cmds)
+ cmd->container = blk;
+
+ if (blk->qc)
+ {
+ block_t _blk = storage->find_blk(blk->qc->get_blk_hash());
+ if (_blk == nullptr)
+ throw std::runtime_error("block referred by qc not fetched");
+ blk->qc_ref = std::move(_blk);
+ } // otherwise blk->qc_ref remains null
+
+ for (auto pblk: blk->parents) tails.erase(pblk);
+ tails.insert(blk);
+
+ blk->delivered = true;
+ LOG_DEBUG("delivered %.10s", get_hex(blk->get_hash()).c_str());
+ return true;
+}
+
+void HotStuffCore::check_commit(const block_t &_blk) {
+ const block_t &blk = _blk->qc_ref;
+ if (blk->qc_ref == nullptr) return;
+ if (blk->decision) return;
+ block_t p = blk->parents[0];
+ if (p == blk->qc_ref)
+ { /* commit */
+ std::vector<block_t> commit_queue;
+ block_t b;
+ for (b = p; b->height > bexec->height; b = b->parents[0])
+ { /* todo: also commit the uncles/aunts */
+ commit_queue.push_back(b);
+ }
+ if (b != bexec)
+ throw std::runtime_error("safety breached :(");
+ for (auto it = commit_queue.rbegin(); it != commit_queue.rend(); it++)
+ {
+ const block_t &blk = *it;
+ blk->decision = 1;
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ LOG_INFO("commit blk %.10s", get_hex10(blk->get_hash()).c_str());
+#endif
+ for (auto cmd: blk->cmds)
+ do_decide(cmd);
+ }
+ bexec = p;
+ }
+}
+
+bool HotStuffCore::update(const uint256_t &bqc_hash) {
+ block_t _bqc = sanity_check_delivered(bqc_hash);
+ if (_bqc->qc_ref == nullptr) return false;
+ check_commit(_bqc);
+ if (_bqc->qc_ref->height > bqc->qc_ref->height)
+ bqc = _bqc;
+ return true;
+}
+
+void HotStuffCore::on_propose(const std::vector<command_t> &cmds) {
+ size_t nparents = parent_limit < 1 ? tails.size() : parent_limit;
+ assert(tails.size() > 0);
+ block_t p = *tails.rbegin();
+ std::vector<block_t> parents{p};
+ tails.erase(p);
+ nparents--;
+ /* add the rest of tails as "uncles/aunts" */
+ while (nparents--)
+ {
+ auto it = tails.begin();
+ parents.push_back(*it);
+ tails.erase(it);
+ }
+ quorum_cert_bt qc = nullptr;
+ block_t qc_ref = nullptr;
+ if (p != b0 && p->voted.size() >= config.nmajority)
+ {
+ qc = p->self_qc->clone();
+ qc->compute();
+ qc_ref = p;
+ }
+ /* create a new block */
+ block_t bnew = storage->add_blk(
+ Block(
+ parents,
+ cmds,
+ p->height + 1,
+ std::move(qc), qc_ref,
+ nullptr
+ ));
+ const uint256_t bnew_hash = bnew->get_hash();
+ bnew->self_qc = create_quorum_cert(bnew_hash);
+ on_deliver_blk(bnew);
+ update(bnew_hash);
+ Proposal prop(id, bqc->get_hash(), bnew, nullptr);
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ LOG_INFO("propose %s", std::string(*bnew).c_str());
+#endif
+ /* self-vote */
+ on_receive_vote(
+ Vote(id, bqc->get_hash(), bnew_hash,
+ create_part_cert(*priv_key, bnew_hash), this));
+ on_propose_(bnew);
+ /* boradcast to other replicas */
+ do_broadcast_proposal(prop);
+}
+
+void HotStuffCore::on_receive_proposal(const Proposal &prop) {
+ if (!update(prop.bqc_hash)) return;
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ LOG_INFO("got %s", std::string(prop).c_str());
+#endif
+ block_t bnew = prop.blk;
+ sanity_check_delivered(bnew);
+ bool opinion = false;
+ if (bnew->height > vheight)
+ {
+ block_t pref = bqc->qc_ref;
+ block_t b;
+ for (b = bnew;
+ b->height > pref->height;
+ b = b->parents[0]);
+ opinion = b == pref;
+ vheight = bnew->height;
+ }
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ LOG_INFO("now state: %s", std::string(*this).c_str());
+#endif
+ do_vote(prop.proposer,
+ Vote(id,
+ bqc->get_hash(),
+ bnew->get_hash(),
+ (opinion ?
+ create_part_cert(*priv_key, bnew->get_hash()) :
+ nullptr),
+ nullptr));
+}
+
+void HotStuffCore::on_receive_vote(const Vote &vote) {
+ if (!update(vote.bqc_hash)) return;
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ LOG_INFO("got %s", std::string(vote).c_str());
+ LOG_INFO("now state: %s", std::string(*this).c_str());
+#endif
+
+ block_t blk = sanity_check_delivered(vote.blk_hash);
+ if (vote.cert == nullptr) return;
+ if (!vote.verify())
+ {
+ LOG_WARN("invalid vote");
+ return;
+ }
+ if (!blk->voted.insert(vote.voter).second)
+ {
+ LOG_WARN("duplicate votes");
+ return;
+ }
+ size_t qsize = blk->voted.size();
+ if (qsize <= config.nmajority)
+ {
+ blk->self_qc->add_part(vote.voter, *vote.cert);
+ if (qsize == config.nmajority)
+ on_qc_finish(blk);
+ }
+}
+/*** end HotStuff protocol logic ***/
+
+void HotStuffCore::prune(uint32_t staleness) {
+ block_t start;
+ /* skip the blocks */
+ for (start = bexec; staleness; staleness--, start = start->parents[0])
+ if (!start->parents.size()) return;
+ std::stack<block_t> s;
+ start->qc_ref = nullptr;
+ s.push(start);
+ while (!s.empty())
+ {
+ auto &blk = s.top();
+ if (blk->parents.empty())
+ {
+ storage->try_release_blk(blk);
+ s.pop();
+ continue;
+ }
+ blk->qc_ref = nullptr;
+ s.push(blk->parents.back());
+ blk->parents.pop_back();
+ }
+}
+
+int8_t HotStuffCore::get_cmd_decision(const uint256_t &cmd_hash) {
+ auto cmd = storage->find_cmd(cmd_hash);
+ return cmd != nullptr ? cmd->get_decision() : 0;
+}
+
+void HotStuffCore::add_replica(ReplicaID rid, const NetAddr &addr,
+ pubkey_bt &&pub_key) {
+ config.add_replica(rid,
+ ReplicaInfo(rid, addr, std::move(pub_key)));
+ b0->voted.insert(rid);
+}
+
+promise_t HotStuffCore::async_qc_finish(const block_t &blk) {
+ if (blk->voted.size() >= config.nmajority)
+ return promise_t([](promise_t &pm) {
+ pm.resolve();
+ });
+ auto it = qc_waiting.find(blk);
+ if (it == qc_waiting.end())
+ it = qc_waiting.insert(std::make_pair(blk, promise_t())).first;
+ return it->second;
+}
+
+void HotStuffCore::on_qc_finish(const block_t &blk) {
+ auto it = qc_waiting.find(blk);
+ if (it != qc_waiting.end())
+ {
+ it->second.resolve();
+ qc_waiting.erase(it);
+ }
+}
+
+promise_t HotStuffCore::async_wait_propose() {
+ return propose_waiting;
+}
+
+void HotStuffCore::on_propose_(const block_t &blk) {
+ auto t = std::move(propose_waiting);
+ propose_waiting = promise_t();
+ t.resolve(blk);
+}
+
+HotStuffCore::operator std::string () const {
+ DataStream s;
+ s << "<hotstuff "
+ << "bqc=" << get_hex10(bqc->get_hash()) << " "
+ << "bexec=" << get_hex10(bqc->get_hash()) << " "
+ << "vheight=" << std::to_string(vheight) << " "
+ << "tails=" << std::to_string(tails.size()) << ">";
+ return std::string(std::move(s));
+}
+
+void HotStuffBase::add_replica(ReplicaID idx, const NetAddr &addr,
+ pubkey_bt &&pub_key) {
+ HotStuffCore::add_replica(idx, addr, std::move(pub_key));
+ if (addr != listen_addr)
+ pn.add_peer(addr);
+}
+
+void HotStuffBase::on_fetch_blk(const block_t &blk) {
+#ifdef HOTSTUFF_ENABLE_TX_PROFILE
+ blk_profiler.get_tx(blk->get_hash());
+#endif
+ LOG_DEBUG("fetched %.10s", get_hex(blk->get_hash()).c_str());
+ part_fetched++;
+ fetched++;
+ for (auto cmd: blk->get_cmds()) on_fetch_cmd(cmd);
+ const uint256_t &blk_hash = blk->get_hash();
+ auto it = blk_fetch_waiting.find(blk_hash);
+ if (it != blk_fetch_waiting.end())
+ {
+ it->second.resolve(blk);
+ blk_fetch_waiting.erase(it);
+ }
+}
+
+void HotStuffBase::on_fetch_cmd(const command_t &cmd) {
+ const uint256_t &cmd_hash = cmd->get_hash();
+ auto it = cmd_fetch_waiting.find(cmd_hash);
+ if (it != cmd_fetch_waiting.end())
+ {
+ it->second.resolve(cmd);
+ cmd_fetch_waiting.erase(it);
+ }
+}
+
+void HotStuffBase::on_deliver_blk(const block_t &blk) {
+ const uint256_t &blk_hash = blk->get_hash();
+ bool valid;
+ /* sanity check: all parents must be delivered */
+ for (const auto &p: blk->get_parent_hashes())
+ assert(storage->is_blk_delivered(p));
+ if ((valid = HotStuffCore::on_deliver_blk(blk)))
+ {
+ LOG_DEBUG("block %.10s delivered",
+ get_hex(blk_hash).c_str());
+ part_parent_size += blk->get_parent_hashes().size();
+ part_delivered++;
+ delivered++;
+ }
+ else
+ {
+ LOG_WARN("dropping invalid block");
+ }
+
+ auto it = blk_delivery_waiting.find(blk_hash);
+ if (it != blk_delivery_waiting.end())
+ {
+ auto &pm = it->second;
+ if (valid)
+ {
+ pm.elapsed.stop(false);
+ auto sec = pm.elapsed.elapsed_sec;
+ part_delivery_time += sec;
+ part_delivery_time_min = std::min(part_delivery_time_min, sec);
+ part_delivery_time_max = std::max(part_delivery_time_max, sec);
+
+ pm.resolve(blk);
+ }
+ else
+ {
+ pm.reject(blk);
+ // TODO: do we need to also free it from storage?
+ }
+ blk_delivery_waiting.erase(it);
+ }
+}
+
+promise_t HotStuffBase::async_fetch_blk(const uint256_t &blk_hash,
+ const NetAddr *replica_id,
+ bool fetch_now) {
+ if (storage->is_blk_fetched(blk_hash))
+ return promise_t([this, &blk_hash](promise_t pm){
+ pm.resolve(storage->find_blk(blk_hash));
+ });
+ auto it = blk_fetch_waiting.find(blk_hash);
+ if (it == blk_fetch_waiting.end())
+ {
+#ifdef HOTSTUFF_ENABLE_TX_PROFILE
+ blk_profiler.rec_tx(blk_hash, false);
+#endif
+ it = blk_fetch_waiting.insert(
+ std::make_pair(
+ blk_hash,
+ BlockFetchContext(blk_hash, this))).first;
+ }
+ if (replica_id != nullptr)
+ it->second.add_replica(*replica_id, fetch_now);
+ return static_cast<promise_t &>(it->second);
+}
+
+promise_t HotStuffBase::async_fetch_cmd(const uint256_t &cmd_hash,
+ const NetAddr *replica_id,
+ bool fetch_now) {
+ if (storage->is_cmd_fetched(cmd_hash))
+ return promise_t([this, &cmd_hash](promise_t pm){
+ pm.resolve(storage->find_cmd(cmd_hash));
+ });
+ auto it = cmd_fetch_waiting.find(cmd_hash);
+ if (it == cmd_fetch_waiting.end())
+ {
+ it = cmd_fetch_waiting.insert(
+ std::make_pair(cmd_hash, CmdFetchContext(cmd_hash, this))).first;
+ }
+ if (replica_id != nullptr)
+ it->second.add_replica(*replica_id, fetch_now);
+ return static_cast<promise_t &>(it->second);
+}
+
+promise_t HotStuffBase::async_deliver_blk(const uint256_t &blk_hash,
+ const NetAddr &replica_id) {
+ if (storage->is_blk_delivered(blk_hash))
+ return promise_t([this, &blk_hash](promise_t pm) {
+ pm.resolve(storage->find_blk(blk_hash));
+ });
+ auto it = blk_delivery_waiting.find(blk_hash);
+ if (it != blk_delivery_waiting.end())
+ return static_cast<promise_t &>(it->second);
+ BlockDeliveryContext pm{[](promise_t){}};
+ it = blk_delivery_waiting.insert(std::make_pair(blk_hash, pm)).first;
+ /* otherwise the on_deliver_batch will resolve */
+ async_fetch_blk(blk_hash, &replica_id).then([this, replica_id](block_t blk) {
+ /* qc_ref should be fetched */
+ std::vector<promise_t> pms;
+ const auto &qc = blk->get_qc();
+ if (qc)
+ pms.push_back(async_fetch_blk(qc->get_blk_hash(), &replica_id));
+ /* the parents should be delivered */
+ for (const auto &phash: blk->get_parent_hashes())
+ pms.push_back(async_deliver_blk(phash, replica_id));
+ promise::all(pms).then([this, blk]() {
+ on_deliver_blk(blk);
+ });
+ });
+ return static_cast<promise_t &>(pm);
+}
+
+void HotStuffBase::propose_handler(const MsgHotStuff &msg, conn_t conn_) {
+ auto conn = static_pointer_cast<PeerNetwork<MsgHotStuff>::Conn>(conn_);
+ const NetAddr &peer = conn->get_peer();
+ Proposal prop(this);
+ msg.parse_propose(prop);
+ block_t blk = prop.blk;
+ promise::all(std::vector<promise_t>{
+ async_deliver_blk(prop.bqc_hash, peer),
+ async_deliver_blk(blk->get_hash(), peer),
+ }).then([this, prop = std::move(prop)]() {
+ on_receive_proposal(prop);
+ });
+}
+
+void HotStuffBase::vote_handler(const MsgHotStuff &msg, conn_t conn_) {
+ auto conn = static_pointer_cast<PeerNetwork<MsgHotStuff>::Conn>(conn_);
+ const NetAddr &peer = conn->get_peer();
+ Vote vote(this);
+ msg.parse_vote(vote);
+ promise::all(std::vector<promise_t>{
+ async_deliver_blk(vote.bqc_hash, peer),
+ async_deliver_blk(vote.blk_hash, peer)
+ }).then([this, vote = std::move(vote)]() {
+ on_receive_vote(vote);
+ });
+}
+
+void HotStuffBase::query_fetch_blk_handler(const MsgHotStuff &msg, conn_t conn_) {
+ auto conn = static_pointer_cast<PeerNetwork<MsgHotStuff>::Conn>(conn_);
+ const NetAddr replica = conn->get_peer();
+ std::vector<uint256_t> blk_hashes;
+ msg.parse_qfetchblk(blk_hashes);
+
+ std::vector<promise_t> pms;
+ for (const auto &h: blk_hashes)
+ pms.push_back(async_fetch_blk(h, nullptr));
+ promise::all(pms).then([replica, this](const promise::values_t values) {
+ MsgHotStuff resp;
+ std::vector<block_t> blks;
+ for (auto &v: values)
+ {
+ auto blk = promise::any_cast<block_t>(v);
+ blks.push_back(blk);
+ }
+ resp.gen_rfetchblk(blks);
+ pn.send_msg(resp, replica);
+ });
+}
+
+void HotStuffBase::resp_fetch_blk_handler(const MsgHotStuff &msg, conn_t) {
+ std::vector<block_t> blks;
+ msg.parse_rfetchblk(blks, this);
+ for (const auto &blk: blks)
+ if (blk) on_fetch_blk(blk);
+}
+
+void HotStuffBase::print_stat() const {
+ LOG_INFO("===== begin stats =====");
+ LOG_INFO("-------- queues -------");
+ LOG_INFO("blk_fetch_waiting: %lu", blk_fetch_waiting.size());
+ LOG_INFO("blk_delivery_waiting: %lu", blk_delivery_waiting.size());
+ LOG_INFO("cmd_fetch_waiting: %lu", cmd_fetch_waiting.size());
+ LOG_INFO("decision_waiting: %lu", decision_waiting.size());
+ LOG_INFO("-------- misc ---------");
+ LOG_INFO("fetched: %lu", fetched);
+ LOG_INFO("delivered: %lu", delivered);
+ LOG_INFO("cmd_cache: %lu", storage->get_cmd_cache_size());
+ LOG_INFO("blk_cache: %lu", storage->get_blk_cache_size());
+ LOG_INFO("------ misc (10s) -----");
+ LOG_INFO("fetched: %lu", part_fetched);
+ LOG_INFO("delivered: %lu", part_delivered);
+ LOG_INFO("decided: %lu", part_decided);
+ LOG_INFO("gened: %lu", part_gened);
+ LOG_INFO("avg. parent_size: %.3f",
+ part_delivered ? part_parent_size / double(part_delivered) : 0);
+ LOG_INFO("delivery time: %.3f avg, %.3f min, %.3f max",
+ part_delivered ? part_delivery_time / double(part_delivered) : 0,
+ part_delivery_time_min == double_inf ? 0 : part_delivery_time_min,
+ part_delivery_time_max);
+
+ part_parent_size = 0;
+ part_fetched = 0;
+ part_delivered = 0;
+ part_decided = 0;
+ part_gened = 0;
+ part_delivery_time = 0;
+ part_delivery_time_min = double_inf;
+ part_delivery_time_max = 0;
+ LOG_INFO("-- sent opcode (10s) --");
+ auto &sent_op = pn.get_sent_by_opcode();
+ for (auto &op: sent_op)
+ {
+ auto &val = op.second;
+ LOG_INFO("%02x: %lu, %.2fBpm", op.first,
+ val.first, val.first ? val.second / double(val.first) : 0);
+ val.first = val.second = 0;
+ }
+ LOG_INFO("-- recv opcode (10s) --");
+ auto &recv_op = pn.get_recv_by_opcode();
+ for (auto &op: recv_op)
+ {
+ auto &val = op.second;
+ LOG_INFO("%02x: %lu, %.2fBpm", op.first,
+ val.first, val.first ? val.second / double(val.first) : 0);
+ val.first = val.second = 0;
+ }
+ LOG_INFO("--- replica msg. (10s) ---");
+ size_t _nsent = 0;
+ size_t _nrecv = 0;
+ for (const auto &replica: pn.all_peers())
+ {
+ auto conn = pn.get_peer_conn(replica);
+ size_t ns = conn->get_nsent();
+ size_t nr = conn->get_nrecv();
+ conn->clear_nsent();
+ conn->clear_nrecv();
+ LOG_INFO("%s: %u, %u, %u",
+ std::string(replica).c_str(), ns, nr, part_fetched_replica[replica]);
+ _nsent += ns;
+ _nrecv += nr;
+ part_fetched_replica[replica] = 0;
+ }
+ nsent += _nsent;
+ nrecv += _nrecv;
+ LOG_INFO("sent: %lu", _nsent);
+ LOG_INFO("recv: %lu", _nrecv);
+ LOG_INFO("--- replica msg. total ---");
+ LOG_INFO("sent: %lu", nsent);
+ LOG_INFO("recv: %lu", nrecv);
+ LOG_INFO("====== end stats ======");
+}
+
+promise_t HotStuffBase::async_decide(const uint256_t &cmd_hash) {
+ if (get_cmd_decision(cmd_hash))
+ return promise_t([this, cmd_hash](promise_t pm){
+ pm.resolve(storage->find_cmd(cmd_hash));
+ });
+ /* otherwise the do_decide will resolve the promise */
+ auto it = decision_waiting.find(cmd_hash);
+ if (it == decision_waiting.end())
+ {
+ promise_t pm{[](promise_t){}};
+ it = decision_waiting.insert(std::make_pair(cmd_hash, pm)).first;
+ }
+ return it->second;
+}
+
+HotStuffBase::HotStuffBase(uint32_t blk_size,
+ int32_t parent_limit,
+ ReplicaID rid,
+ privkey_bt &&priv_key,
+ NetAddr listen_addr,
+ EventContext eb,
+ pacemaker_bt pmaker):
+ HotStuffCore(rid, std::move(priv_key), parent_limit),
+ listen_addr(listen_addr),
+ blk_size(blk_size),
+ eb(eb),
+ pmaker(std::move(pmaker)),
+ pn(eb),
+
+ fetched(0), delivered(0),
+ nsent(0), nrecv(0),
+ part_parent_size(0),
+ part_fetched(0),
+ part_delivered(0),
+ part_decided(0),
+ part_gened(0),
+ part_delivery_time(0),
+ part_delivery_time_min(double_inf),
+ part_delivery_time_max(0)
+{
+ if (pmaker == nullptr)
+ this->pmaker = new PaceMakerDummy(this);
+ /* register the handlers for msg from replicas */
+ pn.reg_handler(PROPOSE, std::bind(&HotStuffBase::propose_handler, this, _1, _2));
+ pn.reg_handler(VOTE, std::bind(&HotStuffBase::vote_handler, this, _1, _2));
+ pn.reg_handler(QUERY_FETCH_BLK, std::bind(&HotStuffBase::query_fetch_blk_handler, this, _1, _2));
+ pn.reg_handler(RESP_FETCH_BLK, std::bind(&HotStuffBase::resp_fetch_blk_handler, this, _1, _2));
+ pn.init(listen_addr);
+}
+
+void HotStuffBase::do_broadcast_proposal(const Proposal &prop) {
+ MsgHotStuff prop_msg;
+ prop_msg.gen_propose(prop);
+ for (const auto &replica: pn.all_peers())
+ pn.send_msg(prop_msg, replica);
+}
+
+void HotStuffBase::do_vote(ReplicaID last_proposer, const Vote &vote) {
+ MsgHotStuff vote_msg;
+ vote_msg.gen_vote(vote);
+ pmaker->next_proposer(last_proposer)
+ .then([this, vote_msg](ReplicaID proposer) {
+ pn.send_msg(vote_msg, get_config().get_addr(proposer));
+ });
+}
+
+void HotStuffBase::do_decide(const command_t &cmd) {
+ auto it = decision_waiting.find(cmd->get_hash());
+ if (it != decision_waiting.end())
+ {
+ it->second.resolve(cmd);
+ decision_waiting.erase(it);
+ }
+}
+
+HotStuffBase::~HotStuffBase() {}
+
+void HotStuffBase::start(bool eb_loop) {
+ /* ((n - 1) + 1 - 1) / 3 */
+ uint32_t nfaulty = pn.all_peers().size() / 3;
+ if (nfaulty == 0)
+ LOG_WARN("too few replicas in the system to tolerate any failure");
+ on_init(nfaulty);
+ if (eb_loop)
+ eb.dispatch();
+}
+
+}
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..c7e1fe6
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,631 @@
+#ifndef _HOTSTUFF_CORE_H
+#define _HOTSTUFF_CORE_H
+
+#include <queue>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "salticidae/stream.h"
+#include "salticidae/util.h"
+#include "salticidae/network.h"
+#include "salticidae/msg.h"
+
+#include "promise.hpp"
+#include "util.h"
+#include "entity.h"
+#include "crypto.h"
+
+using salticidae::EventContext;
+using salticidae::Event;
+using salticidae::NetAddr;
+using salticidae::MsgNetwork;
+using salticidae::PeerNetwork;
+using salticidae::ElapsedTime;
+using salticidae::_1;
+using salticidae::_2;
+
+namespace hotstuff {
+
+const double ent_waiting_timeout = 10;
+const double double_inf = 1e10;
+
+enum {
+ PROPOSE = 0x0,
+ VOTE = 0x1,
+ QUERY_FETCH_BLK = 0x2,
+ RESP_FETCH_BLK = 0x3,
+};
+
+using promise::promise_t;
+
+struct Proposal;
+struct Vote;
+
+/** Abstraction for HotStuff protocol state machine (without network implementation). */
+class HotStuffCore {
+ block_t b0; /** the genesis block */
+ /* === state variables === */
+ /** block containing the QC for the highest block having one */
+ block_t bqc;
+ block_t bexec; /**< last executed block */
+ uint32_t vheight; /**< height of the block last voted for */
+ /* === auxilliary variables === */
+ privkey_bt priv_key; /**< private key for signing votes */
+ std::set<block_t, BlockHeightCmp> tails; /**< set of tail blocks */
+ ReplicaConfig config; /**< replica configuration */
+ /* === async event queues === */
+ std::unordered_map<block_t, promise_t> qc_waiting;
+ promise_t propose_waiting;
+
+ block_t sanity_check_delivered(const uint256_t &blk_hash);
+ void sanity_check_delivered(const block_t &blk);
+ void check_commit(const block_t &_bqc);
+ bool update(const uint256_t &bqc_hash);
+ void on_qc_finish(const block_t &blk);
+ void on_propose_(const block_t &blk);
+
+ protected:
+ ReplicaID id; /**< identity of the replica itself */
+ const int32_t parent_limit; /**< maximum number of parents */
+
+ public:
+ BoxObj<EntityStorage> storage;
+
+ HotStuffCore(ReplicaID id,
+ privkey_bt &&priv_key,
+ int32_t parent_limit);
+ virtual ~HotStuffCore() = default;
+
+ /* Inputs of the state machine triggered by external events, should called
+ * by the class user, with proper invariants. */
+
+ /** Call to initialize the protocol, should be called once before all other
+ * functions. */
+ void on_init(uint32_t nfaulty) { config.nmajority = 2 * nfaulty + 1; }
+
+ /** Call to deliver a block.
+ * A block is only delivered if itself is fetched, the block for the
+ * contained qc is fetched and all parents are delivered. The user should
+ * always ensure this invariant. The invalid blocks will be dropped by this
+ * function.
+ * @return true if valid */
+ bool on_deliver_blk(const block_t &blk);
+
+ /** Call upon the delivery of a proposal message.
+ * The block mentioned in the message should be already delivered. */
+ void on_receive_proposal(const Proposal &prop);
+
+ /** Call upon the delivery of a vote message.
+ * The block mentioned in the message should be already delivered. */
+ void on_receive_vote(const Vote &vote);
+
+ /** Call to submit new commands to be decided (executed). */
+ void on_propose(const std::vector<command_t> &cmds);
+
+ /* Functions required to construct concrete instances for abstract classes.
+ * */
+
+ /* Outputs of the state machine triggering external events. The virtual
+ * functions should be implemented by the user to specify the behavior upon
+ * the events. */
+ protected:
+ /** Called by HotStuffCore upon the decision being made for cmd. */
+ virtual void do_decide(const command_t &cmd) = 0;
+ /** Called by HotStuffCore upon broadcasting a new proposal.
+ * The user should send the proposal message to all replicas except for
+ * itself. */
+ virtual void do_broadcast_proposal(const Proposal &prop) = 0;
+ /** Called upon sending out a new vote to the next proposer. The user
+ * should send the vote message to a *good* proposer to have good liveness,
+ * while safety is always guaranteed by HotStuffCore. */
+ virtual void do_vote(ReplicaID last_proposer, const Vote &vote) = 0;
+
+ /* The user plugs in the detailed instances for those
+ * polymorphic data types. */
+ public:
+ /** Create a partial certificate that proves the vote for a block. */
+ virtual part_cert_bt create_part_cert(const PrivKey &priv_key, const uint256_t &blk_hash) = 0;
+ /** Create a partial certificate from its seralized form. */
+ virtual part_cert_bt parse_part_cert(DataStream &s) = 0;
+ /** Create a quorum certificate that proves 2f+1 votes for a block. */
+ virtual quorum_cert_bt create_quorum_cert(const uint256_t &blk_hash) = 0;
+ /** Create a quorum certificate from its serialized form. */
+ virtual quorum_cert_bt parse_quorum_cert(DataStream &s) = 0;
+ /** Create a command object from its serialized form. */
+ virtual command_t parse_cmd(DataStream &s) = 0;
+
+ public:
+ /** Add a replica to the current configuration. This should only be called
+ * before running HotStuffCore protocol. */
+ void add_replica(ReplicaID rid, const NetAddr &addr, pubkey_bt &&pub_key);
+ /** Try to prune blocks lower than last committed height - staleness. */
+ void prune(uint32_t staleness);
+
+ /* PaceMaker can use these functions to monitor the core protocol state
+ * transition */
+ /** Get a promise resolved when the block gets a QC. */
+ promise_t async_qc_finish(const block_t &blk);
+ /** Get a promise resolved when a new block is proposed. */
+ promise_t async_wait_propose();
+
+ /* Other useful functions */
+ block_t get_genesis() { return b0; }
+ const ReplicaConfig &get_config() { return config; }
+ int8_t get_cmd_decision(const uint256_t &cmd_hash);
+ ReplicaID get_id() { return id; }
+ operator std::string () const;
+};
+
+/** Abstraction for proposal messages. */
+struct Proposal: public Serializable {
+ ReplicaID proposer;
+ /** hash for the block containing the highest QC */
+ uint256_t bqc_hash;
+ /** block being proposed */
+ block_t blk;
+
+ /** handle of the core object to allow polymorphism. The user should use
+ * a pointer to the object of the class derived from HotStuffCore */
+ HotStuffCore *hsc;
+
+ Proposal(HotStuffCore *hsc): blk(nullptr), hsc(hsc) {}
+ Proposal(ReplicaID proposer,
+ const uint256_t &bqc_hash,
+ block_t &blk,
+ HotStuffCore *hsc):
+ proposer(proposer),
+ bqc_hash(bqc_hash),
+ blk(blk), hsc(hsc) {}
+
+ void serialize(DataStream &s) const override {
+ s << proposer
+ << bqc_hash
+ << *blk;
+ }
+
+ void unserialize(DataStream &s) override {
+ assert(hsc != nullptr);
+ s >> proposer
+ >> bqc_hash;
+ Block _blk;
+ _blk.unserialize(s, hsc);
+ blk = hsc->storage->add_blk(std::move(_blk));
+ }
+
+ operator std::string () const {
+ DataStream s;
+ s << "<proposal "
+ << "rid=" << std::to_string(proposer) << " "
+ << "bqc=" << get_hex10(bqc_hash) << " "
+ << "blk=" << get_hex10(blk->get_hash()) << ">";
+ return std::string(std::move(s));
+ }
+};
+
+/** Abstraction for vote messages. */
+struct Vote: public Serializable {
+ ReplicaID voter;
+ /** hash for the block containing the highest QC */
+ uint256_t bqc_hash;
+ /** block being voted */
+ uint256_t blk_hash;
+ /** proof of validity for the vote (nullptr for a negative vote) */
+ part_cert_bt cert;
+
+ /** handle of the core object to allow polymorphism */
+ HotStuffCore *hsc;
+
+ Vote(HotStuffCore *hsc): cert(nullptr), hsc(hsc) {}
+ Vote(ReplicaID voter,
+ const uint256_t &bqc_hash,
+ const uint256_t &blk_hash,
+ part_cert_bt &&cert,
+ HotStuffCore *hsc):
+ voter(voter),
+ bqc_hash(bqc_hash),
+ blk_hash(blk_hash),
+ cert(std::move(cert)), hsc(hsc) {}
+
+ Vote(const Vote &other):
+ voter(other.voter),
+ bqc_hash(other.bqc_hash),
+ blk_hash(other.blk_hash),
+ cert(other.cert->clone()),
+ hsc(other.hsc) {}
+
+ Vote(Vote &&other) = default;
+
+ void serialize(DataStream &s) const override {
+ s << voter
+ << bqc_hash
+ << blk_hash;
+ if (cert == nullptr)
+ s << (uint8_t)0;
+ else
+ s << (uint8_t)1 << *cert;
+ }
+
+ void unserialize(DataStream &s) override {
+ assert(hsc != nullptr);
+ uint8_t has_cert;
+ s >> voter
+ >> bqc_hash
+ >> blk_hash
+ >> has_cert;
+ cert = has_cert ? hsc->parse_part_cert(s) : nullptr;
+ }
+
+ bool verify() const {
+ assert(hsc != nullptr);
+ return cert->verify(hsc->get_config().get_pubkey(voter)) &&
+ cert->get_blk_hash() == blk_hash;
+ }
+
+ operator std::string () const {
+ DataStream s;
+ s << "<vote "
+ << "rid=" << std::to_string(voter) << " "
+ << "bqc=" << get_hex10(bqc_hash) << " "
+ << "blk=" << get_hex10(blk_hash) << " "
+ << "cert=" << (cert ? "yes" : "no") << ">";
+ return std::string(std::move(s));
+ }
+};
+
+/** Abstraction for liveness gadget (oracle). */
+class PaceMaker {
+ public:
+ virtual ~PaceMaker() = default;
+ /** Get a promise resolved when the pace maker thinks it is a *good* time
+ * to issue new commands. When promise is resolved with the ID of itself,
+ * the replica should propose the command, otherwise it will forward the
+ * command to the proposer indicated by the ID. */
+ virtual promise_t beat() = 0;
+ /** Get a promise resolved when the pace maker thinks it is a *good* time
+ * to vote for a block. The promise is resolved with the next proposer's ID
+ * */
+ virtual promise_t next_proposer(ReplicaID last_proposer) = 0;
+};
+
+using pacemaker_bt = BoxObj<PaceMaker>;
+
+/** A pace maker that waits for the qc of the last proposed block. */
+class PaceMakerDummy: public PaceMaker {
+ HotStuffCore *hsc;
+ std::queue<promise_t> pending_beats;
+ block_t last_proposed;
+ bool locked;
+
+ void schedule_next() {
+ if (!pending_beats.empty() && !locked)
+ {
+ auto pm = pending_beats.front();
+ pending_beats.pop();
+ hsc->async_qc_finish(last_proposed).then(
+ [id = hsc->get_id(), pm]() {
+ pm.resolve(id);
+ });
+ locked = true;
+ }
+ }
+
+ void update_last_proposed() {
+ hsc->async_wait_propose().then([this](block_t blk) {
+ update_last_proposed();
+ last_proposed = blk;
+ locked = false;
+ schedule_next();
+ });
+ }
+
+ public:
+ PaceMakerDummy(HotStuffCore *hsc):
+ hsc(hsc),
+ last_proposed(hsc->get_genesis()),
+ locked(false) {
+ update_last_proposed();
+ }
+
+ promise_t beat() override {
+ promise_t pm;
+ pending_beats.push(pm);
+ schedule_next();
+ return pm;
+ }
+
+ promise_t next_proposer(ReplicaID last_proposer) override {
+ return promise_t([last_proposer](promise_t &pm) {
+ pm.resolve(last_proposer);
+ });
+ }
+};
+
+/** Network message format for HotStuff. */
+struct MsgHotStuff: public salticidae::MsgBase<> {
+ using MsgBase::MsgBase;
+ void gen_propose(const Proposal &);
+ void parse_propose(Proposal &) const;
+
+ void gen_vote(const Vote &);
+ void parse_vote(Vote &) const;
+
+ void gen_qfetchblk(const std::vector<uint256_t> &blk_hashes);
+ void parse_qfetchblk(std::vector<uint256_t> &blk_hashes) const;
+
+ void gen_rfetchblk(const std::vector<block_t> &blks);
+ void parse_rfetchblk(std::vector<block_t> &blks, HotStuffCore *hsc) const;
+};
+
+using promise::promise_t;
+
+class HotStuffBase;
+
+template<EntityType ent_type>
+class FetchContext: public promise_t {
+ Event timeout;
+ HotStuffBase *hs;
+ MsgHotStuff fetch_msg;
+ const uint256_t ent_hash;
+ std::unordered_set<NetAddr> replica_ids;
+ inline void timeout_cb(evutil_socket_t, short);
+ public:
+ FetchContext(const FetchContext &) = delete;
+ FetchContext &operator=(const FetchContext &) = delete;
+ FetchContext(FetchContext &&other);
+
+ FetchContext(const uint256_t &ent_hash, HotStuffBase *hs);
+ ~FetchContext() {}
+
+ inline void send(const NetAddr &replica_id);
+ inline void reset_timeout();
+ inline void add_replica(const NetAddr &replica_id, bool fetch_now = true);
+};
+
+class BlockDeliveryContext: public promise_t {
+ public:
+ ElapsedTime elapsed;
+ BlockDeliveryContext &operator=(const BlockDeliveryContext &) = delete;
+ BlockDeliveryContext(const BlockDeliveryContext &other):
+ promise_t(static_cast<const promise_t &>(other)),
+ elapsed(other.elapsed) {}
+ BlockDeliveryContext(BlockDeliveryContext &&other):
+ promise_t(static_cast<const promise_t &>(other)),
+ elapsed(std::move(other.elapsed)) {}
+ template<typename Func>
+ BlockDeliveryContext(Func callback): promise_t(callback) {
+ elapsed.start();
+ }
+};
+
+
+/** HotStuff protocol (with network implementation). */
+class HotStuffBase: public HotStuffCore {
+ using BlockFetchContext = FetchContext<ENT_TYPE_BLK>;
+ using CmdFetchContext = FetchContext<ENT_TYPE_CMD>;
+ using conn_t = MsgNetwork<MsgHotStuff>::conn_t;
+
+ friend BlockFetchContext;
+ friend CmdFetchContext;
+
+ protected:
+ /** the binding address in replica network */
+ NetAddr listen_addr;
+ /** the block size */
+ size_t blk_size;
+ /** libevent handle */
+ EventContext eb;
+ pacemaker_bt pmaker;
+
+ private:
+ /** whether libevent handle is owned by itself */
+ bool eb_loop;
+ /** network stack */
+ PeerNetwork<MsgHotStuff> pn;
+#ifdef HOTSTUFF_ENABLE_BLK_PROFILE
+ BlockProfiler blk_profiler;
+#endif
+ /* queues for async tasks */
+ std::unordered_map<const uint256_t, BlockFetchContext> blk_fetch_waiting;
+ std::unordered_map<const uint256_t, BlockDeliveryContext> blk_delivery_waiting;
+ std::unordered_map<const uint256_t, CmdFetchContext> cmd_fetch_waiting;
+ std::unordered_map<const uint256_t, promise_t> decision_waiting;
+ std::queue<command_t> cmd_pending;
+
+ /* statistics */
+ uint64_t fetched;
+ uint64_t delivered;
+ mutable uint64_t nsent;
+ mutable uint64_t nrecv;
+
+ mutable uint32_t part_parent_size;
+ mutable uint32_t part_fetched;
+ mutable uint32_t part_delivered;
+ mutable uint32_t part_decided;
+ mutable uint32_t part_gened;
+ mutable double part_delivery_time;
+ mutable double part_delivery_time_min;
+ mutable double part_delivery_time_max;
+ mutable std::unordered_map<const NetAddr, uint32_t> part_fetched_replica;
+
+ void on_fetch_cmd(const command_t &cmd);
+ void on_fetch_blk(const block_t &blk);
+ void on_deliver_blk(const block_t &blk);
+
+ /** deliver consensus message: <propose> */
+ inline void propose_handler(const MsgHotStuff &, conn_t);
+ /** deliver consensus message: <vote> */
+ inline void vote_handler(const MsgHotStuff &, conn_t);
+ /** fetches full block data */
+ inline void query_fetch_blk_handler(const MsgHotStuff &, conn_t);
+ /** receives a block */
+ inline void resp_fetch_blk_handler(const MsgHotStuff &, conn_t);
+
+ void do_broadcast_proposal(const Proposal &) override;
+ void do_vote(ReplicaID, const Vote &) override;
+ void do_decide(const command_t &) override;
+
+ public:
+ HotStuffBase(uint32_t blk_size,
+ int32_t parent_limit,
+ ReplicaID rid,
+ privkey_bt &&priv_key,
+ NetAddr listen_addr,
+ EventContext eb = EventContext(),
+ pacemaker_bt pmaker = nullptr);
+
+ ~HotStuffBase();
+
+ /* the API for HotStuffBase */
+
+ /* Submit the command to be decided. */
+ void add_command(command_t cmd) {
+ cmd_pending.push(storage->add_cmd(cmd));
+ if (cmd_pending.size() >= blk_size)
+ {
+ std::vector<command_t> cmds;
+ for (uint32_t i = 0; i < blk_size; i++)
+ {
+ cmds.push_back(cmd_pending.front());
+ cmd_pending.pop();
+ }
+ pmaker->beat().then([this, cmds = std::move(cmds)]() {
+ on_propose(cmds);
+ });
+ }
+ }
+
+ void add_replica(ReplicaID idx, const NetAddr &addr, pubkey_bt &&pub_key);
+ void start(bool eb_loop = false);
+
+ size_t size() const { return pn.all_peers().size(); }
+ void print_stat() const;
+
+ /* Helper functions */
+ /** Returns a promise resolved (with command_t cmd) when Command is fetched. */
+ promise_t async_fetch_cmd(const uint256_t &cmd_hash, const NetAddr *replica_id, bool fetch_now = true);
+ /** Returns a promise resolved (with block_t blk) when Block is fetched. */
+ promise_t async_fetch_blk(const uint256_t &blk_hash, const NetAddr *replica_id, bool fetch_now = true);
+ /** Returns a promise resolved (with block_t blk) when Block is delivered (i.e. prefix is fetched). */
+ promise_t async_deliver_blk(const uint256_t &blk_hash, const NetAddr &replica_id);
+ /** Returns a promise resolved (with command_t cmd) when Command is decided. */
+ promise_t async_decide(const uint256_t &cmd_hash);
+};
+
+/** HotStuff protocol (templated by cryptographic implementation). */
+template<typename PrivKeyType = PrivKeyDummy,
+ typename PubKeyType = PubKeyDummy,
+ typename PartCertType = PartCertDummy,
+ typename QuorumCertType = QuorumCertDummy>
+class HotStuff: public HotStuffBase {
+ using HotStuffBase::HotStuffBase;
+ protected:
+
+ part_cert_bt create_part_cert(const PrivKey &priv_key, const uint256_t &blk_hash) override {
+ return new PartCertType(
+ static_cast<const PrivKeyType &>(priv_key),
+ blk_hash);
+ }
+
+ part_cert_bt parse_part_cert(DataStream &s) override {
+ PartCert *pc = new PartCertType();
+ s >> *pc;
+ return pc;
+ }
+
+ quorum_cert_bt create_quorum_cert(const uint256_t &blk_hash) override {
+ return new QuorumCertType(get_config(), blk_hash);
+ }
+
+ quorum_cert_bt parse_quorum_cert(DataStream &s) override {
+ QuorumCert *qc = new QuorumCertType();
+ s >> *qc;
+ return qc;
+ }
+
+ public:
+ HotStuff(uint32_t blk_size,
+ int32_t parent_limit,
+ ReplicaID rid,
+ const bytearray_t &raw_privkey,
+ NetAddr listen_addr,
+ EventContext eb = nullptr):
+ HotStuffBase(blk_size,
+ parent_limit,
+ rid,
+ new PrivKeyType(raw_privkey),
+ listen_addr,
+ eb) {}
+
+ void add_replica(ReplicaID idx, const NetAddr &addr, const bytearray_t &pubkey_raw) {
+ DataStream s(pubkey_raw);
+ HotStuffBase::add_replica(idx, addr, new PubKeyType(pubkey_raw));
+ }
+};
+
+using HotStuffNoSig = HotStuff<>;
+using HotStuffSecp256k1 = HotStuff<PrivKeySecp256k1, PubKeySecp256k1,
+ PartCertSecp256k1, QuorumCertSecp256k1>;
+
+template<EntityType ent_type>
+FetchContext<ent_type>::FetchContext(FetchContext && other):
+ promise_t(static_cast<const promise_t &>(other)),
+ hs(other.hs),
+ fetch_msg(std::move(other.fetch_msg)),
+ ent_hash(other.ent_hash),
+ replica_ids(std::move(other.replica_ids)) {
+ other.timeout.del();
+ timeout = Event(hs->eb, -1, 0,
+ std::bind(&FetchContext::timeout_cb, this, _1, _2));
+ reset_timeout();
+}
+
+template<>
+inline void FetchContext<ENT_TYPE_CMD>::timeout_cb(evutil_socket_t, short) {
+ HOTSTUFF_LOG_WARN("cmd fetching %.10s timeout", get_hex(ent_hash).c_str());
+ for (const auto &replica_id: replica_ids)
+ send(replica_id);
+ reset_timeout();
+}
+
+template<>
+inline void FetchContext<ENT_TYPE_BLK>::timeout_cb(evutil_socket_t, short) {
+ HOTSTUFF_LOG_WARN("block fetching %.10s timeout", get_hex(ent_hash).c_str());
+ for (const auto &replica_id: replica_ids)
+ send(replica_id);
+ reset_timeout();
+}
+
+template<EntityType ent_type>
+FetchContext<ent_type>::FetchContext(
+ const uint256_t &ent_hash, HotStuffBase *hs):
+ promise_t([](promise_t){}),
+ hs(hs), ent_hash(ent_hash) {
+ fetch_msg.gen_qfetchblk(std::vector<uint256_t>{ent_hash});
+
+ timeout = Event(hs->eb, -1, 0,
+ std::bind(&FetchContext::timeout_cb, this, _1, _2));
+ reset_timeout();
+}
+
+template<EntityType ent_type>
+void FetchContext<ent_type>::send(const NetAddr &replica_id) {
+ hs->part_fetched_replica[replica_id]++;
+ hs->pn.send_msg(fetch_msg, replica_id);
+}
+
+template<EntityType ent_type>
+void FetchContext<ent_type>::reset_timeout() {
+ timeout.add_with_timeout(salticidae::gen_rand_timeout(ent_waiting_timeout));
+}
+
+template<EntityType ent_type>
+void FetchContext<ent_type>::add_replica(const NetAddr &replica_id, bool fetch_now) {
+ if (replica_ids.empty() && fetch_now)
+ send(replica_id);
+ replica_ids.insert(replica_id);
+}
+
+}
+
+#endif
diff --git a/src/crypto.cpp b/src/crypto.cpp
new file mode 100644
index 0000000..335a521
--- /dev/null
+++ b/src/crypto.cpp
@@ -0,0 +1,25 @@
+#include "entity.h"
+#include "crypto.h"
+
+namespace hotstuff {
+
+secp256k1_context_t secp256k1_default_sign_ctx = new Secp256k1Context(true);
+secp256k1_context_t secp256k1_default_verify_ctx = new Secp256k1Context(false);
+
+QuorumCertSecp256k1::QuorumCertSecp256k1(
+ const ReplicaConfig &config, const uint256_t &blk_hash):
+ QuorumCert(), blk_hash(blk_hash), rids(config.nmajority) {
+ rids.clear();
+}
+
+bool QuorumCertSecp256k1::verify(const ReplicaConfig &config) {
+ bytearray_t _blk_hash(blk_hash);
+ if (rids.size() < config.nmajority) return false;
+ for (size_t i = 0; i < rids.size(); i++)
+ if (!sigs[i].verify(_blk_hash,
+ static_cast<const PubKeySecp256k1 &>(config.get_pubkey(rids.get(i)))))
+ return false;
+ return true;
+}
+
+}
diff --git a/src/crypto.h b/src/crypto.h
new file mode 100644
index 0000000..2fbf745
--- /dev/null
+++ b/src/crypto.h
@@ -0,0 +1,386 @@
+#ifndef _HOTSTUFF_CRYPTO_H
+#define _HOTSTUFF_CRYPTO_H
+
+#include "salticidae/crypto.h"
+#include "salticidae/ref.h"
+#include "secp256k1.h"
+#include <openssl/rand.h>
+#include "type.h"
+
+using salticidae::RcObj;
+using salticidae::BoxObj;
+
+namespace hotstuff {
+
+using salticidae::SHA256;
+
+class PubKey: public Serializable, Cloneable {
+ public:
+ virtual ~PubKey() = default;
+ virtual PubKey *clone() override = 0;
+};
+
+using pubkey_bt = BoxObj<PubKey>;
+
+class PrivKey: public Serializable {
+ public:
+ virtual ~PrivKey() = default;
+ virtual pubkey_bt get_pubkey() const = 0;
+ virtual void from_rand() = 0;
+};
+
+using privkey_bt = BoxObj<PrivKey>;
+
+class PartCert: public Serializable, public Cloneable {
+ public:
+ virtual ~PartCert() = default;
+ virtual bool verify(const PubKey &pubkey) = 0;
+ virtual const uint256_t &get_blk_hash() const = 0;
+ virtual PartCert *clone() override = 0;
+};
+
+class ReplicaConfig;
+
+class QuorumCert: public Serializable, public Cloneable {
+ public:
+ virtual ~QuorumCert() = default;
+ virtual void add_part(ReplicaID replica, const PartCert &pc) = 0;
+ virtual void compute() = 0;
+ virtual bool verify(const ReplicaConfig &config) = 0;
+ virtual const uint256_t &get_blk_hash() const = 0;
+ virtual QuorumCert *clone() override = 0;
+};
+
+using part_cert_bt = BoxObj<PartCert>;
+using quorum_cert_bt = BoxObj<QuorumCert>;
+
+class PubKeyDummy: public PubKey {
+ PubKeyDummy *clone() override { return new PubKeyDummy(*this); }
+ void serialize(DataStream &) const override {}
+ void unserialize(DataStream &) override {}
+};
+
+class PrivKeyDummy: public PrivKey {
+ pubkey_bt get_pubkey() const override { return new PubKeyDummy(); }
+ void serialize(DataStream &) const override {}
+ void unserialize(DataStream &) override {}
+ void from_rand() override {}
+};
+
+class PartCertDummy: public PartCert {
+ uint256_t blk_hash;
+ public:
+ PartCertDummy() {}
+ PartCertDummy(const uint256_t &blk_hash):
+ blk_hash(blk_hash) {}
+
+ void serialize(DataStream &s) const override {
+ s << (uint32_t)0 << blk_hash;
+ }
+
+ void unserialize(DataStream &s) override {
+ uint32_t tmp;
+ s >> tmp >> blk_hash;
+ }
+
+ PartCert *clone() override {
+ return new PartCertDummy(blk_hash);
+ }
+
+ bool verify(const PubKey &) override { return true; }
+
+ const uint256_t &get_blk_hash() const override { return blk_hash; }
+};
+
+class QuorumCertDummy: public QuorumCert {
+ uint256_t blk_hash;
+ public:
+ QuorumCertDummy() {}
+ QuorumCertDummy(const ReplicaConfig &, const uint256_t &blk_hash):
+ blk_hash(blk_hash) {}
+
+ void serialize(DataStream &s) const override {
+ s << (uint32_t)1 << blk_hash;
+ }
+
+ void unserialize(DataStream &s) override {
+ uint32_t tmp;
+ s >> tmp >> blk_hash;
+ }
+
+ QuorumCert *clone() override {
+ return new QuorumCertDummy(*this);
+ }
+
+ void add_part(ReplicaID, const PartCert &) override {}
+ void compute() override {}
+ bool verify(const ReplicaConfig &) override { return true; }
+
+ const uint256_t &get_blk_hash() const override { return blk_hash; }
+};
+
+
+class Secp256k1Context {
+ secp256k1_context *ctx;
+ friend class PubKeySecp256k1;
+ friend class SigSecp256k1;
+ public:
+ Secp256k1Context(bool sign = false):
+ ctx(secp256k1_context_create(
+ sign ? SECP256K1_CONTEXT_SIGN :
+ SECP256K1_CONTEXT_VERIFY)) {}
+
+ Secp256k1Context(const Secp256k1Context &) = delete;
+
+ Secp256k1Context(Secp256k1Context &&other): ctx(other.ctx) {
+ other.ctx = nullptr;
+ }
+
+ ~Secp256k1Context() {
+ if (ctx) secp256k1_context_destroy(ctx);
+ }
+};
+
+using secp256k1_context_t = RcObj<Secp256k1Context>;
+
+extern secp256k1_context_t secp256k1_default_sign_ctx;
+extern secp256k1_context_t secp256k1_default_verify_ctx;
+
+class PrivKeySecp256k1;
+
+class PubKeySecp256k1: public PubKey {
+ static const auto _olen = 33;
+ friend class SigSecp256k1;
+ secp256k1_pubkey data;
+ secp256k1_context_t ctx;
+
+ public:
+ PubKeySecp256k1(const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ PubKey(), ctx(ctx) {}
+
+ PubKeySecp256k1(const bytearray_t &raw_bytes,
+ const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ PubKeySecp256k1(ctx) { from_bytes(raw_bytes); }
+
+ inline PubKeySecp256k1(const PrivKeySecp256k1 &priv_key,
+ const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx);
+
+ void serialize(DataStream &s) const override {
+ static uint8_t output[_olen];
+ size_t olen = _olen;
+ (void)secp256k1_ec_pubkey_serialize(
+ ctx->ctx, (unsigned char *)output,
+ &olen, &data, SECP256K1_EC_COMPRESSED);
+ s.put_data(output, output + _olen);
+ }
+
+ void unserialize(DataStream &s) override {
+ static const auto _exc = std::invalid_argument("ill-formed public key");
+ try {
+ if (!secp256k1_ec_pubkey_parse(
+ ctx->ctx, &data, s.get_data_inplace(_olen), _olen))
+ throw _exc;
+ } catch (std::ios_base::failure &) {
+ throw _exc;
+ }
+ }
+
+ PubKeySecp256k1 *clone() override {
+ return new PubKeySecp256k1(*this);
+ }
+};
+
+class PrivKeySecp256k1: public PrivKey {
+ static const auto nbytes = 32;
+ friend class PubKeySecp256k1;
+ friend class SigSecp256k1;
+ uint8_t data[nbytes];
+ secp256k1_context_t ctx;
+
+ public:
+ PrivKeySecp256k1(const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ PrivKey(), ctx(ctx) {}
+
+ PrivKeySecp256k1(const bytearray_t &raw_bytes,
+ const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ PrivKeySecp256k1(ctx) { from_bytes(raw_bytes); }
+
+ void serialize(DataStream &s) const override {
+ s.put_data(data, data + nbytes);
+ }
+
+ void unserialize(DataStream &s) override {
+ static const auto _exc = std::invalid_argument("ill-formed private key");
+ try {
+ memmove(data, s.get_data_inplace(nbytes), nbytes);
+ } catch (std::ios_base::failure &) {
+ throw _exc;
+ }
+ }
+
+ void from_rand() override {
+ if (!RAND_bytes(data, nbytes))
+ throw std::runtime_error("cannot get rand bytes from openssl");
+ }
+
+ inline pubkey_bt get_pubkey() const override;
+};
+
+pubkey_bt PrivKeySecp256k1::get_pubkey() const {
+ return new PubKeySecp256k1(*this, ctx);
+}
+
+PubKeySecp256k1::PubKeySecp256k1(
+ const PrivKeySecp256k1 &priv_key,
+ const secp256k1_context_t &ctx): PubKey(), ctx(ctx) {
+ if (!secp256k1_ec_pubkey_create(ctx->ctx, &data, priv_key.data))
+ throw std::invalid_argument("invalid secp256k1 private key");
+}
+
+class SigSecp256k1: public Serializable {
+ secp256k1_ecdsa_signature data;
+ secp256k1_context_t ctx;
+
+ void check_msg_length(const bytearray_t &msg) {
+ if (msg.size() != 32)
+ throw std::invalid_argument("the message should be 32-bytes");
+ }
+
+ public:
+ SigSecp256k1(const secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ Serializable(), ctx(ctx) {}
+ SigSecp256k1(const uint256_t &digest,
+ const PrivKeySecp256k1 &priv_key,
+ secp256k1_context_t &ctx =
+ secp256k1_default_sign_ctx):
+ Serializable(), ctx(ctx) {
+ sign(digest, priv_key);
+ }
+
+ void serialize(DataStream &s) const override {
+ static uint8_t output[64];
+ (void)secp256k1_ecdsa_signature_serialize_compact(
+ ctx->ctx, (unsigned char *)output,
+ &data);
+ s.put_data(output, output + 64);
+ }
+
+ void unserialize(DataStream &s) override {
+ static const auto _exc = std::invalid_argument("ill-formed signature");
+ try {
+ if (!secp256k1_ecdsa_signature_parse_compact(
+ ctx->ctx, &data, s.get_data_inplace(64)))
+ throw _exc;
+ } catch (std::ios_base::failure &) {
+ throw _exc;
+ }
+ }
+
+ void sign(const bytearray_t &msg, const PrivKeySecp256k1 &priv_key) {
+ check_msg_length(msg);
+ if (!secp256k1_ecdsa_sign(
+ ctx->ctx, &data,
+ (unsigned char *)&*msg.begin(),
+ (unsigned char *)priv_key.data,
+ NULL, // default nonce function
+ NULL))
+ throw std::invalid_argument("failed to create secp256k1 signature");
+ }
+
+ bool verify(const bytearray_t &msg, const PubKeySecp256k1 &pub_key,
+ const secp256k1_context_t &_ctx) {
+ check_msg_length(msg);
+ return secp256k1_ecdsa_verify(
+ _ctx->ctx, &data,
+ (unsigned char *)&*msg.begin(),
+ &pub_key.data) == 1;
+ }
+
+ bool verify(const bytearray_t &msg, const PubKeySecp256k1 &pub_key) {
+ return verify(msg, pub_key, ctx);
+ }
+};
+
+class PartCertSecp256k1: public SigSecp256k1, public PartCert {
+ uint256_t blk_hash;
+
+ public:
+ PartCertSecp256k1() = default;
+ PartCertSecp256k1(const PrivKeySecp256k1 &priv_key, const uint256_t &blk_hash):
+ SigSecp256k1(blk_hash, priv_key),
+ PartCert(),
+ blk_hash(blk_hash) {}
+
+ bool verify(const PubKey &pub_key) override {
+ return SigSecp256k1::verify(blk_hash,
+ static_cast<const PubKeySecp256k1 &>(pub_key),
+ secp256k1_default_verify_ctx);
+ }
+
+ const uint256_t &get_blk_hash() const override { return blk_hash; }
+
+ PartCertSecp256k1 *clone() override {
+ return new PartCertSecp256k1(*this);
+ }
+
+ void serialize(DataStream &s) const override {
+ s << blk_hash;
+ this->SigSecp256k1::serialize(s);
+ }
+
+ void unserialize(DataStream &s) override {
+ s >> blk_hash;
+ this->SigSecp256k1::unserialize(s);
+ }
+};
+
+class QuorumCertSecp256k1: public QuorumCert {
+ uint256_t blk_hash;
+ salticidae::Bits rids;
+ std::vector<SigSecp256k1> sigs;
+
+ public:
+ QuorumCertSecp256k1() = default;
+ QuorumCertSecp256k1(const ReplicaConfig &config, const uint256_t &blk_hash);
+
+ void add_part(ReplicaID rid, const PartCert &pc) override {
+ if (pc.get_blk_hash() != blk_hash)
+ throw std::invalid_argument("PartCert does match the block hash");
+ if (!rids.get(rid))
+ {
+ rids.set(rid);
+ sigs.push_back(static_cast<const PartCertSecp256k1 &>(pc));
+ }
+ }
+
+ void compute() override {}
+
+ bool verify(const ReplicaConfig &config) override;
+
+ const uint256_t &get_blk_hash() const override { return blk_hash; }
+
+ QuorumCertSecp256k1 *clone() override {
+ return new QuorumCertSecp256k1(*this);
+ }
+
+ void serialize(DataStream &s) const override {
+ s << blk_hash << rids;
+ for (const auto &sig: sigs) s << sig;
+ }
+
+ void unserialize(DataStream &s) override {
+ s >> blk_hash >> rids;
+ sigs.resize(rids.size());
+ for (auto &sig: sigs) s >> sig;
+ }
+};
+
+}
+
+#endif
diff --git a/src/entity.cpp b/src/entity.cpp
new file mode 100644
index 0000000..1294484
--- /dev/null
+++ b/src/entity.cpp
@@ -0,0 +1,35 @@
+#include "entity.h"
+#include "core.h"
+
+namespace hotstuff {
+
+void Block::serialize(DataStream &s) const {
+ s << (uint32_t)parent_hashes.size();
+ for (const auto &hash: parent_hashes)
+ s << hash;
+ s << (uint32_t)cmds.size();
+ for (auto cmd: cmds)
+ s << *cmd;
+ if (qc == nullptr)
+ s << (uint8_t)0;
+ else
+ s << (uint8_t)1 << *qc;
+}
+
+void Block::unserialize(DataStream &s, HotStuffCore *hsc) {
+ uint32_t n;
+ uint8_t has_qc;
+ s >> n;
+ parent_hashes.resize(n);
+ for (auto &hash: parent_hashes)
+ s >> hash;
+ s >> n;
+ cmds.resize(n);
+ for (auto &cmd: cmds)
+ cmd = hsc->parse_cmd(s);
+ s >> has_qc;
+ qc = has_qc ? hsc->parse_quorum_cert(s) : nullptr;
+ this->hash = salticidae::get_hash(*this);
+}
+
+}
diff --git a/src/entity.h b/src/entity.h
new file mode 100644
index 0000000..b3a0df4
--- /dev/null
+++ b/src/entity.h
@@ -0,0 +1,309 @@
+#ifndef _HOTSTUFF_ENT_H
+#define _HOTSTUFF_ENT_H
+
+#include <vector>
+#include <unordered_map>
+#include <unordered_set>
+#include <string>
+#include <cstddef>
+#include <ios>
+
+#include "salticidae/netaddr.h"
+#include "salticidae/ref.h"
+#include "type.h"
+#include "util.h"
+#include "crypto.h"
+
+namespace hotstuff {
+
+enum EntityType {
+ ENT_TYPE_CMD = 0x0,
+ ENT_TYPE_BLK = 0x1
+};
+
+struct ReplicaInfo {
+ ReplicaID id;
+ salticidae::NetAddr addr;
+ pubkey_bt pubkey;
+
+ ReplicaInfo(ReplicaID id,
+ const salticidae::NetAddr &addr,
+ pubkey_bt &&pubkey):
+ id(id), addr(addr), pubkey(std::move(pubkey)) {}
+
+ ReplicaInfo(const ReplicaInfo &other):
+ id(other.id), addr(other.addr),
+ pubkey(other.pubkey->clone()) {}
+
+ ReplicaInfo(ReplicaInfo &&other):
+ id(other.id), addr(other.addr),
+ pubkey(std::move(other.pubkey)) {}
+};
+
+class ReplicaConfig {
+ std::unordered_map<ReplicaID, ReplicaInfo> replica_map;
+
+ public:
+ size_t nmajority;
+
+ void add_replica(ReplicaID rid, const ReplicaInfo &info) {
+ replica_map.insert(std::make_pair(rid, info));
+ }
+
+ const ReplicaInfo &get_info(ReplicaID rid) const {
+ auto it = replica_map.find(rid);
+ if (it == replica_map.end())
+ throw HotStuffError("rid %s not found",
+ get_hex(rid).c_str());
+ return it->second;
+ }
+
+ const PubKey &get_pubkey(ReplicaID rid) const {
+ return *(get_info(rid).pubkey);
+ }
+
+ const salticidae::NetAddr &get_addr(ReplicaID rid) const {
+ return get_info(rid).addr;
+ }
+};
+
+class Block;
+class HotStuffCore;
+
+using block_t = salticidae::RcObj<Block>;
+using block_weak_t = salticidae::WeakObj<Block>;
+
+struct Finality: public Serializable {
+ int8_t decision;
+ uint256_t blk_hash;
+
+ public:
+ Finality(): decision(0) {}
+ Finality(int8_t decision, uint256_t blk_hash):
+ decision(decision), blk_hash(blk_hash) {}
+
+ void serialize(DataStream &s) const override {
+ s << decision;
+ if (decision == 1) s << blk_hash;
+ }
+
+ void unserialize(DataStream &s) override {
+ s >> decision;
+ if (decision == 1) s >> blk_hash;
+ }
+};
+
+class Command: public Serializable {
+ friend HotStuffCore;
+ block_weak_t container;
+ public:
+ virtual ~Command() = default;
+ virtual const uint256_t &get_hash() const = 0;
+ virtual bool verify() const = 0;
+ inline int8_t get_decision() const;
+ inline Finality get_finality() const;
+ block_t get_container() const {
+ return container;
+ }
+};
+
+using command_t = RcObj<Command>;
+
+template<typename Hashable>
+inline static std::vector<uint256_t>
+get_hashes(const std::vector<Hashable> &plist) {
+ std::vector<uint256_t> hashes;
+ for (const auto &p: plist)
+ hashes.push_back(p->get_hash());
+ return std::move(hashes);
+}
+
+class Block {
+ friend HotStuffCore;
+ std::vector<uint256_t> parent_hashes;
+ std::vector<command_t> cmds;
+ quorum_cert_bt qc;
+ uint256_t hash;
+
+ std::vector<block_t> parents;
+ block_t qc_ref;
+ quorum_cert_bt self_qc;
+ uint32_t height;
+ bool delivered;
+ int8_t decision;
+
+ std::unordered_set<ReplicaID> voted;
+
+ public:
+ Block():
+ qc(nullptr),
+ qc_ref(nullptr),
+ self_qc(nullptr), height(0),
+ delivered(false), decision(0) {}
+
+ Block(bool delivered, int8_t decision):
+ qc(nullptr),
+ hash(salticidae::get_hash(*this)),
+ qc_ref(nullptr),
+ self_qc(nullptr), height(0),
+ delivered(delivered), decision(decision) {}
+
+ Block(const std::vector<block_t> &parents,
+ const std::vector<command_t> &cmds,
+ uint32_t height,
+ quorum_cert_bt &&qc,
+ const block_t &qc_ref,
+ quorum_cert_bt &&self_qc,
+ int8_t decision = 0):
+ parent_hashes(get_hashes(parents)),
+ cmds(cmds),
+ qc(std::move(qc)),
+ hash(salticidae::get_hash(*this)),
+ parents(parents),
+ qc_ref(qc_ref),
+ self_qc(std::move(self_qc)),
+ height(height),
+ delivered(0),
+ decision(decision) {}
+
+ void serialize(DataStream &s) const;
+
+ void unserialize(DataStream &s, HotStuffCore *hsc);
+
+ const std::vector<command_t> &get_cmds() const {
+ return cmds;
+ }
+
+ const std::vector<block_t> &get_parents() const {
+ return parents;
+ }
+
+ const std::vector<uint256_t> &get_parent_hashes() const {
+ return parent_hashes;
+ }
+
+ const uint256_t &get_hash() const { return hash; }
+
+ bool verify(const ReplicaConfig &config) const {
+ if (qc && !qc->verify(config)) return false;
+ for (auto cmd: cmds)
+ if (!cmd->verify()) return false;
+ return true;
+ }
+
+ int8_t get_decision() const { return decision; }
+
+ bool is_delivered() const { return delivered; }
+
+ uint32_t get_height() const { return height; }
+
+ const quorum_cert_bt &get_qc() const { return qc; }
+
+ operator std::string () const {
+ DataStream s;
+ s << "<block "
+ << "id=" << get_hex10(hash) << " "
+ << "height=" << std::to_string(height) << " "
+ << "parent=" << get_hex10(parent_hashes[0]) << ">";
+ return std::string(std::move(s));
+ }
+};
+
+struct BlockHeightCmp {
+ bool operator()(const block_t &a, const block_t &b) const {
+ return a->get_height() < b->get_height();
+ }
+};
+
+int8_t Command::get_decision() const {
+ block_t cptr = container;
+ return cptr ? cptr->get_decision() : 0;
+}
+
+Finality Command::get_finality() const {
+ block_t blk = get_container();
+ return Finality(get_decision(),
+ blk ? blk->get_hash() : uint256_t());
+}
+
+class EntityStorage {
+ std::unordered_map<const uint256_t, block_t> blk_cache;
+ std::unordered_map<const uint256_t, command_t> cmd_cache;
+ public:
+ bool is_blk_delivered(const uint256_t &blk_hash) {
+ auto it = blk_cache.find(blk_hash);
+ if (it == blk_cache.end()) return false;
+ return it->second->is_delivered();
+ }
+
+ bool is_blk_fetched(const uint256_t &blk_hash) {
+ return blk_cache.count(blk_hash);
+ }
+
+ const block_t &add_blk(Block &&_blk) {
+ block_t blk = new Block(std::move(_blk));
+ return blk_cache.insert(std::make_pair(blk->get_hash(), blk)).first->second;
+ }
+
+ const block_t &add_blk(const block_t &blk) {
+ return blk_cache.insert(std::make_pair(blk->get_hash(), blk)).first->second;
+ }
+
+ block_t find_blk(const uint256_t &blk_hash) {
+ auto it = blk_cache.find(blk_hash);
+ return it == blk_cache.end() ? nullptr : it->second;
+ }
+
+ bool is_cmd_fetched(const uint256_t &cmd_hash) {
+ return cmd_cache.count(cmd_hash);
+ }
+
+ const command_t &add_cmd(const command_t &cmd) {
+ return cmd_cache.insert(std::make_pair(cmd->get_hash(), cmd)).first->second;
+ }
+
+ command_t find_cmd(const uint256_t &cmd_hash) {
+ auto it = cmd_cache.find(cmd_hash);
+ return it == cmd_cache.end() ? nullptr: it->second;
+ }
+
+ size_t get_cmd_cache_size() {
+ return cmd_cache.size();
+ }
+ size_t get_blk_cache_size() {
+ return blk_cache.size();
+ }
+
+ bool try_release_cmd(const command_t &cmd) {
+ if (cmd.get_cnt() == 2) /* only referred by cmd and the storage */
+ {
+ const auto &cmd_hash = cmd->get_hash();
+ cmd_cache.erase(cmd_hash);
+ return true;
+ }
+ return false;
+ }
+
+ bool try_release_blk(const block_t &blk) {
+ if (blk.get_cnt() == 2) /* only referred by blk and the storage */
+ {
+ const auto &blk_hash = blk->get_hash();
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ HOTSTUFF_LOG_INFO("releasing blk %.10s", get_hex(blk_hash).c_str());
+#endif
+ for (const auto &cmd: blk->get_cmds())
+ try_release_cmd(cmd);
+ blk_cache.erase(blk_hash);
+ return true;
+ }
+#ifdef HOTSTUFF_ENABLE_LOG_PROTO
+ else
+ HOTSTUFF_LOG_INFO("cannot release (%lu)", blk.get_cnt());
+#endif
+ return false;
+ }
+};
+
+}
+
+#endif
diff --git a/src/hotstuff.cpp b/src/hotstuff.cpp
new file mode 100644
index 0000000..b10f103
--- /dev/null
+++ b/src/hotstuff.cpp
@@ -0,0 +1,287 @@
+#include <iostream>
+#include <cstring>
+#include <cassert>
+#include <algorithm>
+#include <random>
+#include <unistd.h>
+#include <signal.h>
+#include <event2/event.h>
+
+#include "salticidae/stream.h"
+#include "salticidae/util.h"
+#include "salticidae/network.h"
+#include "salticidae/msg.h"
+#include "promise.hpp"
+#include "type.h"
+#include "core.h"
+#include "entity.h"
+#include "util.h"
+#include "client.h"
+
+using promise::promise_t;
+using salticidae::NetAddr;
+using salticidae::MsgNetwork;
+using salticidae::ClientNetwork;
+using salticidae::Event;
+using salticidae::ElapsedTime;
+using salticidae::Config;
+using salticidae::_1;
+using salticidae::_2;
+using salticidae::static_pointer_cast;
+using salticidae::get_hash;
+using salticidae::trim_all;
+using salticidae::split;
+
+using hotstuff::HotStuffError;
+using hotstuff::CommandDummy;
+using hotstuff::Finality;
+using hotstuff::command_t;
+using hotstuff::uint256_t;
+using hotstuff::bytearray_t;
+using hotstuff::DataStream;
+using hotstuff::ReplicaID;
+using hotstuff::MsgClient;
+
+using HotStuff = hotstuff::HotStuffSecp256k1;
+
+#define LOG_INFO HOTSTUFF_LOG_INFO
+#define LOG_DEBUG HOTSTUFF_LOG_DEBUG
+#define LOG_WARN HOTSTUFF_LOG_WARN
+#define LOG_ERROR HOTSTUFF_LOG_ERROR
+
+class HotStuffApp;
+
+class HotStuffApp: public HotStuff {
+ double stat_period;
+ /** libevent handle */
+ EventContext eb;
+ /** network messaging between a replica and its client */
+ ClientNetwork<MsgClient> cn;
+ /** timer object to schedule a periodic printing of system statistics */
+ Event ev_stat_timer;
+ /** the binding address for client RPC */
+ NetAddr clisten_addr;
+
+ using conn_client_t = MsgNetwork<MsgClient>::conn_t;
+
+ /** Client */
+ /** submits a new command */
+ inline void client_request_cmd_handler(const MsgClient &, conn_client_t);
+ /** checks if a cmd is decided */
+ inline void client_check_cmd_handler(const MsgClient &, conn_client_t);
+
+ /** The callback function to print stat */
+ inline void print_stat_cb(evutil_socket_t, short);
+
+ command_t parse_cmd(DataStream &s) override {
+ auto cmd = new CommandDummy();
+ s >> *cmd;
+ return cmd;
+ }
+
+ public:
+ HotStuffApp(uint32_t blk_size,
+ int32_t parent_limit,
+ double stat_period,
+ ReplicaID idx,
+ const bytearray_t &raw_privkey,
+ NetAddr plisten_addr,
+ NetAddr clisten_addr,
+ const EventContext &eb);
+
+ void start();
+};
+
+
+std::pair<std::string, std::string> split_ip_port_cport(const std::string &s) {
+ auto ret = trim_all(split(s, ";"));
+ if (ret.size() != 2)
+ throw std::invalid_argument("invalid cport format");
+ return std::make_pair(ret[0], ret[1]);
+}
+
+void signal_handler(int) {
+ throw HotStuffError("got terminal signal");
+}
+
+BoxObj<HotStuffApp> papp = nullptr;
+
+int main(int argc, char **argv) {
+ Config config("hotstuff.conf");
+
+ ElapsedTime elapsed;
+ elapsed.start();
+
+ signal(SIGTERM, signal_handler);
+ signal(SIGINT, signal_handler);
+
+ auto opt_blk_size = Config::OptValInt::create(1);
+ auto opt_parent_limit = Config::OptValInt::create(-1);
+ auto opt_stat_period = Config::OptValDouble::create(10);
+ auto opt_replicas = Config::OptValStrVec::create();
+ auto opt_idx = Config::OptValInt::create(0);
+ auto opt_client_port = Config::OptValInt::create(-1);
+ auto opt_privkey = Config::OptValStr::create();
+ auto opt_help = Config::OptValFlag::create(false);
+
+ config.add_opt("block-size", opt_blk_size, Config::SET_VAL);
+ config.add_opt("parent-limit", opt_parent_limit, Config::SET_VAL);
+ config.add_opt("stat-period", opt_stat_period, Config::SET_VAL);
+ config.add_opt("replica", opt_replicas, Config::APPEND);
+ config.add_opt("idx", opt_idx, Config::SET_VAL);
+ config.add_opt("cport", opt_client_port, Config::SET_VAL);
+ config.add_opt("privkey", opt_privkey, Config::SET_VAL);
+ config.add_opt("help", opt_help, Config::SWITCH_ON, 'h', "show this help info");
+
+ EventContext eb;
+#ifndef HOTSTUFF_ENABLE_LOG_DEBUG
+ try {
+#endif
+ config.parse(argc, argv);
+ if (opt_help->get())
+ {
+ config.print_help();
+ exit(0);
+ }
+ auto idx = opt_idx->get();
+ auto client_port = opt_client_port->get();
+ std::vector<std::pair<std::string, std::string>> replicas;
+ for (const auto &s: opt_replicas->get())
+ {
+ auto res = trim_all(split(s, ","));
+ if (res.size() != 2)
+ throw HotStuffError("invalid replica info");
+ replicas.push_back(std::make_pair(res[0], res[1]));
+ }
+
+ if (!(0 <= idx && (size_t)idx < replicas.size()))
+ throw HotStuffError("replica idx out of range");
+ std::string binding_addr = replicas[idx].first;
+ if (client_port == -1)
+ {
+ auto p = split_ip_port_cport(binding_addr);
+ size_t idx;
+ try {
+ client_port = stoi(p.second, &idx);
+ } catch (std::invalid_argument &) {
+ throw HotStuffError("client port not specified");
+ }
+ }
+
+ NetAddr plisten_addr{split_ip_port_cport(binding_addr).first};
+
+ papp = new HotStuffApp(opt_blk_size->get(),
+ opt_parent_limit->get(),
+ opt_stat_period->get(),
+ idx,
+ hotstuff::from_hex(opt_privkey->get()),
+ plisten_addr,
+ NetAddr("0.0.0.0", client_port),
+ eb);
+ for (size_t i = 0; i < replicas.size(); i++)
+ {
+ auto p = split_ip_port_cport(replicas[i].first);
+ papp->add_replica(i, NetAddr(p.first),
+ hotstuff::from_hex(replicas[i].second));
+ }
+ papp->start();
+#ifndef HOTSTUFF_ENABLE_LOG_DEBUG
+ } catch (std::exception &e) {
+ HOTSTUFF_LOG_INFO("exception: %s", e.what());
+ elapsed.stop(true);
+ }
+#endif
+ return 0;
+}
+
+HotStuffApp::HotStuffApp(uint32_t blk_size,
+ int32_t parent_limit,
+ double stat_period,
+ ReplicaID idx,
+ const bytearray_t &raw_privkey,
+ NetAddr plisten_addr,
+ NetAddr clisten_addr,
+ const EventContext &eb):
+ HotStuff(blk_size, parent_limit, idx, raw_privkey,
+ plisten_addr, eb),
+ stat_period(stat_period),
+ eb(eb),
+ cn(eb),
+ clisten_addr(clisten_addr) {
+ /* register the handlers for msg from clients */
+ cn.reg_handler(hotstuff::REQ_CMD, std::bind(&HotStuffApp::client_request_cmd_handler, this, _1, _2));
+ cn.reg_handler(hotstuff::CHK_CMD, std::bind(&HotStuffApp::client_check_cmd_handler, this, _1, _2));
+ cn.init(clisten_addr);
+}
+
+void HotStuffApp::client_request_cmd_handler(const MsgClient &msg, conn_client_t conn_) {
+ auto conn = static_pointer_cast<ClientNetwork<MsgClient>::Conn>(conn_);
+ const NetAddr addr = conn->get_addr();
+ command_t cmd = new CommandDummy();
+ std::vector<promise_t> pms;
+ msg.parse_reqcmd(static_cast<CommandDummy &>(*cmd));
+
+ bool flag = true;
+#ifndef HOTSTUFF_DISABLE_TX_VERIFY
+ flag &= cmd->verify();
+#endif
+ if (!flag)
+ {
+ LOG_WARN("invalid client cmd");
+ MsgClient resp;
+ resp.gen_respcmd(cmd->get_hash(), Finality(-1, uint256_t()));
+ cn.send_msg(resp, addr);
+ }
+ else
+ {
+ const uint256_t cmd_hash = cmd->get_hash();
+ add_command(cmd);
+ /** wait for the decision of tx */
+ LOG_DEBUG("processing client cmd %.10s", get_hex(cmd_hash).c_str());
+ async_decide(cmd_hash).then([this, addr](command_t cmd) {
+ MsgClient resp;
+ resp.gen_respcmd(cmd->get_hash(), cmd->get_finality());
+ cn.send_msg(resp, addr);
+ });
+ }
+}
+
+void HotStuffApp::client_check_cmd_handler(const MsgClient &msg, conn_client_t conn_) {
+ auto conn = static_pointer_cast<ClientNetwork<MsgClient>::Conn>(conn_);
+ const NetAddr addr = conn->get_addr();
+ uint256_t cmd_hash;
+ msg.parse_chkcmd(cmd_hash);
+ MsgClient resp;
+ command_t cmd = storage->find_cmd(cmd_hash);
+ Finality fin;
+ if (cmd) fin = cmd->get_finality();
+ resp.gen_respcmd(cmd_hash, fin);
+ cn.send_msg(resp, addr);
+}
+
+
+void HotStuffApp::start() {
+ ev_stat_timer = Event(eb, -1, 0,
+ std::bind(&HotStuffApp::print_stat_cb, this, _1, _2));
+ ev_stat_timer.add_with_timeout(stat_period);
+ LOG_INFO("** starting the system with parameters **");
+ LOG_INFO("blk_size = %lu", blk_size);
+ LOG_INFO("parent_limit = %d", parent_limit);
+ LOG_INFO("conns = %lu", HotStuff::size());
+ LOG_INFO("** starting the event loop...");
+#ifdef HOTSTUFF_DISABLE_TX_VERIFY
+ LOG_INFO("!! verification disabled !!");
+#else
+ LOG_INFO("** verification enabled **");
+#endif
+ HotStuff::start();
+ /* enter the event main loop */
+ eb.dispatch();
+}
+
+
+void HotStuffApp::print_stat_cb(evutil_socket_t, short) {
+ HotStuff::print_stat();
+ HotStuffCore::prune(100);
+ ev_stat_timer.add_with_timeout(stat_period);
+}
diff --git a/src/hotstuff_client.cpp b/src/hotstuff_client.cpp
new file mode 100644
index 0000000..5c04e5f
--- /dev/null
+++ b/src/hotstuff_client.cpp
@@ -0,0 +1,181 @@
+#include <cassert>
+#include "salticidae/type.h"
+#include "salticidae/netaddr.h"
+#include "salticidae/network.h"
+#include "salticidae/util.h"
+#include "util.h"
+#include "type.h"
+#include "client.h"
+
+using salticidae::NetAddr;
+using salticidae::Config;
+using salticidae::ElapsedTime;
+using salticidae::EventContext;
+using salticidae::Event;
+using salticidae::bytearray_t;
+using salticidae::trim_all;
+using salticidae::split;
+
+using hotstuff::uint256_t;
+using hotstuff::MsgClient;
+using hotstuff::CommandDummy;
+using hotstuff::Finality;
+
+size_t max_async_num = 10;
+int max_iter_num = 100;
+
+struct Request {
+ ElapsedTime et;
+ Request() { et.start(); }
+};
+
+std::unordered_map<int, salticidae::RingBuffer> buffers;
+std::unordered_map<const uint256_t, Request> waiting;
+
+int connect(const NetAddr &node) {
+ int fd;
+ if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
+ assert(0);
+ struct sockaddr_in sockin;
+ memset(&sockin, 0, sizeof(struct sockaddr_in));
+ sockin.sin_family = AF_INET;
+ sockin.sin_addr.s_addr = node.ip;
+ sockin.sin_port = node.port;
+ if (connect(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) == -1)
+ assert(0);
+ HOTSTUFF_LOG_INFO("connected to %s", std::string(node).c_str());
+ return fd;
+}
+
+void write_msg(int fd, const MsgClient &msg) {
+ bytearray_t msg_data = msg.serialize();
+ if (write(fd, msg_data.data(), msg_data.size()) != (ssize_t)msg_data.size())
+ assert(0);
+}
+
+void read_msg(int fd, MsgClient &msg) {
+ static const size_t BUFF_SEG_SIZE = 1024;
+ ssize_t ret;
+ auto &buffer = buffers[fd];
+ bool read_body = false;
+ for (;;)
+ {
+ bytearray_t buff_seg;
+ if (!read_body && buffer.size() >= MsgClient::header_size)
+ {
+ buff_seg = buffer.pop(MsgClient::header_size);
+ msg = MsgClient(buff_seg.data());
+ read_body = true;
+ }
+ if (read_body && buffer.size() >= msg.get_length())
+ {
+ buff_seg = buffer.pop(msg.get_length());
+ msg.set_payload(std::move(buff_seg));
+ return;
+ }
+
+ buff_seg.resize(BUFF_SEG_SIZE);
+ ret = read(fd, buff_seg.data(), BUFF_SEG_SIZE);
+ assert(ret != -1);
+ if (ret > 0)
+ {
+ buff_seg.resize(ret);
+ buffer.push(std::move(buff_seg));
+ }
+ }
+}
+
+void try_send(int fd) {
+ while (waiting.size() < max_async_num && max_iter_num)
+ {
+ auto cmd = CommandDummy::make_cmd();
+ MsgClient msg;
+ msg.gen_reqcmd(*cmd);
+ write_msg(fd, msg);
+ HOTSTUFF_LOG_INFO("send new cmd %.10s",
+ get_hex(cmd->get_hash()).c_str());
+ waiting.insert(std::make_pair(
+ cmd->get_hash(), Request()));
+ if (max_iter_num > 0)
+ max_iter_num--;
+ }
+}
+
+void on_receive(int fd) {
+ MsgClient msg;
+ uint256_t cmd_hash;
+ Finality fin;
+ read_msg(fd, msg);
+ HOTSTUFF_LOG_DEBUG("got %s", std::string(msg).c_str());
+ if (!msg.verify_checksum())
+ HOTSTUFF_LOG_ERROR("incorrect checksum %08x", msg.get_checksum());
+ msg.parse_respcmd(cmd_hash, fin);
+ HOTSTUFF_LOG_INFO(
+ "fd %d got response for %.10s: <decision=%d, blk=%.10s>",
+ fd, get_hex(cmd_hash).c_str(),
+ fin.decision,
+ get_hex(fin.blk_hash).c_str());
+ auto it = waiting.find(cmd_hash);
+ if (it == waiting.end()) return;
+ waiting.erase(it);
+ try_send(fd);
+}
+
+std::pair<std::string, std::string> split_ip_port_cport(const std::string &s) {
+ auto ret = trim_all(split(s, ";"));
+ return std::make_pair(ret[0], ret[1]);
+}
+
+Event *on_receive_ev;
+
+int main(int argc, char **argv) {
+ Config config("hotstuff.conf");
+ std::vector<NetAddr> peers2;
+ EventContext eb;
+ auto opt_idx = Config::OptValInt::create(-1);
+ auto opt_server_addr = Config::OptValStr::create("127.0.0.1:2234");
+ auto opt_replicas = Config::OptValStrVec::create();
+ auto opt_max_iter_num = Config::OptValInt::create();
+
+ try {
+ config.add_opt("idx", opt_idx, Config::SET_VAL);
+ config.add_opt("server", opt_server_addr, Config::SET_VAL);
+ config.add_opt("replica", opt_replicas, Config::APPEND);
+ config.add_opt("ntx", opt_max_iter_num, Config::SET_VAL);
+ config.parse(argc, argv);
+ auto idx = opt_idx->get();
+ max_iter_num = opt_max_iter_num->get();
+ std::vector<std::pair<std::string, std::string>> replicas;
+ for (const auto &s: opt_replicas->get())
+ {
+ auto res = trim_all(split(s, ","));
+ assert(res.size() == 2);
+ replicas.push_back(std::make_pair(res[0], res[1]));
+ }
+
+ NetAddr server(opt_server_addr->get());
+ if (-1 < idx && (size_t)idx < replicas.size() &&
+ replicas.size() > 0)
+ {
+ for (const auto &p: replicas)
+ {
+ auto _p = split_ip_port_cport(p.first);
+ size_t _;
+ peers2.push_back(NetAddr(NetAddr(_p.first).ip, htons(stoi(_p.second, &_))));
+ }
+ server = peers2[idx];
+ }
+
+ int fd = connect(server);
+ on_receive_ev = new Event{eb, fd, EV_READ, [](int fd, short) {
+ on_receive(fd);
+ on_receive_ev->add();
+ }};
+ on_receive_ev->add();
+ try_send(fd);
+ eb.dispatch();
+ } catch (hotstuff::HotStuffError &e) {
+ HOTSTUFF_LOG_ERROR("exception: %s", std::string(e).c_str());
+ }
+ return 0;
+}
diff --git a/src/hotstuff_keygen.cpp b/src/hotstuff_keygen.cpp
new file mode 100644
index 0000000..7a7e615
--- /dev/null
+++ b/src/hotstuff_keygen.cpp
@@ -0,0 +1,33 @@
+#include <error.h>
+#include "salticidae/util.h"
+#include "crypto.h"
+
+using salticidae::Config;
+using hotstuff::privkey_bt;
+using hotstuff::pubkey_bt;
+
+int main(int argc, char **argv) {
+ Config config("hotstuff.conf");
+ privkey_bt priv_key;
+ auto opt_n = Config::OptValInt::create(1);
+ auto opt_algo = Config::OptValStr::create("secp256k1");
+ config.add_opt("num", opt_n, Config::SET_VAL);
+ config.add_opt("algo", opt_algo, Config::SET_VAL);
+ config.parse(argc, argv);
+ auto &algo = opt_algo->get();
+ if (algo == "secp256k1")
+ priv_key = new hotstuff::PrivKeySecp256k1();
+ else
+ error(1, 0, "algo not supported");
+ int n = opt_n->get();
+ if (n < 1)
+ error(1, 0, "n must be >0");
+ while (n--)
+ {
+ priv_key->from_rand();
+ pubkey_bt pub_key = priv_key->get_pubkey();
+ printf("pub:%s sec:%s\n", get_hex(*pub_key).c_str(),
+ get_hex(*priv_key).c_str());
+ }
+ return 0;
+}
diff --git a/src/promise.hpp b/src/promise.hpp
new file mode 100644
index 0000000..593d5c1
--- /dev/null
+++ b/src/promise.hpp
@@ -0,0 +1,745 @@
+#ifndef _CPPROMISE_HPP
+#define _CPPROMISE_HPP
+
+/**
+ * MIT License
+ * Copyright (c) 2018 Ted Yin <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stack>
+#include <vector>
+#include <memory>
+#include <functional>
+#include <type_traits>
+
+#if __cplusplus >= 201703L
+#ifdef __has_include
+# if __has_include(<any>)
+# include <any>
+# ifdef __cpp_lib_any
+# define _CPPROMISE_STD_ANY
+# endif
+# endif
+#endif
+#endif
+
+#ifndef _CPPROMISE_STD_ANY
+#include <boost/any.hpp>
+#endif
+
+/**
+ * Implement type-safe Promise primitives similar to the ones specified by
+ * Javascript Promise/A+.
+ */
+namespace promise {
+#ifdef _CPPROMISE_STD_ANY
+ using pm_any_t = std::any;
+ template<typename T>
+ constexpr auto any_cast = static_cast<T(*)(const std::any&)>(std::any_cast<T>);
+ using bad_any_cast = std::bad_any_cast;
+#else
+# warning "using boost::any"
+# pragma message "using boost::any"
+ using pm_any_t = boost::any;
+ template<typename T>
+ constexpr auto any_cast = static_cast<T(*)(const boost::any&)>(boost::any_cast<T>);
+ using bad_any_cast = boost::bad_any_cast;
+#endif
+ using callback_t = std::function<void()>;
+ using values_t = std::vector<pm_any_t>;
+
+ /* match lambdas */
+ template<typename T>
+ struct function_traits:
+ public function_traits<decltype(&T::operator())> {};
+
+ template<typename ReturnType>
+ struct function_traits<ReturnType()> {
+ using ret_type = ReturnType;
+ using arg_type = void;
+ using empty_arg = void;
+ };
+
+ /* match plain functions */
+ template<typename ReturnType, typename ArgType>
+ struct function_traits<ReturnType(ArgType)> {
+ using ret_type = ReturnType;
+ using arg_type = ArgType;
+ using non_empty_arg = void;
+ };
+
+ /* match function pointers */
+ template<typename ReturnType, typename... ArgType>
+ struct function_traits<ReturnType(*)(ArgType...)>:
+ public function_traits<ReturnType(ArgType...)> {};
+
+ /* match const member functions */
+ template<typename ClassType, typename ReturnType, typename... ArgType>
+ struct function_traits<ReturnType(ClassType::*)(ArgType...) const>:
+ public function_traits<ReturnType(ArgType...)> {};
+
+ /* match member functions */
+ template<typename ClassType, typename ReturnType, typename... ArgType>
+ struct function_traits<ReturnType(ClassType::*)(ArgType...)>:
+ public function_traits<ReturnType(ArgType...)> {};
+
+ template<typename Func, typename ReturnType>
+ using enable_if_return = typename std::enable_if<
+ std::is_same<typename function_traits<Func>::ret_type,
+ ReturnType>::value>;
+
+ template<typename Func, typename ReturnType>
+ using disable_if_return = typename std::enable_if<
+ !std::is_same<typename function_traits<Func>::ret_type,
+ ReturnType>::value>;
+
+ template<typename Func, typename ArgType>
+ using enable_if_arg = typename std::enable_if<
+ std::is_same<typename function_traits<Func>::arg_type,
+ ArgType>::value>;
+
+ template<typename Func, typename ArgType>
+ using disable_if_arg = typename std::enable_if<
+ !std::is_same<typename function_traits<Func>::arg_type,
+ ArgType>::value>;
+
+ class Promise;
+ //class promise_t: public std::shared_ptr<Promise> {
+ class promise_t {
+ Promise *pm;
+ size_t *ref_cnt;
+ public:
+ friend Promise;
+ template<typename PList> friend promise_t all(const PList &promise_list);
+ template<typename PList> friend promise_t race(const PList &promise_list);
+
+ inline promise_t();
+ inline ~promise_t();
+ template<typename Func> inline promise_t(Func callback);
+
+ void swap(promise_t &other) {
+ std::swap(pm, other.pm);
+ std::swap(ref_cnt, other.ref_cnt);
+ }
+
+ promise_t &operator=(const promise_t &other) {
+ if (this != &other)
+ {
+ promise_t tmp(other);
+ tmp.swap(*this);
+ }
+ return *this;
+ }
+
+ promise_t &operator=(promise_t &&other) {
+ if (this != &other)
+ {
+ promise_t tmp(std::move(other));
+ tmp.swap(*this);
+ }
+ return *this;
+ }
+
+ promise_t(const promise_t &other):
+ pm(other.pm),
+ ref_cnt(other.ref_cnt) {
+ ++*ref_cnt;
+ }
+
+ promise_t(promise_t &&other):
+ pm(other.pm),
+ ref_cnt(other.ref_cnt) {
+ other.pm = nullptr;
+ }
+
+ Promise *operator->() const {
+ return pm;
+ }
+
+ template<typename T> inline void resolve(T result) const;
+ template<typename T> inline void reject(T reason) const;
+ inline void resolve() const;
+ inline void reject() const;
+
+ template<typename FuncFulfilled>
+ inline promise_t then(FuncFulfilled on_fulfilled) const;
+
+ template<typename FuncFulfilled, typename FuncRejected>
+ inline promise_t then(FuncFulfilled on_fulfilled,
+ FuncRejected on_rejected) const;
+
+ template<typename FuncRejected>
+ inline promise_t fail(FuncRejected on_rejected) const;
+ };
+
+#define PROMISE_ERR_INVALID_STATE do {throw std::runtime_error("invalid promise state");} while (0)
+#define PROMISE_ERR_MISMATCH_TYPE do {throw std::runtime_error("mismatching promise value types");} while (0)
+
+ class Promise {
+ template<typename PList> friend promise_t all(const PList &promise_list);
+ template<typename PList> friend promise_t race(const PList &promise_list);
+ std::vector<callback_t> fulfilled_callbacks;
+ std::vector<callback_t> rejected_callbacks;
+#ifdef CPPROMISE_USE_STACK_FREE
+ std::vector<Promise *> fulfilled_pms;
+ std::vector<Promise *> rejected_pms;
+#endif
+ enum class State {
+ Pending,
+#ifdef CPPROMISE_USE_STACK_FREE
+ PreFulfilled,
+ PreRejected,
+#endif
+ Fulfilled,
+ Rejected,
+ } state;
+ pm_any_t result;
+ pm_any_t reason;
+
+ void add_on_fulfilled(callback_t &&cb) {
+ fulfilled_callbacks.push_back(std::move(cb));
+ }
+
+ void add_on_rejected(callback_t &&cb) {
+ rejected_callbacks.push_back(std::move(cb));
+ }
+
+ template<typename Func,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ static constexpr auto cps_transform(
+ Func f, const pm_any_t &result, const promise_t &npm) {
+ return [&result, npm, f = std::forward<Func>(f)]() mutable {
+#ifndef CPPROMISE_USE_STACK_FREE
+ f(result)->then(
+ [npm] (pm_any_t result) {npm->resolve(result);},
+ [npm] (pm_any_t reason) {npm->reject(reason);});
+#else
+ promise_t rpm{f(result)};
+ rpm->then(
+ [rpm, npm] (pm_any_t result) {
+ npm->_resolve(result);
+ },
+ [rpm, npm] (pm_any_t reason) {
+ npm->_reject(reason);
+ });
+ rpm->_dep_resolve(npm);
+ rpm->_dep_reject(npm);
+#endif
+ };
+ }
+
+ template<typename Func,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ static constexpr auto cps_transform(
+ Func f, const pm_any_t &, const promise_t &npm) {
+ return [npm, f = std::forward<Func>(f)]() mutable {
+#ifndef CPPROMISE_USE_STACK_FREE
+ f()->then(
+ [npm] (pm_any_t result) {npm->resolve(result);},
+ [npm] (pm_any_t reason) {npm->reject(reason);});
+#else
+ promise_t rpm{f()};
+ rpm->then(
+ [rpm, npm] (pm_any_t result) {
+ npm->_resolve(result);
+ },
+ [rpm, npm] (pm_any_t reason) {
+ npm->_reject(reason);
+ });
+ rpm->_dep_resolve(npm);
+ rpm->_dep_reject(npm);
+#endif
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, promise_t>::type * = nullptr>
+ constexpr auto gen_on_fulfilled(Func on_fulfilled, const promise_t &npm) {
+ return cps_transform(std::forward<Func>(on_fulfilled), this->result, npm);
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, promise_t>::type * = nullptr>
+ constexpr auto gen_on_rejected(Func on_rejected, const promise_t &npm) {
+ return cps_transform(std::forward<Func>(on_rejected), this->reason, npm);
+ }
+
+
+ template<typename Func,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_on_fulfilled(Func on_fulfilled, const promise_t &npm) {
+ return [this, npm,
+ on_fulfilled = std::forward<Func>(on_fulfilled)]() mutable {
+ on_fulfilled(result);
+ npm->_resolve();
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_on_fulfilled(Func on_fulfilled, const promise_t &npm) {
+ return [on_fulfilled = std::forward<Func>(on_fulfilled), npm]() mutable {
+ on_fulfilled();
+ npm->_resolve();
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_on_rejected(Func on_rejected, const promise_t &npm) {
+ return [this, npm,
+ on_rejected = std::forward<Func>(on_rejected)]() mutable {
+ on_rejected(reason);
+ npm->_reject();
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_on_rejected(Func on_rejected, const promise_t &npm) {
+ return [npm,
+ on_rejected = std::forward<Func>(on_rejected)]() mutable {
+ on_rejected();
+ npm->_reject();
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, pm_any_t>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_on_fulfilled(Func on_fulfilled, const promise_t &npm) {
+ return [this, npm,
+ on_fulfilled = std::forward<Func>(on_fulfilled)]() mutable {
+ npm->_resolve(on_fulfilled(result));
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, pm_any_t>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_on_fulfilled(Func on_fulfilled, const promise_t &npm) {
+ return [npm, on_fulfilled = std::forward<Func>(on_fulfilled)]() mutable {
+ npm->_resolve(on_fulfilled());
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, pm_any_t>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_on_rejected(Func on_rejected, const promise_t &npm) {
+ return [this, npm, on_rejected = std::forward<Func>(on_rejected)]() mutable {
+ npm->_reject(on_rejected(reason));
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, pm_any_t>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_on_rejected(Func on_rejected, const promise_t &npm) {
+ return [npm, on_rejected = std::forward<Func>(on_rejected)]() mutable {
+ npm->_reject(on_rejected());
+ };
+ }
+
+#ifdef CPPROMISE_USE_STACK_FREE
+ void _trigger() {
+ std::stack<std::tuple<
+ std::vector<Promise *>::const_iterator,
+ std::vector<Promise *> *,
+ Promise *>> s;
+ auto push_frame = [&s](Promise *pm) {
+ if (pm->state == State::PreFulfilled)
+ {
+ pm->state = State::Fulfilled;
+ for (auto &cb: pm->fulfilled_callbacks) cb();
+ s.push(std::make_tuple(pm->fulfilled_pms.begin(),
+ &pm->fulfilled_pms,
+ pm));
+ }
+ else if (pm->state == State::PreRejected)
+ {
+ pm->state = State::Rejected;
+ for (auto &cb: pm->rejected_callbacks) cb();
+ s.push(std::make_tuple(pm->rejected_pms.begin(),
+ &pm->rejected_pms,
+ pm));
+ }
+ };
+ push_frame(this);
+ while (!s.empty())
+ {
+ auto &u = s.top();
+ auto &it = std::get<0>(u);
+ auto vec = std::get<1>(u);
+ auto pm = std::get<2>(u);
+ if (it == vec->end())
+ {
+ s.pop();
+ vec->clear();
+ pm->fulfilled_callbacks.clear();
+ pm->rejected_callbacks.clear();
+ continue;
+ }
+ push_frame(*it++);
+ }
+ }
+
+ void trigger_fulfill() {
+ state = State::PreFulfilled;
+ _trigger();
+ }
+
+ void trigger_reject() {
+ state = State::PreRejected;
+ _trigger();
+ }
+
+ void _resolve() {
+ if (state == State::Pending) state = State::PreFulfilled;
+ }
+
+ void _reject() {
+ if (state == State::Pending) state = State::PreRejected;
+ }
+
+ void _dep_resolve(const promise_t &npm) {
+ if (state == State::Pending)
+ fulfilled_pms.push_back(npm.pm);
+ else
+ npm->_trigger();
+ }
+
+ void _dep_reject(const promise_t &npm) {
+ if (state == State::Pending)
+ rejected_pms.push_back(npm.pm);
+ else
+ npm->_trigger();
+ }
+
+ void _resolve(pm_any_t _result) {
+ if (state == State::Pending)
+ {
+ result = _result;
+ state = State::PreFulfilled;
+ }
+ }
+
+ void _reject(pm_any_t _reason) {
+ if (state == State::Pending)
+ {
+ reason = _reason;
+ state = State::PreRejected;
+ }
+ }
+#else
+ void _resolve() { resolve(); }
+ void _reject() { reject(); }
+ void _resolve(pm_any_t result) { resolve(result); }
+ void _reject(pm_any_t reason) { reject(reason); }
+
+ void trigger_fulfill() {
+ state = State::Fulfilled;
+ for (const auto &cb: fulfilled_callbacks) cb();
+ fulfilled_callbacks.clear();
+ }
+
+ void trigger_reject() {
+ state = State::Rejected;
+ for (const auto &cb: rejected_callbacks) cb();
+ rejected_callbacks.clear();
+ }
+#endif
+ public:
+
+ Promise(): state(State::Pending) {}
+ ~Promise() {}
+
+ template<typename FuncFulfilled, typename FuncRejected>
+ promise_t then(FuncFulfilled on_fulfilled,
+ FuncRejected on_rejected) {
+ switch (state)
+ {
+ case State::Pending:
+ return promise_t([this,
+ on_fulfilled = std::forward<FuncFulfilled>(on_fulfilled),
+ on_rejected = std::forward<FuncRejected>(on_rejected)
+ ](promise_t &npm) {
+ add_on_fulfilled(gen_on_fulfilled(std::move(on_fulfilled), npm));
+ add_on_rejected(gen_on_rejected(std::move(on_rejected), npm));
+#ifdef CPPROMISE_USE_STACK_FREE
+ _dep_resolve(npm);
+ _dep_reject(npm);
+#endif
+ });
+ case State::Fulfilled:
+ return promise_t([this,
+ on_fulfilled = std::forward<FuncFulfilled>(on_fulfilled)
+ ](promise_t &npm) {
+ gen_on_fulfilled(std::move(on_fulfilled), npm)();
+ });
+ case State::Rejected:
+ return promise_t([this,
+ on_rejected = std::forward<FuncRejected>(on_rejected)
+ ](promise_t &npm) {
+ gen_on_rejected(std::move(on_rejected), npm)();
+ });
+ default: PROMISE_ERR_INVALID_STATE;
+ }
+ }
+
+ template<typename FuncFulfilled>
+ promise_t then(FuncFulfilled &&on_fulfilled) {
+ switch (state)
+ {
+ case State::Pending:
+ return promise_t([this,
+ on_fulfilled = std::forward<FuncFulfilled>(on_fulfilled)
+ ](promise_t &npm) {
+ add_on_fulfilled(gen_on_fulfilled(std::move(on_fulfilled), npm));
+ add_on_rejected([this, npm]() {npm->_reject(reason);});
+#ifdef CPPROMISE_USE_STACK_FREE
+ _dep_resolve(npm);
+ _dep_reject(npm);
+#endif
+ });
+ case State::Fulfilled:
+ return promise_t([this,
+ on_fulfilled = std::forward<FuncFulfilled>(on_fulfilled)
+ ](promise_t &npm) {
+ gen_on_fulfilled(std::move(on_fulfilled), npm)();
+ });
+ case State::Rejected:
+ return promise_t([this](promise_t &npm) {npm->_reject(reason);});
+ default: PROMISE_ERR_INVALID_STATE;
+ }
+ }
+
+ template<typename FuncRejected>
+ promise_t fail(FuncRejected &&on_rejected) {
+ switch (state)
+ {
+ case State::Pending:
+ return promise_t([this,
+ on_rejected = std::forward<FuncRejected>(on_rejected)
+ ](promise_t &npm) {
+ callback_t ret;
+ add_on_rejected(gen_on_rejected(std::move(on_rejected), npm));
+ add_on_fulfilled([this, npm]() {npm->_resolve(result);});
+#ifdef CPPROMISE_USE_STACK_FREE
+ _dep_resolve(npm);
+ _dep_reject(npm);
+#endif
+ });
+ case State::Fulfilled:
+ return promise_t([this](promise_t &npm) {npm->_resolve(result);});
+ case State::Rejected:
+ return promise_t([this,
+ on_rejected = std::forward<FuncRejected>(on_rejected)
+ ](promise_t &npm) {
+ gen_on_rejected(std::move(on_rejected), npm)();
+ });
+ default: PROMISE_ERR_INVALID_STATE;
+ }
+ }
+
+ void resolve() {
+ if (state == State::Pending) trigger_fulfill();
+ }
+
+ void reject() {
+ if (state == State::Pending) trigger_reject();
+ }
+
+ void resolve(pm_any_t _result) {
+ if (state == State::Pending)
+ {
+ result = _result;
+ trigger_fulfill();
+ }
+ }
+
+ void reject(pm_any_t _reason) {
+ if (state == State::Pending)
+ {
+ reason = _reason;
+ trigger_reject();
+ }
+ }
+ };
+
+ template<typename PList> promise_t all(const PList &promise_list) {
+ return promise_t([&promise_list] (promise_t &npm) {
+ auto size = std::make_shared<size_t>(promise_list.size());
+ auto results = std::make_shared<values_t>();
+ if (!*size) PROMISE_ERR_MISMATCH_TYPE;
+ results->resize(*size);
+ size_t idx = 0;
+ for (const auto &pm: promise_list) {
+ pm->then(
+ [results, size, idx, npm](pm_any_t result) {
+ (*results)[idx] = result;
+ if (!--(*size))
+ npm->_resolve(*results);
+ },
+ [npm](pm_any_t reason) {npm->_reject(reason);});
+#ifdef CPPROMISE_USE_STACK_FREE
+ pm->_dep_resolve(npm);
+ pm->_dep_reject(npm);
+#endif
+ idx++;
+ }
+ });
+ }
+
+ template<typename PList> promise_t race(const PList &promise_list) {
+ return promise_t([&promise_list] (promise_t &npm) {
+ for (const auto &pm: promise_list) {
+ pm->then([npm](pm_any_t result) {npm->_resolve(result);},
+ [npm](pm_any_t reason) {npm->_reject(reason);});
+#ifdef CPPROMISE_USE_STACK_FREE
+ pm->_dep_resolve(npm);
+ pm->_dep_reject(npm);
+#endif
+ }
+ });
+ }
+
+ template<typename Func>
+ inline promise_t::promise_t(Func callback):
+ pm(new Promise()),
+ ref_cnt(new size_t(1)) {
+ callback(*this);
+ }
+
+ inline promise_t::promise_t():
+ pm(new Promise()),
+ ref_cnt(new size_t(1)) {}
+
+ inline promise_t::~promise_t() {
+ if (pm)
+ {
+ if (--*ref_cnt) return;
+ delete pm;
+ delete ref_cnt;
+ }
+ }
+
+ template<typename T>
+ inline void promise_t::resolve(T result) const { (*this)->resolve(result); }
+
+ template<typename T>
+ inline void promise_t::reject(T reason) const { (*this)->reject(reason); }
+
+ inline void promise_t::resolve() const { (*this)->resolve(); }
+ inline void promise_t::reject() const { (*this)->reject(); }
+
+ template<typename T>
+ struct callback_types {
+ using arg_type = typename function_traits<T>::arg_type;
+ using ret_type = typename std::conditional<
+ std::is_same<typename function_traits<T>::ret_type, promise_t>::value,
+ promise_t, pm_any_t>::type;
+ };
+
+ template<typename Func,
+ typename disable_if_arg<Func, pm_any_t>::type * = nullptr,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) {
+ using func_t = callback_types<Func>;
+ return [f = std::forward<Func>(f)](pm_any_t v) mutable {
+ try {
+ f(any_cast<typename func_t::arg_type>(v));
+ } catch (bad_any_cast &e) { PROMISE_ERR_MISMATCH_TYPE; }
+ };
+ }
+
+ template<typename Func,
+ typename enable_if_arg<Func, pm_any_t>::type * = nullptr,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) {
+ return [f = std::forward<Func>(f)](pm_any_t v) mutable {f(v);};
+ }
+
+ template<typename Func,
+ typename enable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) { return std::forward<Func>(f); }
+
+ template<typename Func,
+ typename enable_if_arg<Func, pm_any_t>::type * = nullptr,
+ typename disable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) {
+ using func_t = callback_types<Func>;
+ return [f = std::forward<Func>(f)](pm_any_t v) mutable {
+ return typename func_t::ret_type(f(v));
+ };
+ }
+
+ template<typename Func,
+ typename disable_if_arg<Func, pm_any_t>::type * = nullptr,
+ typename disable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::non_empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) {
+ using func_t = callback_types<Func>;
+ return [f = std::forward<Func>(f)](pm_any_t v) mutable {
+ try {
+ return typename func_t::ret_type(
+ f(any_cast<typename func_t::arg_type>(v)));
+ } catch (bad_any_cast &e) { PROMISE_ERR_MISMATCH_TYPE; }
+ };
+ }
+
+ template<typename Func,
+ typename disable_if_return<Func, void>::type * = nullptr,
+ typename function_traits<Func>::empty_arg * = nullptr>
+ constexpr auto gen_any_callback(Func f) {
+ using func_t = callback_types<Func>;
+ return [f = std::forward<Func>(f)]() mutable {
+ return typename func_t::ret_type(f());
+ };
+ }
+
+ template<typename FuncFulfilled>
+ inline promise_t promise_t::then(FuncFulfilled on_fulfilled) const {
+ return (*this)->then(gen_any_callback(std::forward<FuncFulfilled>(on_fulfilled)));
+ }
+
+ template<typename FuncFulfilled, typename FuncRejected>
+ inline promise_t promise_t::then(FuncFulfilled on_fulfilled,
+ FuncRejected on_rejected) const {
+ return (*this)->then(gen_any_callback(std::forward<FuncFulfilled>(on_fulfilled)),
+ gen_any_callback(std::forward<FuncRejected>(on_rejected)));
+ }
+
+ template<typename FuncRejected>
+ inline promise_t promise_t::fail(FuncRejected on_rejected) const {
+ return (*this)->fail(gen_any_callback(std::forward<FuncRejected>(on_rejected)));
+ }
+}
+
+#endif
diff --git a/src/type.h b/src/type.h
new file mode 100644
index 0000000..4665979
--- /dev/null
+++ b/src/type.h
@@ -0,0 +1,46 @@
+#ifndef _HOTSTUFF_TYPE_H
+#define _HOTSTUFF_TYPE_H
+
+#include "salticidae/stream.h"
+#include "salticidae/type.h"
+#include "salticidae/util.h"
+
+namespace hotstuff {
+
+using salticidae::uint256_t;
+using salticidae::DataStream;
+using salticidae::htole;
+using salticidae::letoh;
+using salticidae::get_hex;
+using salticidae::from_hex;
+using salticidae::bytearray_t;
+
+inline std::string get_hex10(const uint256_t &x) {
+ return get_hex(x).substr(0, 10);
+}
+
+class HotStuffError: public salticidae::SalticidaeError {
+ public:
+ template<typename... Args>
+ HotStuffError(Args... args): salticidae::SalticidaeError(args...) {}
+};
+
+class HotStuffInvalidEntity: public HotStuffError {
+ public:
+ template<typename... Args>
+ HotStuffInvalidEntity(Args... args): HotStuffError(args...) {}
+};
+
+using salticidae::Serializable;
+
+class Cloneable {
+ public:
+ virtual ~Cloneable() = default;
+ virtual Cloneable *clone() = 0;
+};
+
+using ReplicaID = uint16_t;
+
+}
+
+#endif
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..dc509f1
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,7 @@
+#include "util.h"
+
+namespace hotstuff {
+
+Logger logger("hotstuff");
+
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..42c0135
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,119 @@
+#ifndef _HOTSTUFF_UTIL_H
+#define _HOTSTUFF_UTIL_H
+
+#include "salticidae/util.h"
+
+namespace hotstuff {
+
+class Logger: public salticidae::Logger {
+ public:
+ using salticidae::Logger::Logger;
+};
+
+extern Logger logger;
+
+#ifdef HOTSTUFF_DEBUG_LOG
+#define HOTSTUFF_NORMAL_LOG
+#define HOTSTUFF_ENABLE_LOG_DEBUG
+#endif
+
+#ifdef HOTSTUFF_NORMAL_LOG
+#define HOTSTUFF_ENABLE_LOG_INFO
+#define HOTSTUFF_ENABLE_LOG_WARN
+#endif
+
+#ifdef HOTSTUFF_ENABLE_LOG_INFO
+#define HOTSTUFF_LOG_INFO(...) hotstuff::logger.info(__VA_ARGS__)
+#else
+#define HOTSTUFF_LOG_INFO(...) ((void)0)
+#endif
+
+#ifdef HOTSTUFF_ENABLE_LOG_DEBUG
+#define HOTSTUFF_LOG_DEBUG(...) hotstuff::logger.debug(__VA_ARGS__)
+#else
+#define HOTSTUFF_LOG_DEBUG(...) ((void)0)
+#endif
+
+#ifdef HOTSTUFF_ENABLE_LOG_WARN
+#define HOTSTUFF_LOG_WARN(...) hotstuff::logger.warning(__VA_ARGS__)
+#else
+#define HOTSTUFF_LOG_WARN(...) ((void)0)
+#endif
+
+#define HOTSTUFF_LOG_ERROR(...) hotstuff::logger.error(__VA_ARGS__)
+
+#ifdef HOTSTUFF_ENABLE_BLK_PROFILE
+class BlockProfiler {
+ enum BlockState {
+ BLK_SEEN,
+ BLK_FETCH,
+ BLK_CC
+ };
+
+ struct BlockProfile {
+ bool is_local; /* is the block proposed by the replica itself? */
+ BlockState state;
+ double hash_seen_time; /* the first time to see block hash */
+ double full_fetch_time; /* the first time to get full block content */
+ double cc_time; /* the time when it receives cc */
+ double commit_time; /* the time when it commits */
+ };
+
+ std::unordered_map<const uint256, BlockProfile> blocks;
+ ElapsedTime timer;
+
+ public:
+ BlockProfiler() { timer.start(); }
+
+ auto rec_blk(const uint256 &blk_hash, bool is_local) {
+ auto it = blocks.find(blk_hash);
+ assert(it == blocks.end());
+ timer.stop(false);
+ return blocks.insert(std::make_pair(blk_hash,
+ BlockProfile{is_local, BLK_SEEN, timer.elapsed_sec, 0, 0, 0})).first;
+ }
+
+ void get_blk(const uint256 &blk_hash) {
+ auto it = blocks.find(blk_hash);
+ if (it == blocks.end())
+ it = rec_blk(blk_hash, false);
+ BlockProfile &blkp = it->second;
+ assert(blkp.state == BLK_SEEN);
+ timer.stop(false);
+ blkp.full_fetch_time = timer.elapsed_sec;
+ blkp.state = BLK_FETCH;
+ }
+
+ void have_cc(const uint256 &blk_hash) {
+ auto it = blocks.find(blk_hash);
+ assert(it != blocks.end());
+ BlockProfile &blkp = it->second;
+ assert(blkp.state == BLK_FETCH);
+ timer.stop(false);
+ blkp.polling_start_time = timer.elapsed_sec;
+ blkp.state = BLK_CC;
+ }
+
+ const char *decide_blk(const uint256 &blk_hash) {
+ static char buff[1024];
+ auto it = blocks.find(blk_hash);
+ assert(it != blocks.end());
+ BlockProfile &blkp = it->second;
+ assert(blkp.state == BLK_CC);
+ timer.stop(false);
+ blkp.commit_time = timer.elapsed_sec;
+ snprintf(buff, sizeof buff, "(%d,%.4f,%.4f,%.4f,%.4f,%.4f)",
+ blkp.is_local,
+ blkp.hash_seen_time, blkp.full_fetch_time,
+ blkp.polling_start_time, blkp.polling_end_time,
+ blkp.commit_time);
+ blocks.erase(it);
+ return buff;
+ }
+};
+
+#endif
+
+}
+
+#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..60ffdc3
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,4 @@
+include_directories(../src/
+ ../salticidae/include/)
+add_executable(test_secp256k1 test_secp256k1.cpp)
+target_link_libraries(test_secp256k1 hotstuff)
diff --git a/test/test_secp256k1.cpp b/test/test_secp256k1.cpp
new file mode 100644
index 0000000..0991b6e
--- /dev/null
+++ b/test/test_secp256k1.cpp
@@ -0,0 +1,33 @@
+#include "crypto.h"
+
+using namespace hotstuff;
+
+int main() {
+ PrivKeySecp256k1 p;
+ p.from_hex("4aede145d13021fb43c938bced67511a7740c05786d3e0b94ffbdaa7f15afc57");
+ pubkey_bt pub = p.get_pubkey();
+ printf("%s\n", get_hex(*pub).c_str());
+ DataStream s;
+ s << *pub;
+ PubKeySecp256k1 pub2;
+ s >> pub2;
+ printf("%s\n", get_hex(pub2).c_str());
+ SigSecp256k1 sig;
+ sig.sign(bytearray_t(32), p);
+ printf("%s\n", get_hex(sig).c_str());
+ s << sig;
+ SigSecp256k1 sig2(secp256k1_default_verify_ctx);
+ s >> sig2;
+ bytearray_t msg = bytearray_t(32);
+ msg[0] = 1;
+ printf("%d %d\n", sig2.verify(bytearray_t(32), pub2),
+ sig2.verify(msg, pub2));
+ p.from_rand();
+ printf("%s\n", get_hex(p).c_str());
+ sig.sign(msg, p);
+ printf("%s\n", get_hex(sig).c_str());
+ s << sig;
+ s >> sig2;
+ printf("%d %d\n", sig2.verify(msg, PubKeySecp256k1(p)),
+ sig2.verify(msg, pub2));
+}