diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..2fffe7ba --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -3 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 6 +ContinuationIndentWidth: 6 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 3 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 3 +UseTab: Never +... + diff --git a/.gitignore b/.gitignore index 90311de0..39b23163 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ moc_* hardfork.hpp build_xc data +CMakeDoxyfile.in build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b53c7541..1403ba24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,26 +12,30 @@ stages: build: stage: build script: + - rm -rf .git/modules/* ./docs ./libraries/fc - git submodule sync - git submodule update --init --recursive - - cmake . + - rm -rf build + - mkdir build + - cd build + - cmake .. - make -j$(nproc) artifacts: untracked: true paths: - - libraries/ - - programs/ - - tests/ + - build/libraries/ + - build/programs/ + - build/tests/ tags: - builder - + test: stage: test - dependencies: + dependencies: - build script: - - ./tests/betting_test - - ./tests/chain_test - - ./tests/cli_test + - ./build/tests/betting_test --log_level=message + - ./build/tests/chain_test --log_level=message + - ./build/tests/cli_test --log_level=message tags: - builder diff --git a/CMakeDoxyfile.in b/CMakeDoxyfile.in new file mode 100644 index 00000000..b0ed02fb --- /dev/null +++ b/CMakeDoxyfile.in @@ -0,0 +1,279 @@ +# +# DO NOT EDIT! THIS FILE WAS GENERATED BY CMAKE! +# + +DOXYFILE_ENCODING = @DOXYGEN_DOXYFILE_ENCODING@ +PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ +PROJECT_NUMBER = @DOXYGEN_PROJECT_NUMBER@ +PROJECT_BRIEF = @DOXYGEN_PROJECT_BRIEF@ +PROJECT_LOGO = @DOXYGEN_PROJECT_LOGO@ +OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@ +CREATE_SUBDIRS = @DOXYGEN_CREATE_SUBDIRS@ +ALLOW_UNICODE_NAMES = @DOXYGEN_ALLOW_UNICODE_NAMES@ +OUTPUT_LANGUAGE = @DOXYGEN_OUTPUT_LANGUAGE@ +OUTPUT_TEXT_DIRECTION = @DOXYGEN_OUTPUT_TEXT_DIRECTION@ +BRIEF_MEMBER_DESC = @DOXYGEN_BRIEF_MEMBER_DESC@ +REPEAT_BRIEF = @DOXYGEN_REPEAT_BRIEF@ +ABBREVIATE_BRIEF = @DOXYGEN_ABBREVIATE_BRIEF@ +ALWAYS_DETAILED_SEC = @DOXYGEN_ALWAYS_DETAILED_SEC@ +INLINE_INHERITED_MEMB = @DOXYGEN_INLINE_INHERITED_MEMB@ +FULL_PATH_NAMES = @DOXYGEN_FULL_PATH_NAMES@ +STRIP_FROM_PATH = @DOXYGEN_STRIP_FROM_PATH@ +STRIP_FROM_INC_PATH = @DOXYGEN_STRIP_FROM_INC_PATH@ +SHORT_NAMES = @DOXYGEN_SHORT_NAMES@ +JAVADOC_AUTOBRIEF = @DOXYGEN_JAVADOC_AUTOBRIEF@ +JAVADOC_BANNER = @DOXYGEN_JAVADOC_BANNER@ +QT_AUTOBRIEF = @DOXYGEN_QT_AUTOBRIEF@ +MULTILINE_CPP_IS_BRIEF = @DOXYGEN_MULTILINE_CPP_IS_BRIEF@ +INHERIT_DOCS = @DOXYGEN_INHERIT_DOCS@ +SEPARATE_MEMBER_PAGES = @DOXYGEN_SEPARATE_MEMBER_PAGES@ +TAB_SIZE = @DOXYGEN_TAB_SIZE@ +ALIASES = @DOXYGEN_ALIASES@ +TCL_SUBST = @DOXYGEN_TCL_SUBST@ +OPTIMIZE_OUTPUT_FOR_C = @DOXYGEN_OPTIMIZE_OUTPUT_FOR_C@ +OPTIMIZE_OUTPUT_JAVA = @DOXYGEN_OPTIMIZE_OUTPUT_JAVA@ +OPTIMIZE_FOR_FORTRAN = @DOXYGEN_OPTIMIZE_FOR_FORTRAN@ +OPTIMIZE_OUTPUT_VHDL = @DOXYGEN_OPTIMIZE_OUTPUT_VHDL@ +OPTIMIZE_OUTPUT_SLICE = @DOXYGEN_OPTIMIZE_OUTPUT_SLICE@ +EXTENSION_MAPPING = @DOXYGEN_EXTENSION_MAPPING@ +MARKDOWN_SUPPORT = @DOXYGEN_MARKDOWN_SUPPORT@ +TOC_INCLUDE_HEADINGS = @DOXYGEN_TOC_INCLUDE_HEADINGS@ +AUTOLINK_SUPPORT = @DOXYGEN_AUTOLINK_SUPPORT@ +BUILTIN_STL_SUPPORT = @DOXYGEN_BUILTIN_STL_SUPPORT@ +CPP_CLI_SUPPORT = @DOXYGEN_CPP_CLI_SUPPORT@ +SIP_SUPPORT = @DOXYGEN_SIP_SUPPORT@ +IDL_PROPERTY_SUPPORT = @DOXYGEN_IDL_PROPERTY_SUPPORT@ +DISTRIBUTE_GROUP_DOC = @DOXYGEN_DISTRIBUTE_GROUP_DOC@ +GROUP_NESTED_COMPOUNDS = @DOXYGEN_GROUP_NESTED_COMPOUNDS@ +SUBGROUPING = @DOXYGEN_SUBGROUPING@ +INLINE_GROUPED_CLASSES = @DOXYGEN_INLINE_GROUPED_CLASSES@ +INLINE_SIMPLE_STRUCTS = @DOXYGEN_INLINE_SIMPLE_STRUCTS@ +TYPEDEF_HIDES_STRUCT = @DOXYGEN_TYPEDEF_HIDES_STRUCT@ +LOOKUP_CACHE_SIZE = @DOXYGEN_LOOKUP_CACHE_SIZE@ +EXTRACT_ALL = @DOXYGEN_EXTRACT_ALL@ +EXTRACT_PRIVATE = @DOXYGEN_EXTRACT_PRIVATE@ +EXTRACT_PRIV_VIRTUAL = @DOXYGEN_EXTRACT_PRIV_VIRTUAL@ +EXTRACT_PACKAGE = @DOXYGEN_EXTRACT_PACKAGE@ +EXTRACT_STATIC = @DOXYGEN_EXTRACT_STATIC@ +EXTRACT_LOCAL_CLASSES = @DOXYGEN_EXTRACT_LOCAL_CLASSES@ +EXTRACT_LOCAL_METHODS = @DOXYGEN_EXTRACT_LOCAL_METHODS@ +EXTRACT_ANON_NSPACES = @DOXYGEN_EXTRACT_ANON_NSPACES@ +HIDE_UNDOC_MEMBERS = @DOXYGEN_HIDE_UNDOC_MEMBERS@ +HIDE_UNDOC_CLASSES = @DOXYGEN_HIDE_UNDOC_CLASSES@ +HIDE_FRIEND_COMPOUNDS = @DOXYGEN_HIDE_FRIEND_COMPOUNDS@ +HIDE_IN_BODY_DOCS = @DOXYGEN_HIDE_IN_BODY_DOCS@ +INTERNAL_DOCS = @DOXYGEN_INTERNAL_DOCS@ +CASE_SENSE_NAMES = @DOXYGEN_CASE_SENSE_NAMES@ +HIDE_SCOPE_NAMES = @DOXYGEN_HIDE_SCOPE_NAMES@ +HIDE_COMPOUND_REFERENCE= @DOXYGEN_HIDE_COMPOUND_REFERENCE@ +SHOW_INCLUDE_FILES = @DOXYGEN_SHOW_INCLUDE_FILES@ +SHOW_GROUPED_MEMB_INC = @DOXYGEN_SHOW_GROUPED_MEMB_INC@ +FORCE_LOCAL_INCLUDES = @DOXYGEN_FORCE_LOCAL_INCLUDES@ +INLINE_INFO = @DOXYGEN_INLINE_INFO@ +SORT_MEMBER_DOCS = @DOXYGEN_SORT_MEMBER_DOCS@ +SORT_BRIEF_DOCS = @DOXYGEN_SORT_BRIEF_DOCS@ +SORT_MEMBERS_CTORS_1ST = @DOXYGEN_SORT_MEMBERS_CTORS_1ST@ +SORT_GROUP_NAMES = @DOXYGEN_SORT_GROUP_NAMES@ +SORT_BY_SCOPE_NAME = @DOXYGEN_SORT_BY_SCOPE_NAME@ +STRICT_PROTO_MATCHING = @DOXYGEN_STRICT_PROTO_MATCHING@ +GENERATE_TODOLIST = @DOXYGEN_GENERATE_TODOLIST@ +GENERATE_TESTLIST = @DOXYGEN_GENERATE_TESTLIST@ +GENERATE_BUGLIST = @DOXYGEN_GENERATE_BUGLIST@ +GENERATE_DEPRECATEDLIST= @DOXYGEN_GENERATE_DEPRECATEDLIST@ +ENABLED_SECTIONS = @DOXYGEN_ENABLED_SECTIONS@ +MAX_INITIALIZER_LINES = @DOXYGEN_MAX_INITIALIZER_LINES@ +SHOW_USED_FILES = @DOXYGEN_SHOW_USED_FILES@ +SHOW_FILES = @DOXYGEN_SHOW_FILES@ +SHOW_NAMESPACES = @DOXYGEN_SHOW_NAMESPACES@ +FILE_VERSION_FILTER = @DOXYGEN_FILE_VERSION_FILTER@ +LAYOUT_FILE = @DOXYGEN_LAYOUT_FILE@ +CITE_BIB_FILES = @DOXYGEN_CITE_BIB_FILES@ +QUIET = @DOXYGEN_QUIET@ +WARNINGS = @DOXYGEN_WARNINGS@ +WARN_IF_UNDOCUMENTED = @DOXYGEN_WARN_IF_UNDOCUMENTED@ +WARN_IF_DOC_ERROR = @DOXYGEN_WARN_IF_DOC_ERROR@ +WARN_NO_PARAMDOC = @DOXYGEN_WARN_NO_PARAMDOC@ +WARN_AS_ERROR = @DOXYGEN_WARN_AS_ERROR@ +WARN_FORMAT = @DOXYGEN_WARN_FORMAT@ +WARN_LOGFILE = @DOXYGEN_WARN_LOGFILE@ +INPUT = @DOXYGEN_INPUT@ +INPUT_ENCODING = @DOXYGEN_INPUT_ENCODING@ +FILE_PATTERNS = @DOXYGEN_FILE_PATTERNS@ +RECURSIVE = @DOXYGEN_RECURSIVE@ +EXCLUDE = @DOXYGEN_EXCLUDE@ +EXCLUDE_SYMLINKS = @DOXYGEN_EXCLUDE_SYMLINKS@ +EXCLUDE_PATTERNS = @DOXYGEN_EXCLUDE_PATTERNS@ +EXCLUDE_SYMBOLS = @DOXYGEN_EXCLUDE_SYMBOLS@ +EXAMPLE_PATH = @DOXYGEN_EXAMPLE_PATH@ +EXAMPLE_PATTERNS = @DOXYGEN_EXAMPLE_PATTERNS@ +EXAMPLE_RECURSIVE = @DOXYGEN_EXAMPLE_RECURSIVE@ +IMAGE_PATH = @DOXYGEN_IMAGE_PATH@ +INPUT_FILTER = @DOXYGEN_INPUT_FILTER@ +FILTER_PATTERNS = @DOXYGEN_FILTER_PATTERNS@ +FILTER_SOURCE_FILES = @DOXYGEN_FILTER_SOURCE_FILES@ +FILTER_SOURCE_PATTERNS = @DOXYGEN_FILTER_SOURCE_PATTERNS@ +USE_MDFILE_AS_MAINPAGE = @DOXYGEN_USE_MDFILE_AS_MAINPAGE@ +SOURCE_BROWSER = @DOXYGEN_SOURCE_BROWSER@ +INLINE_SOURCES = @DOXYGEN_INLINE_SOURCES@ +STRIP_CODE_COMMENTS = @DOXYGEN_STRIP_CODE_COMMENTS@ +REFERENCED_BY_RELATION = @DOXYGEN_REFERENCED_BY_RELATION@ +REFERENCES_RELATION = @DOXYGEN_REFERENCES_RELATION@ +REFERENCES_LINK_SOURCE = @DOXYGEN_REFERENCES_LINK_SOURCE@ +SOURCE_TOOLTIPS = @DOXYGEN_SOURCE_TOOLTIPS@ +USE_HTAGS = @DOXYGEN_USE_HTAGS@ +VERBATIM_HEADERS = @DOXYGEN_VERBATIM_HEADERS@ +CLANG_ASSISTED_PARSING = @DOXYGEN_CLANG_ASSISTED_PARSING@ +CLANG_OPTIONS = @DOXYGEN_CLANG_OPTIONS@ +CLANG_DATABASE_PATH = @DOXYGEN_CLANG_DATABASE_PATH@ +ALPHABETICAL_INDEX = @DOXYGEN_ALPHABETICAL_INDEX@ +COLS_IN_ALPHA_INDEX = @DOXYGEN_COLS_IN_ALPHA_INDEX@ +IGNORE_PREFIX = @DOXYGEN_IGNORE_PREFIX@ +GENERATE_HTML = @DOXYGEN_GENERATE_HTML@ +HTML_OUTPUT = @DOXYGEN_HTML_OUTPUT@ +HTML_FILE_EXTENSION = @DOXYGEN_HTML_FILE_EXTENSION@ +HTML_HEADER = @DOXYGEN_HTML_HEADER@ +HTML_FOOTER = @DOXYGEN_HTML_FOOTER@ +HTML_STYLESHEET = @DOXYGEN_HTML_STYLESHEET@ +HTML_EXTRA_STYLESHEET = @DOXYGEN_HTML_EXTRA_STYLESHEET@ +HTML_EXTRA_FILES = @DOXYGEN_HTML_EXTRA_FILES@ +HTML_COLORSTYLE_HUE = @DOXYGEN_HTML_COLORSTYLE_HUE@ +HTML_COLORSTYLE_SAT = @DOXYGEN_HTML_COLORSTYLE_SAT@ +HTML_COLORSTYLE_GAMMA = @DOXYGEN_HTML_COLORSTYLE_GAMMA@ +HTML_TIMESTAMP = @DOXYGEN_HTML_TIMESTAMP@ +HTML_DYNAMIC_MENUS = @DOXYGEN_HTML_DYNAMIC_MENUS@ +HTML_DYNAMIC_SECTIONS = @DOXYGEN_HTML_DYNAMIC_SECTIONS@ +HTML_INDEX_NUM_ENTRIES = @DOXYGEN_HTML_INDEX_NUM_ENTRIES@ +GENERATE_DOCSET = @DOXYGEN_GENERATE_DOCSET@ +DOCSET_FEEDNAME = @DOXYGEN_DOCSET_FEEDNAME@ +DOCSET_BUNDLE_ID = @DOXYGEN_DOCSET_BUNDLE_ID@ +DOCSET_PUBLISHER_ID = @DOXYGEN_DOCSET_PUBLISHER_ID@ +DOCSET_PUBLISHER_NAME = @DOXYGEN_DOCSET_PUBLISHER_NAME@ +GENERATE_HTMLHELP = @DOXYGEN_GENERATE_HTMLHELP@ +CHM_FILE = @DOXYGEN_CHM_FILE@ +HHC_LOCATION = @DOXYGEN_HHC_LOCATION@ +GENERATE_CHI = @DOXYGEN_GENERATE_CHI@ +CHM_INDEX_ENCODING = @DOXYGEN_CHM_INDEX_ENCODING@ +BINARY_TOC = @DOXYGEN_BINARY_TOC@ +TOC_EXPAND = @DOXYGEN_TOC_EXPAND@ +GENERATE_QHP = @DOXYGEN_GENERATE_QHP@ +QCH_FILE = @DOXYGEN_QCH_FILE@ +QHP_NAMESPACE = @DOXYGEN_QHP_NAMESPACE@ +QHP_VIRTUAL_FOLDER = @DOXYGEN_QHP_VIRTUAL_FOLDER@ +QHP_CUST_FILTER_NAME = @DOXYGEN_QHP_CUST_FILTER_NAME@ +QHP_CUST_FILTER_ATTRS = @DOXYGEN_QHP_CUST_FILTER_ATTRS@ +QHP_SECT_FILTER_ATTRS = @DOXYGEN_QHP_SECT_FILTER_ATTRS@ +QHG_LOCATION = @DOXYGEN_QHG_LOCATION@ +GENERATE_ECLIPSEHELP = @DOXYGEN_GENERATE_ECLIPSEHELP@ +ECLIPSE_DOC_ID = @DOXYGEN_ECLIPSE_DOC_ID@ +DISABLE_INDEX = @DOXYGEN_DISABLE_INDEX@ +GENERATE_TREEVIEW = @DOXYGEN_GENERATE_TREEVIEW@ +ENUM_VALUES_PER_LINE = @DOXYGEN_ENUM_VALUES_PER_LINE@ +TREEVIEW_WIDTH = @DOXYGEN_TREEVIEW_WIDTH@ +EXT_LINKS_IN_WINDOW = @DOXYGEN_EXT_LINKS_IN_WINDOW@ +FORMULA_FONTSIZE = @DOXYGEN_FORMULA_FONTSIZE@ +FORMULA_TRANSPARENT = @DOXYGEN_FORMULA_TRANSPARENT@ +USE_MATHJAX = @DOXYGEN_USE_MATHJAX@ +MATHJAX_FORMAT = @DOXYGEN_MATHJAX_FORMAT@ +MATHJAX_RELPATH = @DOXYGEN_MATHJAX_RELPATH@ +MATHJAX_EXTENSIONS = @DOXYGEN_MATHJAX_EXTENSIONS@ +MATHJAX_CODEFILE = @DOXYGEN_MATHJAX_CODEFILE@ +SEARCHENGINE = @DOXYGEN_SEARCHENGINE@ +SERVER_BASED_SEARCH = @DOXYGEN_SERVER_BASED_SEARCH@ +EXTERNAL_SEARCH = @DOXYGEN_EXTERNAL_SEARCH@ +SEARCHENGINE_URL = @DOXYGEN_SEARCHENGINE_URL@ +SEARCHDATA_FILE = @DOXYGEN_SEARCHDATA_FILE@ +EXTERNAL_SEARCH_ID = @DOXYGEN_EXTERNAL_SEARCH_ID@ +EXTRA_SEARCH_MAPPINGS = @DOXYGEN_EXTRA_SEARCH_MAPPINGS@ +GENERATE_LATEX = @DOXYGEN_GENERATE_LATEX@ +LATEX_OUTPUT = @DOXYGEN_LATEX_OUTPUT@ +LATEX_CMD_NAME = @DOXYGEN_LATEX_CMD_NAME@ +MAKEINDEX_CMD_NAME = @DOXYGEN_MAKEINDEX_CMD_NAME@ +LATEX_MAKEINDEX_CMD = @DOXYGEN_LATEX_MAKEINDEX_CMD@ +COMPACT_LATEX = @DOXYGEN_COMPACT_LATEX@ +PAPER_TYPE = @DOXYGEN_PAPER_TYPE@ +EXTRA_PACKAGES = @DOXYGEN_EXTRA_PACKAGES@ +LATEX_HEADER = @DOXYGEN_LATEX_HEADER@ +LATEX_FOOTER = @DOXYGEN_LATEX_FOOTER@ +LATEX_EXTRA_STYLESHEET = @DOXYGEN_LATEX_EXTRA_STYLESHEET@ +LATEX_EXTRA_FILES = @DOXYGEN_LATEX_EXTRA_FILES@ +PDF_HYPERLINKS = @DOXYGEN_PDF_HYPERLINKS@ +USE_PDFLATEX = @DOXYGEN_USE_PDFLATEX@ +LATEX_BATCHMODE = @DOXYGEN_LATEX_BATCHMODE@ +LATEX_HIDE_INDICES = @DOXYGEN_LATEX_HIDE_INDICES@ +LATEX_SOURCE_CODE = @DOXYGEN_LATEX_SOURCE_CODE@ +LATEX_BIB_STYLE = @DOXYGEN_LATEX_BIB_STYLE@ +LATEX_TIMESTAMP = @DOXYGEN_LATEX_TIMESTAMP@ +LATEX_EMOJI_DIRECTORY = @DOXYGEN_LATEX_EMOJI_DIRECTORY@ +GENERATE_RTF = @DOXYGEN_GENERATE_RTF@ +RTF_OUTPUT = @DOXYGEN_RTF_OUTPUT@ +COMPACT_RTF = @DOXYGEN_COMPACT_RTF@ +RTF_HYPERLINKS = @DOXYGEN_RTF_HYPERLINKS@ +RTF_STYLESHEET_FILE = @DOXYGEN_RTF_STYLESHEET_FILE@ +RTF_EXTENSIONS_FILE = @DOXYGEN_RTF_EXTENSIONS_FILE@ +RTF_SOURCE_CODE = @DOXYGEN_RTF_SOURCE_CODE@ +GENERATE_MAN = @DOXYGEN_GENERATE_MAN@ +MAN_OUTPUT = @DOXYGEN_MAN_OUTPUT@ +MAN_EXTENSION = @DOXYGEN_MAN_EXTENSION@ +MAN_SUBDIR = @DOXYGEN_MAN_SUBDIR@ +MAN_LINKS = @DOXYGEN_MAN_LINKS@ +GENERATE_XML = @DOXYGEN_GENERATE_XML@ +XML_OUTPUT = @DOXYGEN_XML_OUTPUT@ +XML_PROGRAMLISTING = @DOXYGEN_XML_PROGRAMLISTING@ +XML_NS_MEMB_FILE_SCOPE = @DOXYGEN_XML_NS_MEMB_FILE_SCOPE@ +GENERATE_DOCBOOK = @DOXYGEN_GENERATE_DOCBOOK@ +DOCBOOK_OUTPUT = @DOXYGEN_DOCBOOK_OUTPUT@ +DOCBOOK_PROGRAMLISTING = @DOXYGEN_DOCBOOK_PROGRAMLISTING@ +GENERATE_AUTOGEN_DEF = @DOXYGEN_GENERATE_AUTOGEN_DEF@ +GENERATE_PERLMOD = @DOXYGEN_GENERATE_PERLMOD@ +PERLMOD_LATEX = @DOXYGEN_PERLMOD_LATEX@ +PERLMOD_PRETTY = @DOXYGEN_PERLMOD_PRETTY@ +PERLMOD_MAKEVAR_PREFIX = @DOXYGEN_PERLMOD_MAKEVAR_PREFIX@ +ENABLE_PREPROCESSING = @DOXYGEN_ENABLE_PREPROCESSING@ +MACRO_EXPANSION = @DOXYGEN_MACRO_EXPANSION@ +EXPAND_ONLY_PREDEF = @DOXYGEN_EXPAND_ONLY_PREDEF@ +SEARCH_INCLUDES = @DOXYGEN_SEARCH_INCLUDES@ +INCLUDE_PATH = @DOXYGEN_INCLUDE_PATH@ +INCLUDE_FILE_PATTERNS = @DOXYGEN_INCLUDE_FILE_PATTERNS@ +PREDEFINED = @DOXYGEN_PREDEFINED@ +EXPAND_AS_DEFINED = @DOXYGEN_EXPAND_AS_DEFINED@ +SKIP_FUNCTION_MACROS = @DOXYGEN_SKIP_FUNCTION_MACROS@ +TAGFILES = @DOXYGEN_TAGFILES@ +GENERATE_TAGFILE = @DOXYGEN_GENERATE_TAGFILE@ +ALLEXTERNALS = @DOXYGEN_ALLEXTERNALS@ +EXTERNAL_GROUPS = @DOXYGEN_EXTERNAL_GROUPS@ +EXTERNAL_PAGES = @DOXYGEN_EXTERNAL_PAGES@ +CLASS_DIAGRAMS = @DOXYGEN_CLASS_DIAGRAMS@ +DIA_PATH = @DOXYGEN_DIA_PATH@ +HIDE_UNDOC_RELATIONS = @DOXYGEN_HIDE_UNDOC_RELATIONS@ +HAVE_DOT = @DOXYGEN_HAVE_DOT@ +DOT_NUM_THREADS = @DOXYGEN_DOT_NUM_THREADS@ +DOT_FONTNAME = @DOXYGEN_DOT_FONTNAME@ +DOT_FONTSIZE = @DOXYGEN_DOT_FONTSIZE@ +DOT_FONTPATH = @DOXYGEN_DOT_FONTPATH@ +CLASS_GRAPH = @DOXYGEN_CLASS_GRAPH@ +COLLABORATION_GRAPH = @DOXYGEN_COLLABORATION_GRAPH@ +GROUP_GRAPHS = @DOXYGEN_GROUP_GRAPHS@ +UML_LOOK = @DOXYGEN_UML_LOOK@ +UML_LIMIT_NUM_FIELDS = @DOXYGEN_UML_LIMIT_NUM_FIELDS@ +TEMPLATE_RELATIONS = @DOXYGEN_TEMPLATE_RELATIONS@ +INCLUDE_GRAPH = @DOXYGEN_INCLUDE_GRAPH@ +INCLUDED_BY_GRAPH = @DOXYGEN_INCLUDED_BY_GRAPH@ +CALL_GRAPH = @DOXYGEN_CALL_GRAPH@ +CALLER_GRAPH = @DOXYGEN_CALLER_GRAPH@ +GRAPHICAL_HIERARCHY = @DOXYGEN_GRAPHICAL_HIERARCHY@ +DIRECTORY_GRAPH = @DOXYGEN_DIRECTORY_GRAPH@ +DOT_IMAGE_FORMAT = @DOXYGEN_DOT_IMAGE_FORMAT@ +INTERACTIVE_SVG = @DOXYGEN_INTERACTIVE_SVG@ +DOT_PATH = @DOXYGEN_DOT_PATH@ +DOTFILE_DIRS = @DOXYGEN_DOTFILE_DIRS@ +MSCFILE_DIRS = @DOXYGEN_MSCFILE_DIRS@ +DIAFILE_DIRS = @DOXYGEN_DIAFILE_DIRS@ +PLANTUML_JAR_PATH = @DOXYGEN_PLANTUML_JAR_PATH@ +PLANTUML_CFG_FILE = @DOXYGEN_PLANTUML_CFG_FILE@ +PLANTUML_INCLUDE_PATH = @DOXYGEN_PLANTUML_INCLUDE_PATH@ +DOT_GRAPH_MAX_NODES = @DOXYGEN_DOT_GRAPH_MAX_NODES@ +MAX_DOT_GRAPH_DEPTH = @DOXYGEN_MAX_DOT_GRAPH_DEPTH@ +DOT_TRANSPARENT = @DOXYGEN_DOT_TRANSPARENT@ +DOT_MULTI_TARGETS = @DOXYGEN_DOT_MULTI_TARGETS@ +GENERATE_LEGEND = @DOXYGEN_GENERATE_LEGEND@ +DOT_CLEANUP = @DOXYGEN_DOT_CLEANUP@ diff --git a/CMakeLists.txt b/CMakeLists.txt index cd539a9c..9c6be0bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ else( WIN32 ) # Apple AND Linux endif( APPLE ) if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-parentheses -Wno-terminate -Wno-invalid-offsetof" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-parentheses -Wno-terminate -Wno-invalid-offsetof -Wno-sign-compare" ) elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization" ) diff --git a/Dockerfile b/Dockerfile index c20b4bea..5f63bd77 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN \ build-essential \ ca-certificates \ cmake \ + dnsutils \ doxygen \ git \ graphviz \ @@ -22,6 +23,7 @@ RUN \ libreadline-dev \ libssl-dev \ libtool \ + libzmq3-dev \ locales \ ntp \ pkg-config \ @@ -57,7 +59,7 @@ RUN \ cd build/release && \ cmake \ -DBOOST_ROOT="$BOOST_ROOT" \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=Debug \ ../.. && \ make witness_node cli_wallet && \ install -s programs/witness_node/witness_node programs/cli_wallet/cli_wallet /usr/local/bin && \ diff --git a/README.md b/README.md index 8207bb29..941afa68 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ This is a quick introduction to get new developers and witnesses up to speed on The following dependencies were necessary for a clean install of Ubuntu 18.04 LTS: ``` - sudo apt-get install gcc-5 g++-5 cmake make libbz2-dev\ - libdb++-dev libdb-dev libssl-dev openssl libreadline-dev\ - autoconf libtool git + sudo apt-get install autoconf bash build-essential ca-certificates cmake \ + doxygen git graphviz libbz2-dev libcurl4-openssl-dev libncurses-dev \ + libreadline-dev libssl-dev libtool libzmq3-dev locales ntp pkg-config \ + wget ``` ## Build Boost 1.67.0 diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index d66b4052..e0e5e6c2 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -13,7 +13,7 @@ add_library( graphene_app # need to link graphene_debug_witness because plugins aren't sufficiently isolated #246 #target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness ) -target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_affiliate_stats graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie graphene_elasticsearch ) +target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_affiliate_stats graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie graphene_elasticsearch peerplays_sidechain ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index c808a27c..11d39f69 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -448,7 +448,17 @@ namespace graphene { namespace app { } case balance_object_type:{ /** these are free from any accounts */ break; - } + } case son_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->son_account ); + break; + } case sidechain_address_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->sidechain_address_account ); + break; + } case sport_object_type: case event_group_object_type: case event_object_type: diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 2c7553f5..db73124f 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -226,7 +226,7 @@ namespace detail { void new_connection( const fc::http::websocket_connection_ptr& c ) { - auto wsc = std::make_shared(*c, GRAPHENE_MAX_NESTED_OBJECTS); + auto wsc = std::make_shared(c, GRAPHENE_MAX_NESTED_OBJECTS); auto login = std::make_shared( std::ref(*_self) ); login->enable_api("database_api"); diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 6b14f2bc..a2c56c59 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -112,7 +112,7 @@ class database_api_impl : public std::enable_shared_from_this uint64_t get_asset_count()const; // Peerplays - vector list_sports() const; + vector list_sports() const; vector list_event_groups(sport_id_type sport_id) const; vector list_events_in_group(event_group_id_type event_group_id) const; vector list_betting_market_groups(event_id_type) const; @@ -124,14 +124,14 @@ class database_api_impl : public std::enable_shared_from_this vector get_lotteries( asset_id_type stop = asset_id_type(), unsigned limit = 100, asset_id_type start = asset_id_type() )const; - vector get_account_lotteries( account_id_type issuer, + vector get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const; asset get_lottery_balance( asset_id_type lottery_id )const; sweeps_vesting_balance_object get_sweeps_vesting_balance_object( account_id_type account )const; asset get_sweeps_vesting_balance_available_for_claim( account_id_type account )const; - + // Markets / feeds vector get_limit_orders( const asset_id_type a, const asset_id_type b, const uint32_t limit )const; vector get_limit_orders( const std::string& a, const std::string& b, const uint32_t limit)const; @@ -156,6 +156,24 @@ class database_api_impl : public std::enable_shared_from_this fc::optional get_committee_member_by_account(const std::string account_id_or_name)const; map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + // SON members + vector> get_sons(const vector& son_ids)const; + fc::optional get_son_by_account(account_id_type account)const; + map lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const; + uint64_t get_son_count()const; + + // SON wallets + optional get_active_son_wallet(); + optional get_son_wallet_by_time_point(time_point_sec time_point); + vector> get_son_wallets(uint32_t limit); + + // Sidechain addresses + vector> get_sidechain_addresses(const vector& sidechain_address_ids)const; + vector> get_sidechain_addresses_by_account(account_id_type account)const; + vector> get_sidechain_addresses_by_sidechain(sidechain_type sidechain)const; + fc::optional get_sidechain_address_by_account_and_sidechain(account_id_type account, sidechain_type sidechain)const; + uint64_t get_sidechain_addresses_count()const; + // Votes vector lookup_vote_ids( const vector& votes )const; @@ -217,6 +235,9 @@ class database_api_impl : public std::enable_shared_from_this vector get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const; vector get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const; + // Account Role + vector get_account_roles_by_owner(account_id_type owner) const; + //private: const account_object* get_account_from_string( const std::string& name_or_id, bool throw_if_not_found = true ) const; @@ -571,11 +592,11 @@ vector> database_api::get_key_references( vector> database_api_impl::get_key_references( vector keys )const { wdump( (keys) ); - + const auto& idx = _db.get_index_type(); const auto& aidx = dynamic_cast(idx); const auto& refs = aidx.get_secondary_index(); - + vector< vector > final_result; final_result.reserve(keys.size()); @@ -696,7 +717,7 @@ std::map database_api_impl::get_full_accounts( const const auto& proposal_idx = _db.get_index_type(); const auto& pidx = dynamic_cast(proposal_idx); const auto& proposals_by_account = pidx.get_secondary_index(); - + std::map results; for (const std::string& account_name_or_id : names_or_ids) @@ -786,7 +807,7 @@ std::map database_api_impl::get_full_accounts( const acnt.withdraws.emplace_back(withdraw); }); - auto pending_payouts_range = + auto pending_payouts_range = _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments)); @@ -1143,7 +1164,7 @@ vector database_api_impl::get_lotteries( asset_id_type stop, return result; } -vector database_api::get_account_lotteries( account_id_type issuer, +vector database_api::get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const @@ -1151,7 +1172,7 @@ vector database_api::get_account_lotteries( account_id_type issuer return my->get_account_lotteries( issuer, stop, limit, start ); } -vector database_api_impl::get_account_lotteries( account_id_type issuer, +vector database_api_impl::get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const @@ -1793,6 +1814,213 @@ map database_api_impl::lookup_committee_member return committee_members_by_account_name; } +////////////////////////////////////////////////////////////////////// +// // +// SON members // +// // +////////////////////////////////////////////////////////////////////// + +vector> database_api::get_sons(const vector& son_ids)const +{ + return my->get_sons( son_ids ); +} + +vector> database_api_impl::get_sons(const vector& son_ids)const +{ + vector> result; result.reserve(son_ids.size()); + std::transform(son_ids.begin(), son_ids.end(), std::back_inserter(result), + [this](son_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; +} + +fc::optional database_api::get_son_by_account(account_id_type account)const +{ + return my->get_son_by_account( account ); +} + +fc::optional database_api_impl::get_son_by_account(account_id_type account) const +{ + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find(account); + if( itr != idx.end() ) + return *itr; + return {}; +} + +map database_api::lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const +{ + return my->lookup_son_accounts( lower_bound_name, limit ); +} + +map database_api_impl::lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const +{ + FC_ASSERT( limit <= 1000 ); + const auto& sons_by_id = _db.get_index_type().indices().get(); + + // we want to order sons by account name, but that name is in the account object + // so the son_index doesn't have a quick way to access it. + // get all the names and look them all up, sort them, then figure out what + // records to return. This could be optimized, but we expect the + // number of witnesses to be few and the frequency of calls to be rare + std::map sons_by_account_name; + for (const son_object& son : sons_by_id) + if (auto account_iter = _db.find(son.son_account)) + if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name + sons_by_account_name.insert(std::make_pair(account_iter->name, son.id)); + + auto end_iter = sons_by_account_name.begin(); + while (end_iter != sons_by_account_name.end() && limit--) + ++end_iter; + sons_by_account_name.erase(end_iter, sons_by_account_name.end()); + return sons_by_account_name; +} + +uint64_t database_api::get_son_count()const +{ + return my->get_son_count(); +} + +uint64_t database_api_impl::get_son_count()const +{ + return _db.get_index_type().indices().size(); +} + +////////////////////////////////////////////////////////////////////// +// // +// SON Wallets // +// // +////////////////////////////////////////////////////////////////////// + +optional database_api::get_active_son_wallet() +{ + return my->get_active_son_wallet(); +} + +optional database_api_impl::get_active_son_wallet() +{ + const auto& idx = _db.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj != idx.rend()) { + return *obj; + } + return {}; +} + +optional database_api::get_son_wallet_by_time_point(time_point_sec time_point) +{ + return my->get_son_wallet_by_time_point(time_point); +} + +optional database_api_impl::get_son_wallet_by_time_point(time_point_sec time_point) +{ + const auto& son_wallets_by_id = _db.get_index_type().indices().get(); + for (const son_wallet_object& swo : son_wallets_by_id) { + if ((time_point >= swo.valid_from) && (time_point < swo.expires)) + return swo; + } + return {}; +} + +vector> database_api::get_son_wallets(uint32_t limit) +{ + return my->get_son_wallets(limit); +} + +vector> database_api_impl::get_son_wallets(uint32_t limit) +{ + FC_ASSERT( limit <= 1000 ); + vector> result; + const auto& son_wallets_by_id = _db.get_index_type().indices().get(); + for (const son_wallet_object& swo : son_wallets_by_id) + result.push_back(swo); + return result; +} + +////////////////////////////////////////////////////////////////////// +// // +// Sidechain Accounts // +// // +////////////////////////////////////////////////////////////////////// + +vector> database_api::get_sidechain_addresses(const vector& sidechain_address_ids)const +{ + return my->get_sidechain_addresses( sidechain_address_ids ); +} + +vector> database_api_impl::get_sidechain_addresses(const vector& sidechain_address_ids)const +{ + vector> result; result.reserve(sidechain_address_ids.size()); + std::transform(sidechain_address_ids.begin(), sidechain_address_ids.end(), std::back_inserter(result), + [this](sidechain_address_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; +} + +vector> database_api::get_sidechain_addresses_by_account(account_id_type account)const +{ + return my->get_sidechain_addresses_by_account( account ); +} + +vector> database_api_impl::get_sidechain_addresses_by_account(account_id_type account)const +{ + vector> result; + const auto& sidechain_addresses_range = _db.get_index_type().indices().get().equal_range(account); + std::for_each(sidechain_addresses_range.first, sidechain_addresses_range.second, + [&result] (const sidechain_address_object& sao) { + if( sao.expires == time_point_sec::maximum() ) + result.push_back(sao); + }); + return result; +} + +vector> database_api::get_sidechain_addresses_by_sidechain(sidechain_type sidechain)const +{ + return my->get_sidechain_addresses_by_sidechain( sidechain ); +} + +vector> database_api_impl::get_sidechain_addresses_by_sidechain(sidechain_type sidechain)const +{ + vector> result; + const auto& sidechain_addresses_range = _db.get_index_type().indices().get().equal_range(sidechain); + std::for_each(sidechain_addresses_range.first, sidechain_addresses_range.second, + [&result] (const sidechain_address_object& sao) { + if( sao.expires == time_point_sec::maximum() ) + result.push_back(sao); + }); + return result; +} + +fc::optional database_api::get_sidechain_address_by_account_and_sidechain(account_id_type account, sidechain_type sidechain)const +{ + return my->get_sidechain_address_by_account_and_sidechain( account, sidechain ); +} + +fc::optional database_api_impl::get_sidechain_address_by_account_and_sidechain(account_id_type account, sidechain_type sidechain)const +{ + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find( boost::make_tuple( account, sidechain, time_point_sec::maximum() ) ); + if( itr != idx.end() ) + return *itr; + return {}; +} + +uint64_t database_api::get_sidechain_addresses_count()const +{ + return my->get_sidechain_addresses_count(); +} + +uint64_t database_api_impl::get_sidechain_addresses_count()const +{ + return _db.get_index_type().indices().size(); +} + ////////////////////////////////////////////////////////////////////// // // // Votes // @@ -1812,6 +2040,7 @@ vector database_api_impl::lookup_vote_ids( const vector& const auto& committee_idx = _db.get_index_type().indices().get(); const auto& for_worker_idx = _db.get_index_type().indices().get(); const auto& against_worker_idx = _db.get_index_type().indices().get(); + const auto& son_idx = _db.get_index_type().indices().get(); vector result; result.reserve( votes.size() ); @@ -1854,6 +2083,16 @@ vector database_api_impl::lookup_vote_ids( const vector& } break; } + case vote_id_type::son: + { + auto itr = son_idx.find( id ); + if( itr != son_idx.end() ) + result.emplace_back( variant( *itr, 1 ) ); + else + result.emplace_back( variant() ); + break; + } + case vote_id_type::VOTE_TYPE_COUNT: break; // supress unused enum value warnings default: FC_CAPTURE_AND_THROW( fc::out_of_range_exception, (id) ); @@ -2203,7 +2442,7 @@ vector database_api::get_tournaments(tournament_id_type stop, vector database_api_impl::get_tournaments(tournament_id_type stop, unsigned limit, - tournament_id_type start) + tournament_id_type start) { vector result; const auto& tournament_idx = _db.get_index_type().indices().get(); @@ -2230,7 +2469,7 @@ vector database_api_impl::get_tournaments_by_state(tournament unsigned limit, tournament_id_type start, tournament_state state) -{ +{ vector result; const auto& tournament_idx = _db.get_index_type().indices().get(); for (auto elem: tournament_idx) { @@ -2888,6 +3127,23 @@ vector database_api_impl::get_offer_history_by_bidder(cons return result; } + +vector database_api::get_account_roles_by_owner(account_id_type owner) const +{ + return my->get_account_roles_by_owner(owner); +} + +vector database_api_impl::get_account_roles_by_owner(account_id_type owner) const +{ + const auto &idx_aro = _db.get_index_type().indices().get(); + auto idx_aro_range = idx_aro.equal_range(owner); + vector result; + for (auto itr = idx_aro_range.first; itr != idx_aro_range.second; ++itr) + { + result.push_back(*itr); + } + return result; +} ////////////////////////////////////////////////////////////////////// // // // Private methods // @@ -2986,7 +3242,7 @@ void database_api_impl::handle_object_changed(bool force_notify, bool full_objec /// if a connection hangs then this could get backed up and result in /// a failure to exit cleanly. //fc::async([capture_this,this,updates,market_broadcast_queue](){ - //if( _subscribe_callback ) + //if( _subscribe_callback ) // _subscribe_callback( updates ); for(auto id : ids) diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index a313e2f8..a436aacd 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -61,8 +61,14 @@ namespace graphene { namespace app { plug->plugin_set_program_options(plugin_cli_options, plugin_cfg_options); if( !plugin_cli_options.options().empty() ) _cli_options.add(plugin_cli_options); + if( !plugin_cfg_options.options().empty() ) + { + std::string header_name = "plugin-cfg-header-" + plug->plugin_name(); + std::string header_desc = plug->plugin_name() + " plugin options"; + _cfg_options.add_options()(header_name.c_str(), header_desc.c_str()); _cfg_options.add(plugin_cfg_options); + } add_available_plugin( plug ); return plug; diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 0b141125..ec5f8c1a 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -43,6 +43,9 @@ #include #include #include +#include +#include +#include #include #include @@ -52,6 +55,7 @@ #include #include #include +#include #include @@ -599,6 +603,102 @@ class database_api */ map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + ///////////////// + // SON members // + ///////////////// + + /** + * @brief Get a list of SONs by ID + * @param son_ids IDs of the SONs to retrieve + * @return The SONs corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_sons(const vector& son_ids)const; + + /** + * @brief Get the SON owned by a given account + * @param account The ID of the account whose SON should be retrieved + * @return The SON object, or null if the account does not have a SON + */ + fc::optional get_son_by_account(account_id_type account)const; + + /** + * @brief Get names and IDs for registered SONs + * @param lower_bound_name Lower bound of the first name to return + * @param limit Maximum number of results to return -- must not exceed 1000 + * @return Map of SON names to corresponding IDs + */ + map lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const; + + /** + * @brief Get the total number of SONs registered with the blockchain + */ + uint64_t get_son_count()const; + + ///////////////////////// + // SON Wallets // + ///////////////////////// + + /** + * @brief Get active SON wallet + * @return Active SON wallet object + */ + optional get_active_son_wallet(); + + /** + * @brief Get SON wallet that was active for a given time point + * @param time_point Time point + * @return SON wallet object, for the wallet that was active for a given time point + */ + optional get_son_wallet_by_time_point(time_point_sec time_point); + + /** + * @brief Get full list of SON wallets + * @param limit Maximum number of results to return + * @return A list of SON wallet objects + */ + vector> get_son_wallets(uint32_t limit); + + ///////////////////////// + // Sidechain Addresses // + ///////////////////////// + + /** + * @brief Get a list of sidechain addresses + * @param sidechain_address_ids IDs of the sidechain addresses to retrieve + * @return The sidechain accounts corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_sidechain_addresses(const vector& sidechain_address_ids)const; + + /** + * @brief Get the sidechain addresses for a given account + * @param account The ID of the account whose sidechain addresses should be retrieved + * @return The sidechain addresses objects, or null if the account does not have a sidechain addresses + */ + vector> get_sidechain_addresses_by_account(account_id_type account)const; + + /** + * @brief Get the sidechain addresses for a given sidechain + * @param sidechain Sidechain for which addresses should be retrieved + * @return The sidechain addresses objects, or null if the sidechain does not have any addresses + */ + vector> get_sidechain_addresses_by_sidechain(sidechain_type sidechain)const; + + /** + * @brief Get the sidechain addresses for a given account and sidechain + * @param account The ID of the account whose sidechain addresses should be retrieved + * @param sidechain Sidechain for which address should be retrieved + * @return The sidechain addresses objects, or null if the account does not have a sidechain addresses for a given sidechain + */ + fc::optional get_sidechain_address_by_account_and_sidechain(account_id_type account, sidechain_type sidechain)const; + + /** + * @brief Get the total number of sidechain addresses registered with the blockchain + */ + uint64_t get_sidechain_addresses_count()const; /// WORKERS @@ -829,6 +929,11 @@ class database_api vector get_offer_history_by_issuer(const offer_history_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const; vector get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const; vector get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const; + + ////////////////// + // ACCOUNT ROLE // + ////////////////// + vector get_account_roles_by_owner(account_id_type owner) const; private: std::shared_ptr< database_api_impl > my; }; @@ -937,6 +1042,24 @@ FC_API(graphene::app::database_api, (get_committee_member_by_account) (lookup_committee_member_accounts) + // SON members + (get_sons) + (get_son_by_account) + (lookup_son_accounts) + (get_son_count) + + // SON wallets + (get_active_son_wallet) + (get_son_wallet_by_time_point) + (get_son_wallets) + + // Sidechain addresses + (get_sidechain_addresses) + (get_sidechain_addresses_by_account) + (get_sidechain_addresses_by_sidechain) + (get_sidechain_address_by_account_and_sidechain) + (get_sidechain_addresses_count) + // workers (get_workers_by_account) // Votes @@ -999,4 +1122,7 @@ FC_API(graphene::app::database_api, (get_offer_history_by_issuer) (get_offer_history_by_item) (get_offer_history_by_bidder) + + // Account Roles + (get_account_roles_by_owner) ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt old mode 100644 new mode 100755 index 88d868e5..73024f69 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -124,6 +124,18 @@ add_library( graphene_chain offer_evaluator.cpp nft_evaluator.cpp protocol/nft.cpp + protocol/account_role.cpp + account_role_evaluator.cpp + + son_evaluator.cpp + son_object.cpp + + son_wallet_evaluator.cpp + son_wallet_deposit_evaluator.cpp + son_wallet_withdraw_evaluator.cpp + + sidechain_address_evaluator.cpp + sidechain_transaction_evaluator.cpp ${HEADERS} ${PROTOCOL_HEADERS} diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index ad6ac5dc..a166e7e5 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -110,6 +110,8 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio } if( d.head_block_time() < HARDFORK_999_TIME ) FC_ASSERT( !op.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); + if (d.head_block_time() < HARDFORK_SON_TIME) + FC_ASSERT(op.name != "son-account", "Son account creation before SON hardfork"); FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." ); diff --git a/libraries/chain/account_role_evaluator.cpp b/libraries/chain/account_role_evaluator.cpp new file mode 100644 index 00000000..9c0274e4 --- /dev/null +++ b/libraries/chain/account_role_evaluator.cpp @@ -0,0 +1,162 @@ +#include + +#include +#include +#include +#include + +namespace graphene +{ + namespace chain + { + + void_result account_role_create_evaluator::do_evaluate(const account_role_create_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + + rbac_operation_hardfork_visitor arvtor(now); + for (const auto &op_type : op.allowed_operations) + { + arvtor(op_type); + } + + for (const auto &acc : op.whitelisted_accounts) + { + acc(d); + } + + FC_ASSERT(op.valid_to > now, "valid_to expiry should be in future"); + FC_ASSERT((op.valid_to - now) <= fc::seconds(d.get_global_properties().parameters.account_roles_max_lifetime()), "Validity of the account role beyond max expiry"); + + const auto &ar_idx = d.get_index_type().indices().get(); + auto aro_range = ar_idx.equal_range(op.owner); + FC_ASSERT(std::distance(aro_range.first, aro_range.second) < d.get_global_properties().parameters.account_roles_max_per_account(), "Max account roles that can be created by one owner is reached"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + object_id_type account_role_create_evaluator::do_apply(const account_role_create_operation &op) + { + try + { + database &d = db(); + return d.create([&op](account_role_object &obj) mutable { + obj.owner = op.owner; + obj.name = op.name; + obj.metadata = op.metadata; + obj.allowed_operations = op.allowed_operations; + obj.whitelisted_accounts = op.whitelisted_accounts; + obj.valid_to = op.valid_to; + }) + .id; + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_update_evaluator::do_evaluate(const account_role_update_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + const account_role_object &aobj = op.account_role_id(d); + FC_ASSERT(aobj.owner == op.owner, "Only owner account can update account role object"); + + for (const auto &op_type : op.allowed_operations_to_remove) + { + FC_ASSERT(aobj.allowed_operations.find(op_type) != aobj.allowed_operations.end(), + "Cannot remove non existent operation"); + } + + for (const auto &acc : op.accounts_to_remove) + { + FC_ASSERT(aobj.whitelisted_accounts.find(acc) != aobj.whitelisted_accounts.end(), + "Cannot remove non existent account"); + } + + rbac_operation_hardfork_visitor arvtor(now); + for (const auto &op_type : op.allowed_operations_to_add) + { + arvtor(op_type); + } + FC_ASSERT((aobj.allowed_operations.size() + op.allowed_operations_to_add.size() - op.allowed_operations_to_remove.size()) > 0, "Allowed operations should be positive"); + + for (const auto &acc : op.accounts_to_add) + { + acc(d); + } + FC_ASSERT((aobj.whitelisted_accounts.size() + op.accounts_to_add.size() - op.accounts_to_remove.size()) > 0, "Accounts should be positive"); + + if (op.valid_to) + { + FC_ASSERT(*op.valid_to > now, "valid_to expiry should be in future"); + FC_ASSERT((*op.valid_to - now) <= fc::seconds(d.get_global_properties().parameters.account_roles_max_lifetime()), "Validity of the account role beyond max expiry"); + } + + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_update_evaluator::do_apply(const account_role_update_operation &op) + { + try + { + database &d = db(); + const account_role_object &aobj = op.account_role_id(d); + d.modify(aobj, [&op](account_role_object &obj) { + if (op.name) + obj.name = *op.name; + if (op.metadata) + obj.metadata = *op.metadata; + obj.allowed_operations.insert(op.allowed_operations_to_add.begin(), op.allowed_operations_to_add.end()); + obj.whitelisted_accounts.insert(op.accounts_to_add.begin(), op.accounts_to_add.end()); + for (const auto &op_type : op.allowed_operations_to_remove) + obj.allowed_operations.erase(op_type); + for (const auto &acc : op.accounts_to_remove) + obj.whitelisted_accounts.erase(acc); + if (op.valid_to) + obj.valid_to = *op.valid_to; + }); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_delete_evaluator::do_evaluate(const account_role_delete_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + const account_role_object &aobj = op.account_role_id(d); + FC_ASSERT(aobj.owner == op.owner, "Only owner account can delete account role object"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_delete_evaluator::do_apply(const account_role_delete_operation &op) + { + try + { + database &d = db(); + const account_role_object &aobj = op.account_role_id(d); + d.remove(aobj); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 7a26a2cb..e6168438 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -42,6 +42,9 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o database& d = db(); + if (d.head_block_time() < HARDFORK_SON_TIME) + FC_ASSERT(op.symbol != "BTC", "BTC asset creation before SON hardfork"); + const auto& chain_parameters = d.get_global_properties().parameters; FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); diff --git a/libraries/chain/custom_account_authority_evaluator.cpp b/libraries/chain/custom_account_authority_evaluator.cpp index 200590f6..bbf29c2c 100644 --- a/libraries/chain/custom_account_authority_evaluator.cpp +++ b/libraries/chain/custom_account_authority_evaluator.cpp @@ -4,37 +4,13 @@ #include #include #include +#include namespace graphene { namespace chain { -struct rbac_operation_hardfork_visitor -{ - typedef void result_type; - const fc::time_point_sec block_time; - - rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {} - void operator()(int op_type) const - { - int first_allowed_op = operation::tag::value; - switch (op_type) - { - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permission not allowed on this operation yet!"); - break; - default: - FC_ASSERT(op_type < first_allowed_op, "Custom permission not allowed on this operation!"); - } - } -}; - void_result create_custom_account_authority_evaluator::do_evaluate(const custom_account_authority_create_operation &op) { try @@ -149,4 +125,4 @@ void_result delete_custom_account_authority_evaluator::do_apply(const custom_acc } } // namespace chain -} // namespace graphene \ No newline at end of file +} // namespace graphene diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index e2fc9aab..e8a49199 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -426,6 +426,7 @@ processed_transaction database::push_proposal(const proposal_object& proposal) auto session = _undo_db.start_undo_session(true); for( auto& op : proposal.proposed_transaction.operations ) eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); + remove_son_proposal(proposal); remove(proposal); session.merge(); } catch ( const fc::exception& e ) { @@ -698,8 +699,13 @@ void database::_apply_block( const signed_block& next_block ) _current_virtual_op = 0; } - if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) - update_witness_schedule(next_block); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) { + update_witness_schedule(next_block); + if(global_props.active_sons.size() > 0) { + update_son_schedule(next_block); + } + } + const uint32_t missed = update_witness_missed_blocks( next_block ); update_global_dynamic_data( next_block, missed ); update_signing_witness(signing_witness, next_block); @@ -729,8 +735,13 @@ void database::_apply_block( const signed_block& next_block ) // update_global_dynamic_data() as perhaps these methods only need // to be called for header validation? update_maintenance_flag( maint_needed ); - if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) - update_witness_schedule(); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) { + update_witness_schedule(); + if(global_props.active_sons.size() > 0) { + update_son_schedule(); + } + } + if( !_node_property_object.debug_updates.empty() ) apply_debug_updates(); @@ -789,12 +800,14 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx if( !(skip & (skip_transaction_signatures | skip_authority_check) ) ) { - auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; - auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; - auto get_custom = [&]( account_id_type id, const operation& op ) { + auto get_active = [this]( account_id_type id ) { return &id(*this).active; }; + auto get_owner = [this]( account_id_type id ) { return &id(*this).owner; }; + auto get_custom = [this]( account_id_type id, const operation& op ) { return get_account_custom_authorities(id, op); }; - trx.verify_authority( chain_id, get_active, get_owner, get_custom, get_global_properties().parameters.max_authority_depth ); + trx.verify_authority( chain_id, get_active, get_owner, get_custom, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(head_block_time()), + get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 0f7af1a8..07b96fea 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include #include @@ -192,4 +195,126 @@ bool database::item_locked(const nft_id_type &item) const auto items_itr = market_items._locked_items.find(item); return (items_itr != market_items._locked_items.end()); } + +bool database::account_role_valid(const account_role_object &aro, account_id_type account, optional op_type) const +{ + return (aro.valid_to > head_block_time()) && + (aro.whitelisted_accounts.find(account) != aro.whitelisted_accounts.end()) && + (!op_type || (aro.allowed_operations.find(*op_type) != aro.allowed_operations.end())); +} +std::set database::get_sons_being_deregistered() +{ + std::set ret; + const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); + + for( auto& son_proposal : son_proposal_idx ) + { + if(son_proposal.proposal_type == son_proposal_type::son_deregister_proposal) + { + ret.insert(son_proposal.son_id); + } + } + return ret; +} + +std::set database::get_sons_to_be_deregistered() +{ + std::set ret; + const auto& son_idx = get_index_type().indices().get< by_id >(); + + for( auto& son : son_idx ) + { + if(son.status == son_status::in_maintenance) + { + auto stats = son.statistics(*this); + // TODO : We need to add a function that returns if we can deregister SON + // i.e. with introduction of PW code, we have to make a decision if the SON + // is needed for release of funds from the PW + if(head_block_time() - stats.last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time())) + { + ret.insert(son.id); + } + } + } + return ret; +} + +std::set database::get_sons_being_reported_down() +{ + std::set ret; + const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); + + for( auto& son_proposal : son_proposal_idx ) + { + if(son_proposal.proposal_type == son_proposal_type::son_report_down_proposal) + { + ret.insert(son_proposal.son_id); + } + } + return ret; +} + +fc::optional database::create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ) +{ + son_deregister_operation son_dereg_op; + son_dereg_op.payer = get_global_properties().parameters.son_account(); + son_dereg_op.son_id = son_id; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = paying_son; + proposal_op.proposed_ops.push_back( op_wrapper( son_dereg_op ) ); + uint32_t lifetime = ( get_global_properties().parameters.block_interval * get_global_properties().active_witnesses.size() ) * 3; + proposal_op.expiration_time = time_point_sec( head_block_time().sec_since_epoch() + lifetime ); + return proposal_op; +} + +signed_transaction database::create_signed_transaction( const fc::ecc::private_key& signing_private_key, const operation& op ) +{ + signed_transaction processed_trx; + auto dyn_props = get_dynamic_global_properties(); + processed_trx.set_reference_block( dyn_props.head_block_id ); + processed_trx.set_expiration( head_block_time() + get_global_properties().parameters.maximum_time_until_expiration ); + processed_trx.operations.push_back( op ); + current_fee_schedule().set_fee( processed_trx.operations.back() ); + + processed_trx.sign( signing_private_key, get_chain_id() ); + + return processed_trx; +} + +bool database::is_son_dereg_valid( son_id_type son_id ) +{ + const auto& son_idx = get_index_type().indices().get< by_id >(); + auto son = son_idx.find( son_id ); + if(son == son_idx.end()) + { + return false; + } + + return (son->status == son_status::in_maintenance && + (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time()))); +} + +bool database::is_son_active( son_id_type son_id ) +{ + const auto& son_idx = get_index_type().indices().get< by_id >(); + auto son = son_idx.find( son_id ); + if(son == son_idx.end()) + { + return false; + } + + const global_property_object& gpo = get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + + auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), son_id); + return (it_son != active_son_ids.end()); +} + } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 9ae1fb96..e857ed94 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -60,6 +61,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -85,6 +93,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include @@ -186,6 +201,9 @@ const uint8_t offer_object::type_id; const uint8_t offer_history_object::space_id; const uint8_t offer_history_object::type_id; +const uint8_t account_role_object::space_id; +const uint8_t account_role_object::type_id; + void database::initialize_evaluators() { _operation_evaluators.resize(255); @@ -275,6 +293,28 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -291,6 +331,7 @@ void database::initialize_indexes() acnt_index->add_secondary_index(); add_index< primary_index >(); // 256 members per chunk + add_index< primary_index >(); add_index< primary_index >(); // 1024 witnesses per chunk add_index< primary_index >(); add_index< primary_index >(); @@ -323,6 +364,15 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + + add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + + add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); @@ -339,6 +389,7 @@ void database::initialize_indexes() add_index< primary_index> >(); add_index< primary_index > >(); add_index< primary_index > >(); + add_index< primary_index > >(); add_index< primary_index > >(); add_index< primary_index< special_authority_index > >(); add_index< primary_index< buyback_index > >(); @@ -353,6 +404,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); } @@ -997,6 +1049,29 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); FC_ASSERT( _p_witness_schedule_obj->id == witness_schedule_id_type() ); + // Initialize witness schedule +#ifndef NDEBUG + const son_schedule_object& sso = +#endif + create([&](son_schedule_object& _sso) + { + // for scheduled + memset(_sso.rng_seed.begin(), 0, _sso.rng_seed.size()); + + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_witnesses = get_global_properties().active_witnesses; + + _sso.scheduler = son_scheduler(); + _sso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1); + + + _sso.last_scheduling_block = 0; + + _sso.recent_slots_filled = fc::uint128::max_value(); + }); + assert( sso.id == son_schedule_id_type() ); + // Enable fees modify(get_global_properties(), [&genesis_state](global_property_object& p) { p.parameters.current_fees = genesis_state.initial_parameters.current_fees; diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 03d2a274..d23b0fe2 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,32 @@ vector> database::sort return refs; } +template<> +vector> database::sort_votable_objects(size_t count) const +{ + const auto& all_sons = get_index_type().indices().get< by_id >(); + std::vector> refs; + for( auto& son : all_sons ) + { + if(son.has_valid_config() && son.status != son_status::deregistered) + { + refs.push_back(std::cref(son)); + } + } + count = std::min(count, refs.size()); + std::partial_sort(refs.begin(), refs.begin() + count, refs.end(), + [this](const son_object& a, const son_object& b)->bool { + share_type oa_vote = _vote_tally_buffer[a.vote_id]; + share_type ob_vote = _vote_tally_buffer[b.vote_id]; + if( oa_vote != ob_vote ) + return oa_vote > ob_vote; + return a.vote_id < b.vote_id; + }); + + refs.resize(count, refs.front()); + return refs; +} + template void database::perform_account_maintenance(Type tally_helper) { @@ -150,6 +177,215 @@ void database::update_worker_votes() } } +void database::pay_sons() +{ + auto get_weight = []( uint64_t total_votes ) { + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + uint16_t weight = std::max((total_votes >> bits_to_drop), uint64_t(1) ); + return weight; + }; + time_point_sec now = head_block_time(); + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + // Current requirement is that we have to pay every 24 hours, so the following check + if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { + uint64_t weighted_total_txs_signed = 0; + share_type son_budget = dpo.son_budget; + get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight](const object& o) { + const son_statistics_object& s = static_cast(o); + const auto& idx = get_index_type().indices().get(); + auto son_obj = idx.find( s.owner ); + auto son_weight = get_weight(_vote_tally_buffer[son_obj->vote_id]); + weighted_total_txs_signed += (s.txs_signed * son_weight); + }); + + + // Now pay off each SON proportional to the number of transactions signed. + get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &dpo, &son_budget, &get_weight](const object& o) { + const son_statistics_object& s = static_cast(o); + if(s.txs_signed > 0){ + auto son_params = get_global_properties().parameters; + const auto& idx = get_index_type().indices().get(); + auto son_obj = idx.find( s.owner ); + auto son_weight = get_weight(_vote_tally_buffer[son_obj->vote_id]); + share_type pay = (s.txs_signed * son_weight * son_budget.value)/weighted_total_txs_signed; + + modify( *son_obj, [&]( son_object& _son_obj) + { + _son_obj.pay_son_fee(pay, *this); + }); + //Remove the amount paid out to SON from global SON Budget + modify( dpo, [&]( dynamic_global_property_object& _dpo ) + { + _dpo.son_budget -= pay; + } ); + //Reset the tx counter in each son statistics object + modify( s, [&]( son_statistics_object& _s) + { + _s.total_txs_signed += _s.txs_signed; + _s.txs_signed = 0; + }); + } + }); + //Note the last son pay out time + modify( dpo, [&]( dynamic_global_property_object& _dpo ) + { + _dpo.last_son_payout_time = now; + }); + } +} + +void database::update_son_metrics(const vector& curr_active_sons) +{ + vector current_sons; + + current_sons.reserve(curr_active_sons.size()); + std::transform(curr_active_sons.begin(), curr_active_sons.end(), + std::inserter(current_sons, current_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + const auto& son_idx = get_index_type().indices().get< by_id >(); + for( auto& son : son_idx ) + { + auto& stats = son.statistics(*this); + bool is_active_son = (std::find(current_sons.begin(), current_sons.end(), son.id) != current_sons.end()); + modify( stats, [&]( son_statistics_object& _stats ) + { + _stats.total_downtime += _stats.current_interval_downtime; + _stats.current_interval_downtime = 0; + if(is_active_son) + { + _stats.total_voted_time = _stats.total_voted_time + get_global_properties().parameters.maintenance_interval; + } + }); + } +} + +void database::update_son_statuses(const vector& curr_active_sons, const vector& new_active_sons) +{ + vector current_sons, new_sons; + vector sons_to_remove, sons_to_add; + const auto& idx = get_index_type().indices().get(); + + current_sons.reserve(curr_active_sons.size()); + std::transform(curr_active_sons.begin(), curr_active_sons.end(), + std::inserter(current_sons, current_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + new_sons.reserve(new_active_sons.size()); + std::transform(new_active_sons.begin(), new_active_sons.end(), + std::inserter(new_sons, new_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + // find all cur_active_sons members that is not in new_active_sons + for_each(current_sons.begin(), current_sons.end(), + [&sons_to_remove, &new_sons](const son_id_type& si) + { + if(std::find(new_sons.begin(), new_sons.end(), si) == + new_sons.end()) + { + sons_to_remove.push_back(si); + } + } + ); + + for( const auto& sid : sons_to_remove ) + { + auto son = idx.find( sid ); + if(son == idx.end()) // SON is deleted already + continue; + // keep maintenance status for nodes becoming inactive + if(son->status == son_status::active) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::inactive; + }); + } + } + + // find all new_active_sons members that is not in cur_active_sons + for_each(new_sons.begin(), new_sons.end(), + [&sons_to_add, ¤t_sons](const son_id_type& si) + { + if(std::find(current_sons.begin(), current_sons.end(), si) == + current_sons.end()) + { + sons_to_add.push_back(si); + } + } + ); + + for( const auto& sid : sons_to_add ) + { + auto son = idx.find( sid ); + FC_ASSERT(son != idx.end(), "Invalid SON in active list, id={sonid}.", ("sonid", sid)); + // keep maintenance status for new nodes + if(son->status == son_status::inactive) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::active; + }); + } + } + + ilog("New SONS"); + for(size_t i = 0; i < new_sons.size(); i++) { + auto son = idx.find( new_sons[i] ); + if(son == idx.end()) // SON is deleted already + continue; + ilog( "${s}, status = ${ss}, total_votes = ${sv}", ("s", new_sons[i])("ss", son->status)("sv", son->total_votes) ); + } + + if( sons_to_remove.size() > 0 ) + { + remove_inactive_son_proposals(sons_to_remove); + } +} + +void database::update_son_wallet(const vector& new_active_sons) +{ + bool should_recreate_pw = true; + + // Expire for current son_wallet_object wallet, if exists + const auto& idx_swi = get_index_type().indices().get(); + auto obj = idx_swi.rbegin(); + if (obj != idx_swi.rend()) { + // Compare current wallet SONs and to-be lists of active sons + auto cur_wallet_sons = (*obj).sons; + + bool wallet_son_sets_equal = (cur_wallet_sons.size() == new_active_sons.size()); + if (wallet_son_sets_equal) { + for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { + wallet_son_sets_equal = wallet_son_sets_equal && cur_wallet_sons.at(i) == new_active_sons.at(i); + } + } + + should_recreate_pw = !wallet_son_sets_equal; + + if (should_recreate_pw) { + modify(*obj, [&, obj](son_wallet_object &swo) { + swo.expires = head_block_time(); + }); + } + } + + should_recreate_pw = should_recreate_pw && (new_active_sons.size() >= get_chain_properties().immutable_parameters.min_son_count); + + if (should_recreate_pw) { + // Create new son_wallet_object, to initiate wallet recreation + create( [&]( son_wallet_object& obj ) { + obj.valid_from = head_block_time(); + obj.expires = time_point_sec::maximum(); + obj.sons.insert(obj.sons.end(), new_active_sons.begin(), new_active_sons.end()); + }); + } +} + void database::pay_workers( share_type& budget ) { const auto head_time = head_block_time(); @@ -207,7 +443,7 @@ void database::update_active_witnesses() /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) - share_type stake_tally = 0; + share_type stake_tally = 0; size_t witness_count = 0; if( stake_target > 0 ) @@ -306,7 +542,7 @@ void database::update_active_witnesses() void database::update_active_committee_members() { try { assert( _committee_count_histogram_buffer.size() > 0 ); - share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2; + share_type stake_target = (_total_voting_stake-_committee_count_histogram_buffer[0]) / 2; /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) @@ -395,6 +631,146 @@ void database::update_active_committee_members() }); } FC_CAPTURE_AND_RETHROW() } +void database::update_active_sons() +{ try { + assert( _son_count_histogram_buffer.size() > 0 ); + share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer[0]) / 2; + + /// accounts that vote for 0 or 1 son do not get to express an opinion on + /// the number of sons to have (they abstain and are non-voting accounts) + + share_type stake_tally = 0; + + size_t son_count = 0; + if( stake_target > 0 ) + { + while( (son_count < _son_count_histogram_buffer.size() - 1) + && (stake_tally <= stake_target) ) + { + stake_tally += _son_count_histogram_buffer[++son_count]; + } + } + + const global_property_object& gpo = get_global_properties(); + const chain_parameters& cp = gpo.parameters; + auto sons = sort_votable_objects(cp.maximum_son_count); + + const auto& all_sons = get_index_type().indices(); + + auto& local_vote_buffer_ref = _vote_tally_buffer; + for( const son_object& son : all_sons ) + { + if(son.status == son_status::request_maintenance) + { + auto& stats = son.statistics(*this); + modify( stats, [&]( son_statistics_object& _s){ + _s.last_down_timestamp = head_block_time(); + }); + } + modify( son, [local_vote_buffer_ref]( son_object& obj ){ + obj.total_votes = local_vote_buffer_ref[obj.vote_id]; + if(obj.status == son_status::request_maintenance) + obj.status = son_status::in_maintenance; + }); + } + + // Update SON authority + if( gpo.parameters.son_account() != GRAPHENE_NULL_ACCOUNT ) + { + modify( get(gpo.parameters.son_account()), [&]( account_object& a ) + { + if( head_block_time() < HARDFORK_533_TIME ) + { + map weights; + a.active.weight_threshold = 0; + a.active.account_auths.clear(); + + for( const son_object& son : sons ) + { + weights.emplace(son.son_account, uint64_t(1)); + } + + for( const auto& weight : weights ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + a.active.account_auths[weight.first] += 1; + a.active.weight_threshold += 1; + } + + a.active.weight_threshold *= 2; + a.active.weight_threshold /= 3; + a.active.weight_threshold += 1; + } + else + { + vote_counter vc; + for( const son_object& son : sons ) + vc.add( son.son_account, UINT64_C(1) ); + vc.finish_2_3( a.active ); + } + } ); + } + + + // Compare current and to-be lists of active sons + auto cur_active_sons = gpo.active_sons; + vector new_active_sons; + const auto &acc = get(gpo.parameters.son_account()); + for( const son_object& son : sons ) { + son_info swi; + swi.son_id = son.id; + swi.weight = acc.active.account_auths.at(son.son_account); + swi.signing_key = son.signing_key; + swi.sidechain_public_keys = son.sidechain_public_keys; + new_active_sons.push_back(swi); + } + + bool son_sets_equal = (cur_active_sons.size() == new_active_sons.size()); + if (son_sets_equal) { + for( size_t i = 0; i < cur_active_sons.size(); i++ ) { + son_sets_equal = son_sets_equal && cur_active_sons.at(i) == new_active_sons.at(i); + } + } + + if (son_sets_equal) { + ilog( "Active SONs set NOT CHANGED" ); + } else { + ilog( "Active SONs set CHANGED" ); + + update_son_wallet(new_active_sons); + update_son_statuses(cur_active_sons, new_active_sons); + } + + // Update son performance metrics + update_son_metrics(cur_active_sons); + + modify(gpo, [&]( global_property_object& gp ){ + gp.active_sons.clear(); + gp.active_sons.reserve(new_active_sons.size()); + gp.active_sons.insert(gp.active_sons.end(), new_active_sons.begin(), new_active_sons.end()); + }); + + const son_schedule_object& sso = son_schedule_id_type()(*this); + modify(sso, [&](son_schedule_object& _sso) + { + flat_set active_sons; + active_sons.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_sons, active_sons.end()), + [](const son_info& swi) { + return swi.son_id; + }); + _sso.scheduler.update(active_sons); + // similar to witness, produce schedule for sons + if(cur_active_sons.size() == 0 && new_active_sons.size() > 0) + { + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + for( size_t i=0; i= HARDFORK_SON_TIME){ + rec.leftover_son_funds = dpo.son_budget; + available_funds += rec.leftover_son_funds; + son_budget = gpo.parameters.son_pay_max(); + son_budget = std::min(son_budget, available_funds); + rec.son_budget = son_budget; + available_funds -= son_budget; + } + fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value; worker_budget_u128 *= uint64_t(time_to_maint); worker_budget_u128 /= 60*60*24; @@ -502,9 +889,11 @@ void database::process_budget() rec.supply_delta = rec.witness_budget + rec.worker_budget + + rec.son_budget - rec.leftover_worker_funds - rec.from_accumulated_fees - - rec.from_unused_witness_budget; + - rec.from_unused_witness_budget + - rec.leftover_son_funds; modify(core, [&]( asset_dynamic_data_object& _core ) { @@ -513,9 +902,11 @@ void database::process_budget() assert( rec.supply_delta == witness_budget + worker_budget + + son_budget - leftover_worker_funds - _core.accumulated_fees - dpo.witness_budget + - dpo.son_budget ); _core.accumulated_fees = 0; }); @@ -526,6 +917,7 @@ void database::process_budget() // available_funds, we replace it with witness_budget // instead of adding it. _dpo.witness_budget = witness_budget; + _dpo.son_budget = son_budget; _dpo.last_budget_time = now; }); @@ -941,14 +1333,23 @@ void clear_expired_custom_account_authorities(database& db) } } +void clear_expired_account_roles(database& db) +{ + const auto& arindex = db.get_index_type().indices().get(); + while(!arindex.empty() && arindex.begin()->valid_to < db.head_block_time()) + { + db.remove(*arindex.begin()); + } +} + // Schedules payouts from a dividend distribution account to the current holders of the // dividend-paying asset. This takes any deposits made to the dividend distribution account // since the last time it was called, and distributes them to the current owners of the // dividend-paying asset according to the amount they own. -void schedule_pending_dividend_balances(database& db, +void schedule_pending_dividend_balances(database& db, const asset_object& dividend_holder_asset_obj, const asset_dividend_data_object& dividend_data, - const fc::time_point_sec& current_head_block_time, + const fc::time_point_sec& current_head_block_time, const account_balance_index& balance_index, const vesting_balance_index& vesting_index, const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index, @@ -957,7 +1358,7 @@ void schedule_pending_dividend_balances(database& db, dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}", ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); auto balance_by_acc_index = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); - auto current_distribution_account_balance_range = + auto current_distribution_account_balance_range = //balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); balance_by_acc_index.get_account_balances(dividend_data.dividend_distribution_account); auto previous_distribution_account_balance_range = @@ -968,7 +1369,7 @@ void schedule_pending_dividend_balances(database& db, const auto& gpo = db.get_global_properties(); // get the list of accounts that hold nonzero balances of the dividend asset - auto holder_balances_begin = + auto holder_balances_begin = balance_index.indices().get().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id)); auto holder_balances_end = balance_index.indices().get().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type())); @@ -1028,7 +1429,7 @@ void schedule_pending_dividend_balances(database& db, ("previous", (int64_t)std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second))); // when we pay out the dividends to the holders, we need to know the total balance of the dividend asset in all - // accounts other than the distribution account (it would be silly to distribute dividends back to + // accounts other than the distribution account (it would be silly to distribute dividends back to // the distribution account) share_type total_balance_of_dividend_asset; if(db.head_block_time() >= HARDFORK_GPOS_TIME && dividend_holder_asset_obj.symbol == GRAPHENE_SYMBOL) { // only core @@ -1059,7 +1460,7 @@ void schedule_pending_dividend_balances(database& db, share_type previous_balance; asset_id_type payout_asset_type; - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || current_distribution_account_balance_iter->second->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) { // there are no more previous balances or there is no previous balance for this particular asset type @@ -1067,7 +1468,7 @@ void schedule_pending_dividend_balances(database& db, current_balance = current_distribution_account_balance_iter->second->balance; idump((payout_asset_type)(current_balance)); } - else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || + else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->second->asset_type) { // there are no more current balances or there is no current balance for this particular previous asset type @@ -1086,7 +1487,7 @@ void schedule_pending_dividend_balances(database& db, share_type delta_balance = current_balance - previous_balance; - // Next, figure out if we want to share this out -- if the amount added to the distribution + // Next, figure out if we want to share this out -- if the amount added to the distribution // account since last payout is too small, we won't bother. share_type total_fee_per_asset_in_payout_asset; @@ -1095,7 +1496,7 @@ void schedule_pending_dividend_balances(database& db, { payout_asset_object = &db.get_core_asset(); total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core; - dlog("Fee for distributing ${payout_asset_type}: ${fee}", + dlog("Fee for distributing ${payout_asset_type}: ${fee}", ("payout_asset_type", asset_id_type()(db).symbol) ("fee", asset(total_fee_per_asset_in_core, asset_id_type()))); } @@ -1111,7 +1512,7 @@ void schedule_pending_dividend_balances(database& db, FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type); total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount; - dlog("Fee for distributing ${payout_asset_type}: ${fee}", + dlog("Fee for distributing ${payout_asset_type}: ${fee}", ("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset)); } @@ -1124,7 +1525,7 @@ void schedule_pending_dividend_balances(database& db, wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options)); minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64(); } - + dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance)); if (delta_balance > 0) { @@ -1137,7 +1538,7 @@ void schedule_pending_dividend_balances(database& db, db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) { d.accumulated_fees += total_fee_per_asset_in_core; }); - db.adjust_balance(dividend_data.dividend_distribution_account, + db.adjust_balance(dividend_data.dividend_distribution_account, asset(-total_fee_per_asset_in_core, asset_id_type())); delta_balance -= total_fee_per_asset_in_core; } @@ -1152,7 +1553,7 @@ void schedule_pending_dividend_balances(database& db, ("need", asset(total_fee_per_asset_in_core, asset_id_type())) ("have", asset(dynamic_data.fee_pool, payout_asset_type))); // deduct the fee from the dividend distribution account - db.adjust_balance(dividend_data.dividend_distribution_account, + db.adjust_balance(dividend_data.dividend_distribution_account, asset(-total_fee_per_asset_in_payout_asset, payout_asset_type)); // convert it to core db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) { @@ -1166,7 +1567,7 @@ void schedule_pending_dividend_balances(database& db, delta_balance -= total_fee_per_asset_in_payout_asset; } - dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", + dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", ("count", holder_account_count) ("total", total_balance_of_dividend_asset)); share_type remaining_amount_to_distribute = delta_balance; @@ -1238,7 +1639,7 @@ void schedule_pending_dividend_balances(database& db, dlog("Pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name) ("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type))); - dlog("Remaining balance not paid out: ${amount}", + dlog("Remaining balance not paid out: ${amount}", ("amount", asset(remaining_amount_to_distribute, payout_asset_type))); share_type distributed_amount = delta_balance - remaining_amount_to_distribute; @@ -1268,7 +1669,7 @@ void schedule_pending_dividend_balances(database& db, // This should be extremely rare (caused by an override transfer by the asset owner). // Reduce all pending payouts proportionally share_type total_pending_balances; - auto pending_payouts_range = + auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) @@ -1305,10 +1706,10 @@ void schedule_pending_dividend_balances(database& db, } // iterate - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || current_distribution_account_balance_iter->second->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) ++current_distribution_account_balance_iter; - else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || + else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->second->asset_type) ++previous_distribution_account_balance_iter; else @@ -1350,7 +1751,7 @@ void process_dividend_assets(database& db) { try { - dlog("Dividend payout time has arrived for asset ${holder_asset}", + dlog("Dividend payout time has arrived for asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)); #ifndef NDEBUG // dump balances before the payouts for debugging @@ -1362,14 +1763,14 @@ void process_dividend_assets(database& db) // when we do the payouts, we first increase the balances in all of the receiving accounts // and use this map to keep track of the total amount of each asset paid out. - // Afterwards, we decrease the distribution account's balance by the total amount paid out, + // Afterwards, we decrease the distribution account's balance by the total amount paid out, // and modify the distributed_balances accordingly std::map amounts_paid_out_by_asset; - auto pending_payouts_range = + auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); // the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account - // we iterate in this order so we can build up a list of payouts for each account to put in the + // we iterate in this order so we can build up a list of payouts for each account to put in the // virtual op vector payouts_for_this_holder; fc::optional last_holder_account_id; @@ -1381,7 +1782,7 @@ void process_dividend_assets(database& db) auto approved_assets_iter = approved_assets.find(asset_id); if (approved_assets_iter != approved_assets.end()) return approved_assets_iter->second; - bool is_approved = is_authorized_asset(db, dividend_distribution_account_object, + bool is_approved = is_authorized_asset(db, dividend_distribution_account_object, asset_id(db)); approved_assets[asset_id] = is_approved; return is_approved; @@ -1394,8 +1795,8 @@ void process_dividend_assets(database& db) if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one - db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, - *last_holder_account_id, + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, payouts_for_this_holder)); dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); payouts_for_this_holder.clear(); @@ -1407,14 +1808,14 @@ void process_dividend_assets(database& db) is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) { - dlog("Processing payout of ${asset} to account ${account}", + dlog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); db.adjust_balance(pending_balance_object.owner, - asset(pending_balance_object.pending_balance, + asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); - payouts_for_this_holder.push_back(asset(pending_balance_object.pending_balance, + payouts_for_this_holder.push_back(asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); last_holder_account_id = pending_balance_object.owner; amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; @@ -1430,8 +1831,8 @@ void process_dividend_assets(database& db) if (last_holder_account_id && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one - db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, - *last_holder_account_id, + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, payouts_for_this_holder)); dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); } @@ -1444,11 +1845,11 @@ void process_dividend_assets(database& db) const asset_id_type& asset_paid_out = value.first; const share_type& amount_paid_out = value.second; - db.adjust_balance(dividend_data.dividend_distribution_account, + db.adjust_balance(dividend_data.dividend_distribution_account, asset(-amount_paid_out, asset_paid_out)); - auto distributed_balance_iter = - distributed_dividend_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, + auto distributed_balance_iter = + distributed_dividend_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, asset_paid_out)); assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()); if (distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()) @@ -1465,7 +1866,7 @@ void process_dividend_assets(database& db) fc::optional next_payout_time; if (dividend_data_obj.options.payout_interval) { - // if there was a previous payout, make our next payment one interval + // if there was a previous payout, make our next payment one interval uint32_t current_time_sec = current_head_block_time.sec_since_epoch(); fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time; uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch(); @@ -1480,12 +1881,84 @@ void process_dividend_assets(database& db) (dividend_data_obj.last_payout_time) (dividend_data_obj.options.next_payout_time)); }); - } + } FC_RETHROW_EXCEPTIONS(error, "Error while paying out dividends for holder asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)) } } } FC_CAPTURE_AND_RETHROW() } +void database::perform_son_tasks() +{ + const global_property_object& gpo = get_global_properties(); + if(gpo.parameters.son_account() == GRAPHENE_NULL_ACCOUNT && head_block_time() >= HARDFORK_SON_TIME) + { + const auto& son_account = create([&](account_object& a) { + a.name = "son-account"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 0; + a.registrar = a.lifetime_referrer = a.referrer = a.id; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }); + + modify( gpo, [&son_account]( global_property_object& gpo ) { + gpo.parameters.extensions.value.son_account = son_account.get_id(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.son_account = son_account.get_id(); + }); + } + // create BTC asset here because son_account is the issuer of the BTC + if (gpo.parameters.btc_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_TIME) + { + const asset_dynamic_data_object& dyn_asset = + create([](asset_dynamic_data_object& a) { + a.current_supply = 0; + }); + + const asset_object& btc_asset = + create( [&gpo, &dyn_asset]( asset_object& a ) { + a.symbol = "BTC"; + a.precision = 8; + a.issuer = gpo.parameters.son_account(); + a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + a.options.market_fee_percent = 500; // 5% + a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + a.options.flags = asset_issuer_permission_flags::charge_market_fee | + //asset_issuer_permission_flags::white_list | + asset_issuer_permission_flags::override_authority | + asset_issuer_permission_flags::transfer_restricted | + asset_issuer_permission_flags::disable_confidential; + a.options.core_exchange_rate.base.amount = 100000; + a.options.core_exchange_rate.base.asset_id = asset_id_type(0); + a.options.core_exchange_rate.quote.amount = 2500; // CoinMarketCap approx value + a.options.core_exchange_rate.quote.asset_id = a.id; + a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty + a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set + a.options.whitelist_markets.clear(); // might be traded with + a.options.blacklist_markets.clear(); // might not be traded with + a.dynamic_asset_data_id = dyn_asset.id; + }); + modify( gpo, [&btc_asset]( global_property_object& gpo ) { + gpo.parameters.extensions.value.btc_asset = btc_asset.get_id(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.btc_asset = btc_asset.get_id(); + }); + } + // Pay the SONs + if (head_block_time() >= HARDFORK_SON_TIME) + { + // Before making a budget we should pay out SONs + // This function should check if its time to pay sons + // and modify the global son funds accordingly, whatever is left is passed on to next budget + pay_sons(); + } +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { try { const auto& gpo = get_global_properties(); @@ -1508,6 +1981,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g d._vote_tally_buffer.resize(props.next_available_vote_id); d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1); d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1); + d._son_count_histogram_buffer.resize(props.parameters.maximum_son_count / 2 + 1); d._total_voting_stake = 0; auto balance_type = vesting_balance_type::normal; @@ -1619,6 +2093,18 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // same rationale as for witnesses d._committee_count_histogram_buffer[offset] += voting_stake; } + if( opinion_account.options.num_son <= props.parameters.maximum_son_count ) + { + uint16_t offset = std::min(size_t(opinion_account.options.num_son/2), + d._son_count_histogram_buffer.size() - 1); + // votes for a number greater than maximum_son_count + // are turned into votes for maximum_son_count. + // + // in particular, this takes care of the case where a + // member was voting for a high number, then the + // parameter was lowered. + d._son_count_histogram_buffer[offset] += voting_stake; + } d._total_voting_stake += voting_stake; } @@ -1634,11 +2120,14 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g }; clear_canary a(_witness_count_histogram_buffer), b(_committee_count_histogram_buffer), + d(_son_count_histogram_buffer), c(_vote_tally_buffer); + perform_son_tasks(); update_top_n_authorities(*this); update_active_witnesses(); update_active_committee_members(); + update_active_sons(); update_worker_votes(); const dynamic_global_property_object& dgpo = get_dynamic_global_properties(); @@ -1668,6 +2157,32 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g p.pending_parameters->extensions.value.gpos_subperiod = p.parameters.extensions.value.gpos_subperiod; if( !p.pending_parameters->extensions.value.gpos_vesting_lockin_period.valid() ) p.pending_parameters->extensions.value.gpos_vesting_lockin_period = p.parameters.extensions.value.gpos_vesting_lockin_period; + if( !p.pending_parameters->extensions.value.rbac_max_permissions_per_account.valid() ) + p.pending_parameters->extensions.value.rbac_max_permissions_per_account = p.parameters.extensions.value.rbac_max_permissions_per_account; + if( !p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime.valid() ) + p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime = p.parameters.extensions.value.rbac_max_account_authority_lifetime; + if( !p.pending_parameters->extensions.value.rbac_max_authorities_per_permission.valid() ) + p.pending_parameters->extensions.value.rbac_max_authorities_per_permission = p.parameters.extensions.value.rbac_max_authorities_per_permission; + if( !p.pending_parameters->extensions.value.account_roles_max_per_account.valid() ) + p.pending_parameters->extensions.value.account_roles_max_per_account = p.parameters.extensions.value.account_roles_max_per_account; + if( !p.pending_parameters->extensions.value.account_roles_max_lifetime.valid() ) + p.pending_parameters->extensions.value.account_roles_max_lifetime = p.parameters.extensions.value.account_roles_max_lifetime; + if( !p.pending_parameters->extensions.value.son_vesting_amount.valid() ) + p.pending_parameters->extensions.value.son_vesting_amount = p.parameters.extensions.value.son_vesting_amount; + if( !p.pending_parameters->extensions.value.son_vesting_period.valid() ) + p.pending_parameters->extensions.value.son_vesting_period = p.parameters.extensions.value.son_vesting_period; + if( !p.pending_parameters->extensions.value.son_pay_max.valid() ) + p.pending_parameters->extensions.value.son_pay_max = p.parameters.extensions.value.son_pay_max; + if( !p.pending_parameters->extensions.value.son_pay_time.valid() ) + p.pending_parameters->extensions.value.son_pay_time = p.parameters.extensions.value.son_pay_time; + if( !p.pending_parameters->extensions.value.son_deregister_time.valid() ) + p.pending_parameters->extensions.value.son_deregister_time = p.parameters.extensions.value.son_deregister_time; + if( !p.pending_parameters->extensions.value.son_heartbeat_frequency.valid() ) + p.pending_parameters->extensions.value.son_heartbeat_frequency = p.parameters.extensions.value.son_heartbeat_frequency; + if( !p.pending_parameters->extensions.value.son_down_time.valid() ) + p.pending_parameters->extensions.value.son_down_time = p.parameters.extensions.value.son_down_time; + if( !p.pending_parameters->extensions.value.son_bitcoin_min_tx_confirmations.valid() ) + p.pending_parameters->extensions.value.son_bitcoin_min_tx_confirmations = p.parameters.extensions.value.son_bitcoin_min_tx_confirmations; p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); } @@ -1717,8 +2232,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; }); // Ideally we have to do this after every block but that leads to longer block applicaiton/replay times. // So keep it here as it is not critical. valid_to check ensures - // these custom account auths are not usable. + // these custom account auths and account roles are not usable. clear_expired_custom_account_authorities(*this); + clear_expired_account_roles(*this); // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time process_budget(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index f56e3d8b..06ffdee5 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -40,6 +40,7 @@ #include #include #include +#include using namespace fc; @@ -49,8 +50,13 @@ using namespace graphene::chain; struct get_impacted_account_visitor { flat_set& _impacted; - get_impacted_account_visitor( flat_set& impact ):_impacted(impact) {} - typedef void result_type; + bool _ignore_custom_op_reqd_auths; + + get_impacted_account_visitor( flat_set& impact, bool ignore_custom_operation_required_auths ) + : _impacted( impact ), _ignore_custom_op_reqd_auths( ignore_custom_operation_required_auths ) + {} + + using result_type = void; void operator()( const transfer_operation& op ) { @@ -136,7 +142,7 @@ struct get_impacted_account_visitor { vector other; for( const auto& proposed_op : op.proposed_ops ) - operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other ); + operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other, _ignore_custom_op_reqd_auths ); for( auto& o : other ) add_authority_accounts( _impacted, o ); } @@ -344,22 +350,85 @@ struct get_impacted_account_visitor void operator()( const finalize_offer_operation& op ) { _impacted.insert( op.fee_paying_account ); } + void operator()( const account_role_create_operation& op ){ + _impacted.insert( op.owner ); + } + void operator()( const account_role_update_operation& op ){ + _impacted.insert( op.owner ); + } + void operator()( const account_role_delete_operation& op ){ + _impacted.insert( op.owner ); + } + void operator()( const son_create_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_update_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_deregister_operation& op ) { + _impacted.insert( op.payer); + } + void operator()( const son_heartbeat_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_report_down_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_maintenance_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_wallet_recreate_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_update_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_process_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_process_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_address_add_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_address_update_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_address_delete_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_sign_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_send_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_settle_operation& op ) { + _impacted.insert( op.payer ); + } }; -void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) -{ - get_impacted_account_visitor vtor = get_impacted_account_visitor( result ); +void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_operation_required_auths ) { + get_impacted_account_visitor vtor = get_impacted_account_visitor( result, ignore_custom_operation_required_auths ); op.visit( vtor ); } -void graphene::chain::transaction_get_impacted_accounts( const transaction& tx, flat_set& result ) -{ +void graphene::chain::transaction_get_impacted_accounts( const transaction& tx, flat_set& result, bool ignore_custom_operation_required_auths ) { for( const auto& op : tx.operations ) - operation_get_impacted_accounts( op, result ); + operation_get_impacted_accounts( op, result, ignore_custom_operation_required_auths ); } -void get_relevant_accounts( const object* obj, flat_set& accounts ) -{ +void get_relevant_accounts( const object* obj, flat_set& accounts, bool ignore_custom_operation_required_auths ) { if( obj->id.space() == protocol_ids ) { switch( (object_type)obj->id.type() ) @@ -406,12 +475,14 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case proposal_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); - transaction_get_impacted_accounts( aobj->proposed_transaction, accounts ); + transaction_get_impacted_accounts( aobj->proposed_transaction, accounts, + ignore_custom_operation_required_auths); break; } case operation_history_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); - operation_get_impacted_accounts( aobj->op, accounts ); + operation_get_impacted_accounts( aobj->op, accounts, + ignore_custom_operation_required_auths); break; } case withdraw_permission_object_type:{ const auto& aobj = dynamic_cast(obj); @@ -432,6 +503,30 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case balance_object_type:{ /** these are free from any accounts */ break; + } case account_role_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->owner ); + accounts.insert( aobj->whitelisted_accounts.begin(), aobj->whitelisted_accounts.end() ); + break; + } case son_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->son_account ); + break; + } case son_wallet_object_type:{ + break; + } case son_wallet_deposit_object_type:{ + break; + } case son_wallet_withdraw_object_type:{ + break; + } case sidechain_address_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->sidechain_address_account ); + break; + } case sidechain_transaction_object_type:{ + break; } } } @@ -462,7 +557,8 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case impl_transaction_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); - transaction_get_impacted_accounts( aobj->trx, accounts ); + transaction_get_impacted_accounts( aobj->trx, accounts, + ignore_custom_operation_required_auths); break; } case impl_blinded_balance_object_type:{ const auto& aobj = dynamic_cast(obj); @@ -507,6 +603,7 @@ void database::notify_changed_objects() if( _undo_db.enabled() ) { const auto& head_undo = _undo_db.head(); + auto chain_time = head_block_time(); // New if( !new_objects.empty() ) @@ -518,7 +615,8 @@ void database::notify_changed_objects() new_ids.push_back(item); auto obj = find_object(item); if(obj != nullptr) - get_relevant_accounts(obj, new_accounts_impacted); + get_relevant_accounts(obj, new_accounts_impacted, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time)); } GRAPHENE_TRY_NOTIFY( new_objects, new_ids, new_accounts_impacted) @@ -532,7 +630,8 @@ void database::notify_changed_objects() for( const auto& item : head_undo.old_values ) { changed_ids.push_back(item.first); - get_relevant_accounts(item.second.get(), changed_accounts_impacted); + get_relevant_accounts(item.second.get(), changed_accounts_impacted, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time)); } GRAPHENE_TRY_NOTIFY( changed_objects, changed_ids, changed_accounts_impacted) @@ -549,7 +648,8 @@ void database::notify_changed_objects() removed_ids.emplace_back( item.first ); auto obj = item.second.get(); removed.emplace_back( obj ); - get_relevant_accounts(obj, removed_accounts_impacted); + get_relevant_accounts(obj, removed_accounts_impacted, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time)); } GRAPHENE_TRY_NOTIFY( removed_objects, removed_ids, removed, removed_accounts_impacted) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 96f9e3ab..2e896070 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -225,6 +225,7 @@ void database::clear_expired_proposals() elog("Failed to apply proposed transaction on its expiration. Deleting it.\n${proposal}\n${error}", ("proposal", proposal)("error", e.to_detail_string())); } + remove_son_proposal(proposal); remove(proposal); } } @@ -730,5 +731,50 @@ void database::finalize_expired_offers(){ } }); } FC_CAPTURE_AND_RETHROW()} +void database::remove_son_proposal( const proposal_object& proposal ) +{ try { + if( proposal.proposed_transaction.operations.size() == 1 && + ( proposal.proposed_transaction.operations.back().which() == operation::tag::value || + proposal.proposed_transaction.operations.back().which() == operation::tag::value) ) + { + const auto& son_proposal_idx = get_index_type().indices().get(); + auto son_proposal_itr = son_proposal_idx.find( proposal.id ); + if( son_proposal_itr == son_proposal_idx.end() ) { + return; + } + remove( *son_proposal_itr ); + } +} FC_CAPTURE_AND_RETHROW( (proposal) ) } + +void database::remove_inactive_son_down_proposals( const vector& son_ids_to_remove ) +{ + const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); + std::vector proposals_to_remove; + + for( auto& son_proposal : son_proposal_idx ) + { + if(son_proposal.proposal_type == son_proposal_type::son_report_down_proposal) + { + auto it = std::find(son_ids_to_remove.begin(), son_ids_to_remove.end(), son_proposal.son_id); + if (it != son_ids_to_remove.end()) + { + ilog( "Removing inactive proposal ${p} for son ${s}", ("p", son_proposal.proposal_id) ("s",son_proposal.son_id)); + proposals_to_remove.push_back(son_proposal.proposal_id); + } + } + } + + for( auto& proposal_id : proposals_to_remove ) + { + const auto& proposal_obj = proposal_id(*this); + remove_son_proposal(proposal_obj); + remove(proposal_obj); + } +} + +void database::remove_inactive_son_proposals( const vector& son_ids_to_remove ) +{ + remove_inactive_son_down_proposals( son_ids_to_remove ); +} } } diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index 762157bc..084c8e1d 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include namespace graphene { namespace chain { @@ -72,6 +74,47 @@ witness_id_type database::get_scheduled_witness( uint32_t slot_num )const return wid; } +son_id_type database::get_scheduled_son( uint32_t slot_num )const +{ + son_id_type sid; + const global_property_object& gpo = get_global_properties(); + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + const son_schedule_object& sso = son_schedule_id_type()(*this); + uint64_t current_aslot = dpo.current_aslot + slot_num; + return sso.current_shuffled_sons[ current_aslot % sso.current_shuffled_sons.size() ]; + } + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM && + slot_num != 0 ) + { + const son_schedule_object& sso = son_schedule_id_type()(*this); + // ask the near scheduler who goes in the given slot + bool slot_is_near = sso.scheduler.get_slot(slot_num-1, sid); + if(! slot_is_near) + { + // if the near scheduler doesn't know, we have to extend it to + // a far scheduler. + // n.b. instantiating it is slow, but block gaps long enough to + // need it are likely pretty rare. + + witness_scheduler_rng far_rng(sso.rng_seed.begin(), GRAPHENE_FAR_SCHEDULE_CTR_IV); + + far_future_son_scheduler far_scheduler = + far_future_son_scheduler(sso.scheduler, far_rng); + if(!far_scheduler.get_slot(slot_num-1, sid)) + { + // no scheduled son -- somebody set up us the bomb + // n.b. this code path is impossible, the present + // implementation of far_future_son_scheduler + // returns true unconditionally + assert( false ); + } + } + } + return sid; +} + fc::time_point_sec database::get_slot_time(uint32_t slot_num)const { if( slot_num == 0 ) @@ -146,6 +189,41 @@ void database::update_witness_schedule() } } +void database::update_son_schedule() +{ + const son_schedule_object& sso = son_schedule_id_type()(*this); + const global_property_object& gpo = get_global_properties(); + + if( head_block_num() % gpo.active_sons.size() == 0 ) + { + modify( sso, [&]( son_schedule_object& _sso ) + { + _sso.current_shuffled_sons.clear(); + _sso.current_shuffled_sons.reserve( gpo.active_sons.size() ); + + for( const son_info& w : gpo.active_sons ) + _sso.current_shuffled_sons.push_back( w.son_id ); + + auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32; + for( uint32_t i = 0; i < _sso.current_shuffled_sons.size(); ++i ) + { + /// High performance random generator + /// http://xorshift.di.unimi.it/ + uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL; + k ^= (k >> 12); + k ^= (k << 25); + k ^= (k >> 27); + k *= 2685821657736338717ULL; + + uint32_t jmax = _sso.current_shuffled_sons.size() - i; + uint32_t j = i + k%jmax; + std::swap( _sso.current_shuffled_sons[i], + _sso.current_shuffled_sons[j] ); + } + }); + } +} + vector database::get_near_witness_schedule()const { const witness_schedule_object& wso = get_witness_schedule_object(); @@ -226,6 +304,71 @@ void database::update_witness_schedule(const signed_block& next_block) idump( ( double(total_time/1000000.0)/calls) ); } +void database::update_son_schedule(const signed_block& next_block) +{ + auto start = fc::time_point::now(); + const global_property_object& gpo = get_global_properties(); + const son_schedule_object& sso = get(son_schedule_id_type()); + uint32_t schedule_needs_filled = gpo.active_sons.size(); + uint32_t schedule_slot = get_slot_at_time(next_block.timestamp); + + // We shouldn't be able to generate _pending_block with timestamp + // in the past, and incoming blocks from the network with timestamp + // in the past shouldn't be able to make it this far without + // triggering FC_ASSERT elsewhere + + assert( schedule_slot > 0 ); + + son_id_type first_son; + bool slot_is_near = sso.scheduler.get_slot( schedule_slot-1, first_son ); + + son_id_type son; + + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + + assert( dpo.random.data_size() == witness_scheduler_rng::seed_length ); + assert( witness_scheduler_rng::seed_length == sso.rng_seed.size() ); + + modify(sso, [&](son_schedule_object& _sso) + { + _sso.slots_since_genesis += schedule_slot; + witness_scheduler_rng rng(sso.rng_seed.data, _sso.slots_since_genesis); + + _sso.scheduler._min_token_count = std::max(int(gpo.active_sons.size()) / 2, 1); + + if( slot_is_near ) + { + uint32_t drain = schedule_slot; + while( drain > 0 ) + { + if( _sso.scheduler.size() == 0 ) + break; + _sso.scheduler.consume_schedule(); + --drain; + } + } + else + { + _sso.scheduler.reset_schedule( first_son ); + } + while( !_sso.scheduler.get_slot(schedule_needs_filled, son) ) + { + if( _sso.scheduler.produce_schedule(rng) & emit_turn ) + memcpy(_sso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); + } + _sso.last_scheduling_block = next_block.block_num(); + _sso.recent_slots_filled = ( + (_sso.recent_slots_filled << 1) + + 1) << (schedule_slot - 1); + }); + auto end = fc::time_point::now(); + static uint64_t total_time = 0; + static uint64_t calls = 0; + total_time += (end - start).count(); + if( ++calls % 1000 == 0 ) + idump( ( double(total_time/1000000.0)/calls) ); +} + uint32_t database::update_witness_missed_blocks( const signed_block& b ) { uint32_t missed_blocks = get_slot_at_time( b.timestamp ); diff --git a/libraries/chain/get_config.cpp b/libraries/chain/get_config.cpp index c961b950..245d6598 100644 --- a/libraries/chain/get_config.cpp +++ b/libraries/chain/get_config.cpp @@ -108,6 +108,11 @@ fc::variant_object get_config() result[ "GRAPHENE_RELAXED_COMMITTEE_ACCOUNT" ] = fc::variant(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); result[ "GRAPHENE_NULL_ACCOUNT" ] = fc::variant(GRAPHENE_NULL_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); result[ "GRAPHENE_TEMP_ACCOUNT" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_PROXY_TO_SELF_ACCOUNT" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_RAKE_FEE_ACCOUNT_ID" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_NULL_WITNESS" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); return result; } diff --git a/libraries/chain/hardfork.d/5050-1.hf b/libraries/chain/hardfork.d/5050-1.hf index eb9aa74d..bd621f26 100644 --- a/libraries/chain/hardfork.d/5050-1.hf +++ b/libraries/chain/hardfork.d/5050-1.hf @@ -1,4 +1,4 @@ -// 5050_1 HARDFORK Thursday, 15 April 2020 20:00:00 GMT +// 5050_1 HARDFORK Wednesday, 15 April 2020 20:00:00 GMT #ifndef HARDFORK_5050_1_TIME #define HARDFORK_5050_1_TIME (fc::time_point_sec( 1586980800 )) #endif diff --git a/libraries/chain/hardfork.d/CORE_210.hf b/libraries/chain/hardfork.d/CORE_210.hf new file mode 100644 index 00000000..0f285274 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_210.hf @@ -0,0 +1,6 @@ +// #210 Check authorities on custom_operation +#ifndef HARDFORK_CORE_210_TIME +#define HARDFORK_CORE_210_TIME (fc::time_point_sec(1893456000)) // Jan 1 00:00:00 2030 (Not yet scheduled) +// Bugfix: pre-HF 210, custom_operation's required_auths field was ignored. +#define MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time) (chain_time <= HARDFORK_CORE_210_TIME) +#endif diff --git a/libraries/chain/hardfork.d/SON.hf b/libraries/chain/hardfork.d/SON.hf new file mode 100644 index 00000000..b3947a75 --- /dev/null +++ b/libraries/chain/hardfork.d/SON.hf @@ -0,0 +1,7 @@ +// SON HARDFORK Wednesday, January 1, 2020 12:00:00 AM - 1577836800 +// SON HARDFORK Monday, March 30, 2020 3:00:00 PM - 1585573200 +// SON HARDFORK Monday, September 21, 2020 1:43:11 PM - 1600695791 +#ifndef HARDFORK_SON_TIME +#include +#define HARDFORK_SON_TIME (fc::time_point_sec( 1585573200 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_role_evaluator.hpp b/libraries/chain/include/graphene/chain/account_role_evaluator.hpp new file mode 100644 index 00000000..29c4ada6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/account_role_evaluator.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + + class account_role_create_evaluator : public evaluator + { + public: + typedef account_role_create_operation operation_type; + void_result do_evaluate( const account_role_create_operation& o ); + object_id_type do_apply( const account_role_create_operation& o ); + }; + + class account_role_update_evaluator : public evaluator + { + public: + typedef account_role_update_operation operation_type; + void_result do_evaluate( const account_role_update_operation& o ); + void_result do_apply( const account_role_update_operation& o ); + }; + + class account_role_delete_evaluator : public evaluator + { + public: + typedef account_role_delete_operation operation_type; + void_result do_evaluate( const account_role_delete_operation& o ); + void_result do_apply( const account_role_delete_operation& o ); + }; + +} } // graphene::chain + diff --git a/libraries/chain/include/graphene/chain/account_role_object.hpp b/libraries/chain/include/graphene/chain/account_role_object.hpp new file mode 100644 index 00000000..9455f475 --- /dev/null +++ b/libraries/chain/include/graphene/chain/account_role_object.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include + +namespace graphene +{ + namespace chain + { + using namespace graphene::db; + + class account_role_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = account_role_type; + + account_id_type owner; + std::string name; + std::string metadata; + flat_set allowed_operations; + flat_set whitelisted_accounts; + time_point_sec valid_to; + }; + + struct by_owner; + struct by_expiration; + using account_role_multi_index_type = multi_index_container< + account_role_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member> + > + > + >; + using account_role_index = generic_index; + } // namespace chain +} // namespace graphene + +FC_REFLECT_DERIVED(graphene::chain::account_role_object, (graphene::db::object), + (owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)(valid_to)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/budget_record_object.hpp b/libraries/chain/include/graphene/chain/budget_record_object.hpp index e3f6d06e..007d46a7 100644 --- a/libraries/chain/include/graphene/chain/budget_record_object.hpp +++ b/libraries/chain/include/graphene/chain/budget_record_object.hpp @@ -45,9 +45,11 @@ struct budget_record // sinks of budget, should sum up to total_budget share_type witness_budget = 0; share_type worker_budget = 0; + share_type son_budget = 0; // unused budget share_type leftover_worker_funds = 0; + share_type leftover_son_funds = 0; // change in supply due to budget operations share_type supply_delta = 0; diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 42c2fd13..89d3cce3 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -90,8 +90,10 @@ #define GRAPHENE_DEFAULT_MIN_WITNESS_COUNT (11) #define GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT (11) +#define GRAPHENE_DEFAULT_MIN_SON_COUNT (5) #define GRAPHENE_DEFAULT_MAX_WITNESSES (1001) // SHOULD BE ODD #define GRAPHENE_DEFAULT_MAX_COMMITTEE (1001) // SHOULD BE ODD +#define GRAPHENE_DEFAULT_MAX_SONS (15) #define GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC (60*60*24*7*4) // Four weeks #define GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks #define GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) @@ -227,6 +229,14 @@ #define TOURNAMENT_MAX_WHITELIST_LENGTH 1000 #define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month #define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week +#define SON_VESTING_AMOUNT (50*GRAPHENE_BLOCKCHAIN_PRECISION) // 50 PPY +#define SON_VESTING_PERIOD (60*60*24*2) // 2 days +#define SON_DEREGISTER_TIME (60*60*12) // 12 Hours in seconds +#define SON_HEARTBEAT_FREQUENCY (60*3) // 3 minutes in seconds +#define SON_DOWN_TIME (60*3*2) // 2 Heartbeats in seconds +#define SON_BITCOIN_MIN_TX_CONFIRMATIONS (1) +#define SON_PAY_TIME (60*60*24) // 1 day +#define SON_PAY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200)) #define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT) #define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0)) #define SWEEPS_VESTING_BALANCE_MULTIPLIER 100000000 @@ -243,4 +253,7 @@ #define NFT_TOKEN_MIN_LENGTH 3 #define NFT_TOKEN_MAX_LENGTH 15 -#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH \ No newline at end of file +#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH + +#define ACCOUNT_ROLES_MAX_PER_ACCOUNT 20 // Max 20 roles can be created by a resource owner +#define ACCOUNT_ROLES_MAX_LIFETIME 365*24*60*60 // 1 Year diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 8ecb4b91..05927399 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -40,6 +40,8 @@ #include +#include + #include #include @@ -240,6 +242,22 @@ namespace graphene { namespace chain { */ witness_id_type get_scheduled_witness(uint32_t slot_num)const; + /** + * @brief Get the son scheduled for block production in a slot. + * + * slot_num always corresponds to a time in the future. + * + * If slot_num == 1, returns the next scheduled son. + * If slot_num == 2, returns the next scheduled son after + * 1 block gap. + * + * Use the get_slot_time() and get_slot_at_time() functions + * to convert between slot_num and timestamp. + * + * Passing slot_num == 0 returns GRAPHENE_NULL_WITNESS + */ + son_id_type get_scheduled_son(uint32_t slot_num)const; + /** * Get the time at which the given slot occurs. * @@ -263,6 +281,8 @@ namespace graphene { namespace chain { vector get_near_witness_schedule()const; void update_witness_schedule(); void update_witness_schedule(const signed_block& next_block); + void update_son_schedule(); + void update_son_schedule(const signed_block& next_block); void check_lottery_end_by_participants( asset_id_type asset_id ); void check_ending_lotteries(); @@ -283,6 +303,14 @@ namespace graphene { namespace chain { uint64_t get_random_bits( uint64_t bound ); const witness_schedule_object& get_witness_schedule_object()const; bool item_locked(const nft_id_type& item)const; + bool account_role_valid(const account_role_object& aro, account_id_type account, optional op_type = optional()) const; + std::set get_sons_being_deregistered(); + std::set get_sons_being_reported_down(); + std::set get_sons_to_be_deregistered(); + fc::optional create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ); + signed_transaction create_signed_transaction( const fc::ecc::private_key& signing_private_key, const operation& op ); + bool is_son_dereg_valid( son_id_type son_id ); + bool is_son_active( son_id_type son_id ); time_point_sec head_block_time()const; uint32_t head_block_num()const; @@ -531,9 +559,18 @@ namespace graphene { namespace chain { void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const; void process_budget(); void pay_workers( share_type& budget ); + void pay_sons(); + void perform_son_tasks(); void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); void update_active_witnesses(); void update_active_committee_members(); + void update_son_metrics( const vector& curr_active_sons ); + void update_active_sons(); + void remove_son_proposal( const proposal_object& proposal ); + void remove_inactive_son_down_proposals( const vector& son_ids_to_remove ); + void remove_inactive_son_proposals( const vector& son_ids_to_remove ); + void update_son_statuses( const vector& cur_active_sons, const vector& new_active_sons ); + void update_son_wallet( const vector& new_active_sons ); void update_worker_votes(); public: @@ -575,6 +612,7 @@ namespace graphene { namespace chain { vector _vote_tally_buffer; vector _witness_count_histogram_buffer; vector _committee_count_histogram_buffer; + vector _son_count_histogram_buffer; uint64_t _total_voting_stake; flat_map _checkpoints; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 0ed401a1..0f95a6e3 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace graphene { namespace chain { @@ -51,6 +52,7 @@ namespace graphene { namespace chain { uint32_t next_available_vote_id = 0; vector active_committee_members; // updated once per maintenance interval flat_set active_witnesses; // updated once per maintenance interval + vector active_sons; // updated once per maintenance interval // n.b. witness scheduling is done by witness_schedule object }; @@ -77,6 +79,9 @@ namespace graphene { namespace chain { time_point_sec next_maintenance_time; time_point_sec last_budget_time; share_type witness_budget; + //Last SON Payout time, it can be different to the maintenance interval time + time_point_sec last_son_payout_time = HARDFORK_SON_TIME; + share_type son_budget = 0; uint32_t accounts_registered_this_interval = 0; /** * Every time a block is missed this increases by @@ -133,6 +138,8 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene:: (next_maintenance_time) (last_budget_time) (witness_budget) + (last_son_payout_time) + (son_budget) (accounts_registered_this_interval) (recently_missed_count) (current_aslot) @@ -147,6 +154,7 @@ FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::obje (next_available_vote_id) (active_committee_members) (active_witnesses) + (active_sons) ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::dynamic_global_property_object ) diff --git a/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp b/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp index 6e18c079..f7128889 100644 --- a/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp @@ -32,6 +32,7 @@ struct immutable_chain_parameters { uint16_t min_committee_member_count = GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT; uint16_t min_witness_count = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT; + uint16_t min_son_count = GRAPHENE_DEFAULT_MIN_SON_COUNT; uint32_t num_special_accounts = 0; uint32_t num_special_assets = 0; }; @@ -41,6 +42,7 @@ struct immutable_chain_parameters FC_REFLECT( graphene::chain::immutable_chain_parameters, (min_committee_member_count) (min_witness_count) + (min_son_count) (num_special_accounts) (num_special_assets) ) diff --git a/libraries/chain/include/graphene/chain/impacted.hpp b/libraries/chain/include/graphene/chain/impacted.hpp index 2a22cbd1..3e070ce6 100644 --- a/libraries/chain/include/graphene/chain/impacted.hpp +++ b/libraries/chain/include/graphene/chain/impacted.hpp @@ -30,13 +30,12 @@ namespace graphene { namespace chain { -void operation_get_impacted_accounts( - const graphene::chain::operation& op, - fc::flat_set& result ); +void operation_get_impacted_accounts( const graphene::chain::operation& op, + fc::flat_set& result, + bool ignore_custom_operation_required_auths ); -void transaction_get_impacted_accounts( - const graphene::chain::transaction& tx, - fc::flat_set& result - ); +void transaction_get_impacted_accounts( const graphene::chain::transaction& tx, + fc::flat_set& result, + bool ignore_custom_operation_required_auths ); -} } // graphene::app \ No newline at end of file +} } // graphene::app diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 1994a92e..6a150852 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -20,6 +20,7 @@ namespace graphene { namespace chain { optional revenue_split; bool is_transferable = false; bool is_sellable = true; + optional account_role; }; class nft_object : public abstract_object @@ -95,7 +96,8 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object) (revenue_partner) (revenue_split) (is_transferable) - (is_sellable) ) + (is_sellable) + (account_role) ) FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), (nft_metadata_id) diff --git a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp index bf6fc547..3e8ee15e 100644 --- a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp @@ -30,6 +30,22 @@ namespace graphene { namespace chain { + class son_hardfork_visitor + { + public: + typedef void result_type; + database& db; + proposal_id_type prop_id; + + son_hardfork_visitor( database& _db, const proposal_id_type& _prop_id ) : db( _db ), prop_id( _prop_id ) {} + + template + void operator()( const T &v ) const {} + + void operator()( const son_deregister_operation &v ); + void operator()( const son_report_down_operation &v ); + }; + class proposal_create_evaluator : public evaluator { public: diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 36926480..50ccb8ae 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -52,6 +52,9 @@ namespace graphene { namespace chain { /// The number of active committee members this account votes the blockchain should appoint /// Must not exceed the actual number of committee members voted for in @ref votes uint16_t num_committee = 0; + /// The number of active son members this account votes the blockchain should appoint + /// Must not exceed the actual number of son members voted for in @ref votes + uint16_t num_son = 0; /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this /// account's balance of core asset. flat_set votes; diff --git a/libraries/chain/include/graphene/chain/protocol/account_role.hpp b/libraries/chain/include/graphene/chain/protocol/account_role.hpp new file mode 100644 index 00000000..6a532cb7 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/account_role.hpp @@ -0,0 +1,82 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + + struct account_role_create_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + asset fee; + + account_id_type owner; + std::string name; + std::string metadata; + flat_set allowed_operations; + flat_set whitelisted_accounts; + time_point_sec valid_to; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct account_role_update_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + asset fee; + + account_id_type owner; + account_role_id_type account_role_id; + optional name; + optional metadata; + flat_set allowed_operations_to_add; + flat_set allowed_operations_to_remove; + flat_set accounts_to_add; + flat_set accounts_to_remove; + optional valid_to; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct account_role_delete_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type owner; + account_role_id_type account_role_id; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } + }; + } // namespace chain +} // namespace graphene + +FC_REFLECT(graphene::chain::account_role_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) +FC_REFLECT(graphene::chain::account_role_update_operation::fee_parameters_type, (fee)(price_per_kbyte)) +FC_REFLECT(graphene::chain::account_role_delete_operation::fee_parameters_type, (fee)) + +FC_REFLECT(graphene::chain::account_role_create_operation, (fee)(owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)(valid_to)(extensions)) +FC_REFLECT(graphene::chain::account_role_update_operation, (fee)(owner)(account_role_id)(name)(metadata)(allowed_operations_to_add)(allowed_operations_to_remove)(accounts_to_add)(accounts_to_remove)(valid_to)(extensions)) +FC_REFLECT(graphene::chain::account_role_delete_operation, (fee)(owner)(account_role_id)(owner)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index ddac3115..88d5dccf 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -27,6 +27,7 @@ #include #include +#include #include <../hardfork.d/GPOS.hf> #include @@ -52,6 +53,21 @@ namespace graphene { namespace chain { optional < uint16_t > rbac_max_permissions_per_account = RBAC_MAX_PERMISSIONS_PER_ACCOUNT; optional < uint32_t > rbac_max_account_authority_lifetime = RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME; optional < uint16_t > rbac_max_authorities_per_permission = RBAC_MAX_AUTHS_PER_PERMISSION; + /* Account Roles - Permissions Parameters */ + optional < uint16_t > account_roles_max_per_account = ACCOUNT_ROLES_MAX_PER_ACCOUNT; + optional < uint32_t > account_roles_max_lifetime = ACCOUNT_ROLES_MAX_LIFETIME; + + optional < uint32_t > son_vesting_amount = SON_VESTING_AMOUNT; + optional < uint32_t > son_vesting_period = SON_VESTING_PERIOD; + optional < uint32_t > son_pay_max = SON_PAY_MAX; + optional < uint32_t > son_pay_time = SON_PAY_TIME; + optional < uint32_t > son_deregister_time = SON_DEREGISTER_TIME; + optional < uint32_t > son_heartbeat_frequency = SON_HEARTBEAT_FREQUENCY; + optional < uint32_t > son_down_time = SON_DOWN_TIME; + optional < uint16_t > son_bitcoin_min_tx_confirmations = SON_BITCOIN_MIN_TX_CONFIRMATIONS; + + optional < account_id_type > son_account; + optional < asset_id_type > btc_asset; }; struct chain_parameters @@ -70,6 +86,7 @@ namespace graphene { namespace chain { uint8_t maximum_asset_feed_publishers = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS; ///< the maximum number of feed publishers for a given asset uint16_t maximum_witness_count = GRAPHENE_DEFAULT_MAX_WITNESSES; ///< maximum number of active witnesses uint16_t maximum_committee_count = GRAPHENE_DEFAULT_MAX_COMMITTEE; ///< maximum number of active committee_members + uint16_t maximum_son_count = GRAPHENE_DEFAULT_MAX_SONS; ///< maximum number of active SONS uint16_t maximum_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have uint16_t reserve_percent_of_fee = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of the network's allocation of a fee that is taken out of circulation uint16_t network_percent_of_fee = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; ///< percent of transaction fees paid to network @@ -101,7 +118,7 @@ namespace graphene { namespace chain { uint32_t maximum_tournament_start_time_in_future = TOURNAMENT_MAX_START_TIME_IN_FUTURE; uint32_t maximum_tournament_start_delay = TOURNAMENT_MAX_START_DELAY; uint16_t maximum_tournament_number_of_wins = TOURNAMENT_MAX_NUMBER_OF_WINS; - + extension extensions; /** defined in fee_schedule.cpp */ @@ -152,6 +169,42 @@ namespace graphene { namespace chain { inline uint16_t rbac_max_authorities_per_permission()const { return extensions.value.rbac_max_authorities_per_permission.valid() ? *extensions.value.rbac_max_authorities_per_permission : RBAC_MAX_AUTHS_PER_PERMISSION; } + inline uint16_t account_roles_max_per_account()const { + return extensions.value.account_roles_max_per_account.valid() ? *extensions.value.account_roles_max_per_account : ACCOUNT_ROLES_MAX_PER_ACCOUNT; + } + inline uint32_t account_roles_max_lifetime()const { + return extensions.value.account_roles_max_lifetime.valid() ? *extensions.value.account_roles_max_lifetime : ACCOUNT_ROLES_MAX_LIFETIME; + } + inline uint32_t son_vesting_amount()const { + return extensions.value.son_vesting_amount.valid() ? *extensions.value.son_vesting_amount : SON_VESTING_AMOUNT; /// current period start date + } + inline uint32_t son_vesting_period()const { + return extensions.value.son_vesting_period.valid() ? *extensions.value.son_vesting_period : SON_VESTING_PERIOD; /// current period start date + } + inline uint16_t son_pay_max()const { + return extensions.value.son_pay_max.valid() ? *extensions.value.son_pay_max : SON_PAY_MAX; + } + inline uint16_t son_pay_time()const { + return extensions.value.son_pay_time.valid() ? *extensions.value.son_pay_time : SON_PAY_TIME; + } + inline uint16_t son_deregister_time()const { + return extensions.value.son_deregister_time.valid() ? *extensions.value.son_deregister_time : SON_DEREGISTER_TIME; + } + inline uint16_t son_heartbeat_frequency()const { + return extensions.value.son_heartbeat_frequency.valid() ? *extensions.value.son_heartbeat_frequency : SON_HEARTBEAT_FREQUENCY; + } + inline uint16_t son_down_time()const { + return extensions.value.son_down_time.valid() ? *extensions.value.son_down_time : SON_DOWN_TIME; + } + inline uint16_t son_bitcoin_min_tx_confirmations()const { + return extensions.value.son_bitcoin_min_tx_confirmations.valid() ? *extensions.value.son_bitcoin_min_tx_confirmations : SON_BITCOIN_MIN_TX_CONFIRMATIONS; + } + inline account_id_type son_account() const { + return extensions.value.son_account.valid() ? *extensions.value.son_account : GRAPHENE_NULL_ACCOUNT; + } + inline asset_id_type btc_asset() const { + return extensions.value.btc_asset.valid() ? *extensions.value.btc_asset : asset_id_type(); + } }; } } // graphene::chain @@ -172,6 +225,18 @@ FC_REFLECT( graphene::chain::parameter_extension, (rbac_max_permissions_per_account) (rbac_max_account_authority_lifetime) (rbac_max_authorities_per_permission) + (account_roles_max_per_account) + (account_roles_max_lifetime) + (son_vesting_amount) + (son_vesting_period) + (son_pay_max) + (son_pay_time) + (son_deregister_time) + (son_heartbeat_frequency) + (son_down_time) + (son_bitcoin_min_tx_confirmations) + (son_account) + (btc_asset) ) FC_REFLECT( graphene::chain::chain_parameters, @@ -188,6 +253,7 @@ FC_REFLECT( graphene::chain::chain_parameters, (maximum_asset_feed_publishers) (maximum_witness_count) (maximum_committee_count) + (maximum_son_count) (maximum_authority_membership) (reserve_percent_of_fee) (network_percent_of_fee) diff --git a/libraries/chain/include/graphene/chain/protocol/custom.hpp b/libraries/chain/include/graphene/chain/protocol/custom.hpp index 5596aaad..6c3af926 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom.hpp @@ -50,6 +50,9 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return payer; } void validate()const; share_type calculate_fee(const fee_parameters_type& k)const; + void get_required_active_authorities( flat_set& auths )const { + auths.insert( required_auths.begin(), required_auths.end() ); + } }; } } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp index f5f8c1cd..b3fe2903 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp @@ -20,6 +20,7 @@ struct custom_account_authority_create_operation : public base_operation time_point_sec valid_from; time_point_sec valid_to; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -38,6 +39,7 @@ struct custom_account_authority_update_operation : public base_operation optional new_valid_from; optional new_valid_to; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -54,6 +56,7 @@ struct custom_account_authority_delete_operation : public base_operation asset fee; custom_account_authority_id_type auth_id; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -64,10 +67,10 @@ struct custom_account_authority_delete_operation : public base_operation } // namespace graphene FC_REFLECT(graphene::chain::custom_account_authority_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) -FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account)) +FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_account_authority_update_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account)) +FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_account_authority_delete_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account)) \ No newline at end of file +FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp index 8093ef07..d61384c2 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp @@ -18,6 +18,7 @@ struct custom_permission_create_operation : public base_operation account_id_type owner_account; string permission_name; authority auth; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -35,6 +36,7 @@ struct custom_permission_update_operation : public base_operation custom_permission_id_type permission_id; optional new_auth; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -51,6 +53,7 @@ struct custom_permission_delete_operation : public base_operation asset fee; custom_permission_id_type permission_id; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -61,10 +64,10 @@ struct custom_permission_delete_operation : public base_operation } // namespace graphene FC_REFLECT(graphene::chain::custom_permission_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) -FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth)) +FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth)(extensions)) FC_REFLECT(graphene::chain::custom_permission_update_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account)) +FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_permission_delete_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account)) \ No newline at end of file +FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index 41e77b06..4facf51a 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -21,6 +21,9 @@ namespace graphene { namespace chain { optional revenue_split; bool is_transferable = false; bool is_sellable = true; + // Accounts Role + optional account_role; + extensions_type extensions; account_id_type fee_payer()const { return owner; } void validate() const; @@ -29,7 +32,11 @@ namespace graphene { namespace chain { struct nft_metadata_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; asset fee; account_id_type owner; @@ -41,6 +48,9 @@ namespace graphene { namespace chain { optional revenue_split; optional is_transferable; optional is_sellable; + // Accounts Role + optional account_role; + extensions_type extensions; account_id_type fee_payer()const { return owner; } void validate() const; @@ -63,6 +73,7 @@ namespace graphene { namespace chain { account_id_type approved; vector approved_operators; std::string token_uri; + extensions_type extensions; account_id_type fee_payer()const { return payer; } void validate() const; @@ -84,6 +95,7 @@ namespace graphene { namespace chain { account_id_type to; nft_id_type token_id; string data; + extensions_type extensions; account_id_type fee_payer()const { return operator_; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -98,6 +110,7 @@ namespace graphene { namespace chain { account_id_type approved; nft_id_type token_id; + extensions_type extensions; account_id_type fee_payer()const { return operator_; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -112,6 +125,7 @@ namespace graphene { namespace chain { account_id_type operator_; bool approved; + extensions_type extensions; account_id_type fee_payer()const { return owner; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -126,10 +140,9 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) ) -FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) ) -FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) ) -FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) ) -FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) ) -FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) ) - +FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) ) +FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) (extensions) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) (extensions) ) +FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) (extensions) ) +FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) (extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 1285d353..1a1222a5 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -49,6 +49,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace graphene { namespace chain { @@ -155,7 +162,29 @@ namespace graphene { namespace chain { nft_mint_operation, nft_safe_transfer_from_operation, nft_approve_operation, - nft_set_approval_for_all_operation + nft_set_approval_for_all_operation, + account_role_create_operation, + account_role_update_operation, + account_role_delete_operation, + son_create_operation, + son_update_operation, + son_deregister_operation, + son_heartbeat_operation, + son_report_down_operation, + son_maintenance_operation, + son_wallet_recreate_operation, + son_wallet_update_operation, + son_wallet_deposit_create_operation, + son_wallet_deposit_process_operation, + son_wallet_withdraw_create_operation, + son_wallet_withdraw_process_operation, + sidechain_address_add_operation, + sidechain_address_update_operation, + sidechain_address_delete_operation, + sidechain_transaction_create_operation, + sidechain_transaction_sign_operation, + sidechain_transaction_send_operation, + sidechain_transaction_settle_operation > operation; /// @} // operations group @@ -166,10 +195,11 @@ namespace graphene { namespace chain { * * @return a set of required authorities for @ref op */ - void operation_get_required_authorities( const operation& op, + void operation_get_required_authorities( const operation& op, flat_set& active, flat_set& owner, - vector& other ); + vector& other, + bool ignore_custom_operation_required_auths ); void operation_validate( const operation& op ); diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp new file mode 100644 index 00000000..a5e12a03 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp @@ -0,0 +1,80 @@ +#pragma once +#include + +#include + +namespace graphene { namespace chain { + + struct sidechain_address_add_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + account_id_type sidechain_address_account; + sidechain_type sidechain; + string deposit_public_key; + string deposit_address; + string deposit_address_data; + string withdraw_public_key; + string withdraw_address; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_address_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_address_id_type sidechain_address_id; + account_id_type sidechain_address_account; + sidechain_type sidechain; + optional deposit_public_key; + optional deposit_address; + optional deposit_address_data; + optional withdraw_public_key; + optional withdraw_address; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_address_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_address_id_type sidechain_address_id; + account_id_type sidechain_address_account; + sidechain_type sidechain; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::sidechain_address_add_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_add_operation, (fee)(payer) + (sidechain_address_account)(sidechain) + (deposit_public_key)(deposit_address)(deposit_address_data) + (withdraw_public_key)(withdraw_address) ) + +FC_REFLECT(graphene::chain::sidechain_address_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_update_operation, (fee)(payer) + (sidechain_address_id) + (sidechain_address_account)(sidechain) + (deposit_public_key)(deposit_address)(deposit_address_data) + (withdraw_public_key)(withdraw_address) ) + +FC_REFLECT(graphene::chain::sidechain_address_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_delete_operation, (fee)(payer) + (sidechain_address_id) + (sidechain_address_account)(sidechain) ) diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp new file mode 100644 index 00000000..01a4ce74 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp @@ -0,0 +1,88 @@ +#pragma once +#include +#include +#include +#include + +namespace graphene { namespace chain { + + struct sidechain_transaction_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_type sidechain; + object_id_type object_id; + std::string transaction; + std::vector signers; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type signer; + account_id_type payer; + + sidechain_transaction_id_type sidechain_transaction_id; + std::string signature; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct sidechain_transaction_send_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_transaction_id_type sidechain_transaction_id; + std::string sidechain_transaction; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct sidechain_transaction_settle_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_transaction_id_type sidechain_transaction_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::sidechain_transaction_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_create_operation, (fee)(payer) + (sidechain) + (object_id) + (transaction) + (signers) ) + +FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation, (fee)(signer)(payer) + (sidechain_transaction_id) + (signature) ) + +FC_REFLECT( graphene::chain::sidechain_transaction_send_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_send_operation, (fee)(payer) + (sidechain_transaction_id) + (sidechain_transaction) ) + +FC_REFLECT( graphene::chain::sidechain_transaction_settle_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_settle_operation, (fee)(payer) + (sidechain_transaction_id) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son.hpp b/libraries/chain/include/graphene/chain/protocol/son.hpp new file mode 100644 index 00000000..10a75412 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + struct son_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type owner_account; + std::string url; + vesting_balance_id_type deposit; + public_key_type signing_key; + flat_map sidechain_public_keys; + vesting_balance_id_type pay_vb; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + optional new_url; + optional new_deposit; + optional new_signing_key; + optional> new_sidechain_public_keys; + optional new_pay_vb; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_deregister_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type payer; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_heartbeat_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + time_point_sec ts; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + + struct son_report_down_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type payer; + time_point_sec down_ts; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + enum class son_maintenance_request_type + { + request_maintenance, + cancel_request_maintenance + }; + + struct son_maintenance_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + son_maintenance_request_type request_type = son_maintenance_request_type::request_maintenance; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_create_operation, (fee)(owner_account)(url)(deposit)(signing_key)(sidechain_public_keys) + (pay_vb) ) + +FC_REFLECT(graphene::chain::son_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_update_operation, (fee)(son_id)(owner_account)(new_url)(new_deposit) + (new_signing_key)(new_sidechain_public_keys)(new_pay_vb) ) + +FC_REFLECT(graphene::chain::son_deregister_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_deregister_operation, (fee)(son_id)(payer) ) + +FC_REFLECT(graphene::chain::son_heartbeat_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_heartbeat_operation, (fee)(son_id)(owner_account)(ts) ) + +FC_REFLECT(graphene::chain::son_report_down_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_report_down_operation, (fee)(son_id)(payer)(down_ts) ) + +FC_REFLECT_ENUM( graphene::chain::son_maintenance_request_type, (request_maintenance)(cancel_request_maintenance) ) +FC_REFLECT(graphene::chain::son_maintenance_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_maintenance_operation, (fee)(son_id)(owner_account)(request_type) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp new file mode 100644 index 00000000..5194bed2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + struct son_wallet_recreate_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + vector sons; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_id_type son_wallet_id; + sidechain_type sidechain; + string address; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_recreate_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_recreate_operation, (fee)(payer)(sons) ) +FC_REFLECT(graphene::chain::son_wallet_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_update_operation, (fee)(payer)(son_wallet_id)(sidechain)(address) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp new file mode 100644 index 00000000..1b6a02c7 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +#include + +namespace graphene { namespace chain { + + struct son_wallet_deposit_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_id_type son_id; + fc::time_point_sec timestamp; + uint32_t block_num; + sidechain_type sidechain; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + fc::safe sidechain_amount; + chain::account_id_type peerplays_from; + chain::account_id_type peerplays_to; + chain::asset peerplays_asset; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_deposit_process_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_deposit_id_type son_wallet_deposit_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_deposit_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_deposit_create_operation, (fee)(payer) + (son_id) (timestamp) (block_num) (sidechain) + (sidechain_uid) (sidechain_transaction_id) (sidechain_from) (sidechain_to) (sidechain_currency) (sidechain_amount) + (peerplays_from) (peerplays_to) (peerplays_asset) ) + +FC_REFLECT(graphene::chain::son_wallet_deposit_process_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_deposit_process_operation, (fee)(payer) + (son_wallet_deposit_id) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp new file mode 100644 index 00000000..0461d4b2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp @@ -0,0 +1,56 @@ +#pragma once +#include +#include + +#include + +namespace graphene { namespace chain { + + struct son_wallet_withdraw_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_id_type son_id; + fc::time_point_sec timestamp; + uint32_t block_num; + sidechain_type sidechain; + std::string peerplays_uid; + std::string peerplays_transaction_id; + chain::account_id_type peerplays_from; + chain::asset peerplays_asset; + sidechain_type withdraw_sidechain; + std::string withdraw_address; + std::string withdraw_currency; + safe withdraw_amount; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_withdraw_process_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_withdraw_id_type son_wallet_withdraw_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_withdraw_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_withdraw_create_operation, (fee)(payer) + (son_id) (timestamp) (block_num) (sidechain) + (peerplays_uid) (peerplays_transaction_id) (peerplays_from) (peerplays_asset) + (withdraw_sidechain) (withdraw_address) (withdraw_currency) (withdraw_amount) ) + +FC_REFLECT(graphene::chain::son_wallet_withdraw_process_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_withdraw_process_operation, (fee)(payer) + (son_wallet_withdraw_id) ) diff --git a/libraries/chain/include/graphene/chain/protocol/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index ec8f3f53..5dbb7752 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -112,7 +112,10 @@ namespace graphene { namespace chain { return results; } - void get_required_authorities( flat_set& active, flat_set& owner, vector& other )const; + void get_required_authorities( flat_set& active, + flat_set& owner, + vector& other, + bool ignore_custom_operation_required_auths )const; }; /** @@ -142,6 +145,7 @@ namespace graphene { namespace chain { const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_authorities, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; @@ -150,6 +154,7 @@ namespace graphene { namespace chain { const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; /** @@ -158,13 +163,13 @@ namespace graphene { namespace chain { * some cases where get_required_signatures() returns a * non-minimal set. */ - set minimize_required_signatures( const chain_id_type& chain_id, const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH ) const; @@ -198,10 +203,11 @@ namespace graphene { namespace chain { const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, - bool allow_committe = false, + bool allow_committee = false, const flat_set& active_aprovals = flat_set(), - const flat_set& owner_approvals = flat_set()); + const flat_set& owner_approvals = flat_set() ); /** * @brief captures the result of evaluating the operations contained in the transaction diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 6fa2ab4d..436197a7 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -176,6 +176,14 @@ namespace graphene { namespace chain { offer_object_type, nft_metadata_type, nft_object_type, + account_role_type, + son_object_type, + son_proposal_object_type, + son_wallet_object_type, + son_wallet_deposit_object_type, + son_wallet_withdraw_object_type, + sidechain_address_object_type, + sidechain_transaction_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -205,7 +213,9 @@ namespace graphene { namespace chain { impl_global_betting_statistics_object_type, impl_lottery_balance_object_type, impl_sweeps_vesting_balance_object_type, - impl_offer_history_object_type + impl_offer_history_object_type, + impl_son_statistics_object_type, + impl_son_schedule_object_type }; //typedef fc::unsigned_int object_id_type; @@ -241,6 +251,14 @@ namespace graphene { namespace chain { class offer_object; class nft_metadata_object; class nft_object; + class account_role_object; + class son_object; + class son_proposal_object; + class son_wallet_object; + class son_wallet_deposit_object; + class son_wallet_withdraw_object; + class sidechain_address_object; + class sidechain_transaction_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -272,6 +290,14 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, offer_object_type, offer_object> offer_id_type; typedef object_id< protocol_ids, nft_metadata_type, nft_metadata_object> nft_metadata_id_type; typedef object_id< protocol_ids, nft_object_type, nft_object> nft_id_type; + typedef object_id< protocol_ids, account_role_type, account_role_object> account_role_id_type; + typedef object_id< protocol_ids, son_object_type, son_object> son_id_type; + typedef object_id< protocol_ids, son_proposal_object_type, son_proposal_object> son_proposal_id_type; + typedef object_id< protocol_ids, son_wallet_object_type, son_wallet_object> son_wallet_id_type; + typedef object_id< protocol_ids, son_wallet_deposit_object_type, son_wallet_deposit_object> son_wallet_deposit_id_type; + typedef object_id< protocol_ids, son_wallet_withdraw_object_type, son_wallet_withdraw_object> son_wallet_withdraw_id_type; + typedef object_id< protocol_ids, sidechain_address_object_type, sidechain_address_object> sidechain_address_id_type; + typedef object_id< protocol_ids, sidechain_transaction_object_type,sidechain_transaction_object> sidechain_transaction_id_type; // implementation types class global_property_object; @@ -296,6 +322,8 @@ namespace graphene { namespace chain { class lottery_balance_object; class sweeps_vesting_balance_object; class offer_history_object; + class son_statistics_object; + class son_schedule_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; @@ -325,6 +353,8 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_lottery_balance_object_type, lottery_balance_object > lottery_balance_id_type; typedef object_id< implementation_ids, impl_sweeps_vesting_balance_object_type, sweeps_vesting_balance_object> sweeps_vesting_balance_id_type; typedef object_id< implementation_ids, impl_offer_history_object_type, offer_history_object> offer_history_id_type; + typedef object_id< implementation_ids, impl_son_statistics_object_type, son_statistics_object > son_statistics_id_type; + typedef object_id< implementation_ids, impl_son_schedule_object_type, son_schedule_object> son_schedule_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -459,6 +489,14 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (offer_object_type) (nft_metadata_type) (nft_object_type) + (account_role_type) + (son_object_type) + (son_proposal_object_type) + (son_wallet_object_type) + (son_wallet_deposit_object_type) + (son_wallet_withdraw_object_type) + (sidechain_address_object_type) + (sidechain_transaction_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -487,6 +525,8 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_lottery_balance_object_type) (impl_sweeps_vesting_balance_object_type) (impl_offer_history_object_type) + (impl_son_statistics_object_type) + (impl_son_schedule_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) @@ -535,6 +575,15 @@ FC_REFLECT_TYPENAME( graphene::chain::custom_account_authority_id_type ) FC_REFLECT_TYPENAME( graphene::chain::offer_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::nft_metadata_id_type ) FC_REFLECT_TYPENAME( graphene::chain::nft_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::account_role_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_proposal_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_deposit_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_withdraw_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::sidechain_address_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::sidechain_transaction_id_type ) + FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/protocol/vesting.hpp b/libraries/chain/include/graphene/chain/protocol/vesting.hpp index 4dffb253..9bde1008 100644 --- a/libraries/chain/include/graphene/chain/protocol/vesting.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vesting.hpp @@ -27,12 +27,14 @@ namespace graphene { namespace chain { - enum class vesting_balance_type { normal, gpos }; + enum class vesting_balance_type { normal, gpos, son }; inline std::string get_vesting_balance_type(vesting_balance_type type) { switch (type) { case vesting_balance_type::normal: return "NORMAL"; + case vesting_balance_type::son: + return "SON"; case vesting_balance_type::gpos: default: return "GPOS"; @@ -55,9 +57,10 @@ namespace graphene { namespace chain { cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){} }; - typedef fc::static_variant vesting_policy_initializer; - + struct dormant_vesting_policy_initializer {}; + typedef fc::static_variant vesting_policy_initializer; /** * @brief Create a vesting balance. @@ -127,13 +130,14 @@ FC_REFLECT( graphene::chain::vesting_balance_create_operation::fee_parameters_ty FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy)(balance_type) ) -FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount)) +FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) ) FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) ) FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) ) +FC_REFLECT(graphene::chain::dormant_vesting_policy_initializer, ) FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer ) -FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (normal)(gpos) ) +FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (normal)(gpos)(son) ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::vesting_balance_create_operation::fee_parameters_type ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::vesting_balance_withdraw_operation::fee_parameters_type ) diff --git a/libraries/chain/include/graphene/chain/protocol/vote.hpp b/libraries/chain/include/graphene/chain/protocol/vote.hpp index 7d733e45..8a46954d 100644 --- a/libraries/chain/include/graphene/chain/protocol/vote.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vote.hpp @@ -59,6 +59,7 @@ struct vote_id_type committee, witness, worker, + son, VOTE_TYPE_COUNT }; @@ -143,7 +144,7 @@ void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo, ui FC_REFLECT_TYPENAME( fc::flat_set ) -FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) ) +FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(son)(VOTE_TYPE_COUNT) ) FC_REFLECT( graphene::chain::vote_id_type, (content) ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::vote_id_type ) diff --git a/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp new file mode 100644 index 00000000..6851c4e7 --- /dev/null +++ b/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp @@ -0,0 +1,48 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + struct rbac_operation_hardfork_visitor + { + typedef void result_type; + const fc::time_point_sec block_time; + + rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {} + void operator()(int op_type) const + { + int first_allowed_op = operation::tag::value; + switch (op_type) + { + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permissions and roles not allowed on this operation yet!"); + break; + default: + FC_ASSERT(op_type >= operation::tag::value && op_type < first_allowed_op, "Custom permissions and roles not allowed on this operation!"); + } + } + }; + + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp new file mode 100644 index 00000000..82bfcf1a --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class add_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_add_operation operation_type; + + void_result do_evaluate(const sidechain_address_add_operation& o); + object_id_type do_apply(const sidechain_address_add_operation& o); +}; + +class update_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_update_operation operation_type; + + void_result do_evaluate(const sidechain_address_update_operation& o); + object_id_type do_apply(const sidechain_address_update_operation& o); +}; + +class delete_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_delete_operation operation_type; + + void_result do_evaluate(const sidechain_address_delete_operation& o); + void_result do_apply(const sidechain_address_delete_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp new file mode 100644 index 00000000..b8aa07c6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp @@ -0,0 +1,92 @@ +#pragma once +#include +#include +#include + +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class sidechain_address_object + * @brief tracks information about a sidechain addresses for user accounts. + * @ingroup object + */ + class sidechain_address_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = sidechain_address_object_type; + + account_id_type sidechain_address_account; + sidechain_type sidechain; + string deposit_public_key; + string deposit_address; + string deposit_address_data; + string withdraw_public_key; + string withdraw_address; + time_point_sec valid_from; + time_point_sec expires; + + sidechain_address_object() : + sidechain(sidechain_type::bitcoin), + deposit_public_key(""), + deposit_address(""), + withdraw_public_key(""), + withdraw_address("") {} + }; + + struct by_account; + struct by_sidechain; + struct by_sidechain_and_deposit_public_key_and_expires; + struct by_withdraw_public_key; + struct by_account_and_sidechain_and_expires; + struct by_sidechain_and_deposit_address_and_expires; + using sidechain_address_multi_index_type = multi_index_container< + sidechain_address_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member, + member + > + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member, + member + > + >, + ordered_non_unique< tag, + composite_key, + member, + member + > + > + > + >; + using sidechain_address_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::sidechain_address_object, (graphene::db::object), + (sidechain_address_account) (sidechain) + (deposit_public_key) (deposit_address) (deposit_address_data) + (withdraw_public_key) (withdraw_address) (valid_from) (expires) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_defs.hpp b/libraries/chain/include/graphene/chain/sidechain_defs.hpp new file mode 100644 index 00000000..6bbc8b5c --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_defs.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace graphene { namespace chain { + +enum class sidechain_type { + unknown, + bitcoin, + ethereum, + eos, + peerplays +}; + +} } + +FC_REFLECT_ENUM(graphene::chain::sidechain_type, + (unknown) + (bitcoin) + (ethereum) + (eos) + (peerplays) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp new file mode 100644 index 00000000..702788ff --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class sidechain_transaction_create_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_create_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_create_operation& o); + object_id_type do_apply(const sidechain_transaction_create_operation& o); +}; + +class sidechain_transaction_sign_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_sign_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_sign_operation& o); + object_id_type do_apply(const sidechain_transaction_sign_operation& o); +}; + +class sidechain_transaction_send_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_send_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_send_operation& o); + object_id_type do_apply(const sidechain_transaction_send_operation& o); +}; + +class sidechain_transaction_settle_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_settle_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_settle_operation& o); + object_id_type do_apply(const sidechain_transaction_settle_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp new file mode 100644 index 00000000..e9011ffb --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + enum class sidechain_transaction_status { + invalid, + valid, + complete, + sent, + settled + }; + + /** + * @class sidechain_transaction_object + * @brief tracks state of sidechain transaction during signing process. + * @ingroup object + */ + class sidechain_transaction_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = sidechain_transaction_object_type; + + sidechain_type sidechain; + object_id_type object_id; + std::string transaction; + std::vector signers; + std::vector> signatures; + std::string sidechain_transaction; + + uint32_t total_weight = 0; + uint32_t current_weight = 0; + uint32_t threshold = 0; + + sidechain_transaction_status status; + }; + + struct by_object_id; + struct by_sidechain_and_status; + using sidechain_transaction_multi_index_type = multi_index_container< + sidechain_transaction_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member + > + > + > + >; + using sidechain_transaction_index = generic_index; +} } // graphene::chain + +FC_REFLECT_ENUM( graphene::chain::sidechain_transaction_status, + (invalid) + (valid) + (complete) + (sent) + (settled) ) + +FC_REFLECT_DERIVED( graphene::chain::sidechain_transaction_object, (graphene::db::object ), + (sidechain) + (object_id) + (transaction) + (signers) + (signatures) + (sidechain_transaction) + (total_weight) + (current_weight) + (threshold) + (status) ) diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp new file mode 100644 index 00000000..c3f1ac49 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -0,0 +1,61 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class create_son_evaluator : public evaluator +{ +public: + typedef son_create_operation operation_type; + + void_result do_evaluate(const son_create_operation& o); + object_id_type do_apply(const son_create_operation& o); +}; + +class update_son_evaluator : public evaluator +{ +public: + typedef son_update_operation operation_type; + + void_result do_evaluate(const son_update_operation& o); + object_id_type do_apply(const son_update_operation& o); +}; + +class deregister_son_evaluator : public evaluator +{ +public: + typedef son_deregister_operation operation_type; + + void_result do_evaluate(const son_deregister_operation& o); + void_result do_apply(const son_deregister_operation& o); +}; + +class son_heartbeat_evaluator : public evaluator +{ +public: + typedef son_heartbeat_operation operation_type; + + void_result do_evaluate(const son_heartbeat_operation& o); + object_id_type do_apply(const son_heartbeat_operation& o); +}; + +class son_report_down_evaluator : public evaluator +{ +public: + typedef son_report_down_operation operation_type; + + void_result do_evaluate(const son_report_down_operation& o); + object_id_type do_apply(const son_report_down_operation& o); +}; + +class son_maintenance_evaluator : public evaluator +{ +public: + typedef son_maintenance_operation operation_type; + + void_result do_evaluate(const son_maintenance_operation& o); + object_id_type do_apply(const son_maintenance_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_info.hpp b/libraries/chain/include/graphene/chain/son_info.hpp new file mode 100644 index 00000000..2bfecac4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_info.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_info + * @brief tracks information about a SON info required to re/create primary wallet + * @ingroup object + */ + struct son_info { + son_id_type son_id; + weight_type weight = 0; + public_key_type signing_key; + flat_map sidechain_public_keys; + + bool operator==(const son_info& rhs) { + bool son_sets_equal = + (son_id == rhs.son_id) && + (weight == rhs.weight) && + (signing_key == rhs.signing_key) && + (sidechain_public_keys.size() == rhs.sidechain_public_keys.size()); + + if (son_sets_equal) { + bool sidechain_public_keys_equal = true; + for (size_t i = 0; i < sidechain_public_keys.size(); i++) { + const auto lhs_scpk = sidechain_public_keys.nth(i); + const auto rhs_scpk = rhs.sidechain_public_keys.nth(i); + sidechain_public_keys_equal = sidechain_public_keys_equal && + (lhs_scpk->first == rhs_scpk->first) && + (lhs_scpk->second == rhs_scpk->second); + } + son_sets_equal = son_sets_equal && sidechain_public_keys_equal; + } + return son_sets_equal; + } + }; + +} } + +FC_REFLECT( graphene::chain::son_info, + (son_id) + (weight) + (signing_key) + (sidechain_public_keys) ) diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp new file mode 100644 index 00000000..3335bd8a --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -0,0 +1,133 @@ +#pragma once +#include +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + enum class son_status + { + inactive, + active, + request_maintenance, + in_maintenance, + deregistered + }; + /** + * @class son_statistics_object + * @ingroup object + * @ingroup implementation + * + * This object contains regularly updated statistical data about an SON. It is provided for the purpose of + * separating the SON transaction data that changes frequently from the SON object data that is mostly static. + */ + class son_statistics_object : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_son_statistics_object_type; + + son_id_type owner; + // Lifetime total transactions signed + uint64_t total_txs_signed = 0; + // Transactions signed since the last son payouts + uint64_t txs_signed = 0; + // Total Voted Active time i.e. duration selected as part of voted active SONs + uint64_t total_voted_time = 0; + // Total Downtime barring the current down time in seconds, used for stats to present to user + uint64_t total_downtime = 0; + // Current Interval Downtime since last maintenance + uint64_t current_interval_downtime = 0; + // Down timestamp, if son status is in_maintenance use this + fc::time_point_sec last_down_timestamp; + // Last Active heartbeat timestamp + fc::time_point_sec last_active_timestamp; + // Deregistered Timestamp + fc::time_point_sec deregistered_timestamp; + // Total sidechain transactions reported by SON network while SON was active + uint64_t total_sidechain_txs_reported = 0; + // Sidechain transactions reported by this SON + uint64_t sidechain_txs_reported = 0; + }; + + /** + * @class son_object + * @brief tracks information about a SON account. + * @ingroup object + */ + class son_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_object_type; + + account_id_type son_account; + vote_id_type vote_id; + uint64_t total_votes = 0; + string url; + vesting_balance_id_type deposit; + public_key_type signing_key; + vesting_balance_id_type pay_vb; + son_statistics_id_type statistics; + son_status status = son_status::inactive; + flat_map sidechain_public_keys; + + void pay_son_fee(share_type pay, database& db); + bool has_valid_config()const; + }; + + struct by_account; + struct by_vote_id; + using son_multi_index_type = multi_index_container< + son_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using son_index = generic_index; + + struct by_owner; + using son_stats_multi_index_type = multi_index_container< + son_statistics_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using son_stats_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(request_maintenance)(in_maintenance)(deregistered) ) + +FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object), + (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb)(statistics)(status)(sidechain_public_keys) ) + +FC_REFLECT_DERIVED( graphene::chain::son_statistics_object, + (graphene::db::object), + (owner) + (total_txs_signed) + (txs_signed) + (total_voted_time) + (total_downtime) + (current_interval_downtime) + (last_down_timestamp) + (last_active_timestamp) + (deregistered_timestamp) + (total_sidechain_txs_reported) + (sidechain_txs_reported) + ) diff --git a/libraries/chain/include/graphene/chain/son_proposal_object.hpp b/libraries/chain/include/graphene/chain/son_proposal_object.hpp new file mode 100644 index 00000000..7b1674b8 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_proposal_object.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +enum class son_proposal_type +{ + son_deregister_proposal, + son_report_down_proposal +}; + +class son_proposal_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_proposal_object_type; + + son_proposal_id_type get_id()const { return id; } + + proposal_id_type proposal_id; + son_id_type son_id; + son_proposal_type proposal_type; +}; + +struct by_proposal; +using son_proposal_multi_index_container = multi_index_container< + son_proposal_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_unique< tag< by_proposal >, member< son_proposal_object, proposal_id_type, &son_proposal_object::proposal_id > > + > +>; +using son_proposal_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_ENUM( graphene::chain::son_proposal_type, (son_deregister_proposal)(son_report_down_proposal) ) + +FC_REFLECT_DERIVED( graphene::chain::son_proposal_object, (graphene::chain::object), (proposal_id)(son_id)(proposal_type) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_deposit_evaluator.hpp b/libraries/chain/include/graphene/chain/son_wallet_deposit_evaluator.hpp new file mode 100644 index 00000000..f3780c1f --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_deposit_evaluator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +namespace graphene { namespace chain { + +class create_son_wallet_deposit_evaluator : public evaluator +{ +public: + typedef son_wallet_deposit_create_operation operation_type; + + void_result do_evaluate(const son_wallet_deposit_create_operation& o); + object_id_type do_apply(const son_wallet_deposit_create_operation& o); +}; + +class process_son_wallet_deposit_evaluator : public evaluator +{ +public: + typedef son_wallet_deposit_process_operation operation_type; + + void_result do_evaluate(const son_wallet_deposit_process_operation& o); + object_id_type do_apply(const son_wallet_deposit_process_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp new file mode 100644 index 00000000..ae68a64f --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_deposit_object + * @brief tracks information about a SON wallet deposit. + * @ingroup object + */ + class son_wallet_deposit_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_deposit_object_type; + + time_point_sec timestamp; + uint32_t block_num; + sidechain_type sidechain = sidechain_type::unknown; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + safe sidechain_amount; + chain::account_id_type peerplays_from; + chain::account_id_type peerplays_to; + chain::asset peerplays_asset; + + std::map expected_reports; + std::set received_reports; + + bool confirmed = false; + + bool processed = false; + }; + + struct by_sidechain_uid; + struct by_sidechain_and_confirmed_and_processed; + using son_wallet_deposit_multi_index_type = multi_index_container< + son_wallet_deposit_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member, + member + > + > + > + >; + using son_wallet_deposit_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_deposit_object, (graphene::db::object), + (timestamp) (block_num) (sidechain) + (sidechain_uid) (sidechain_transaction_id) (sidechain_from) (sidechain_to) (sidechain_currency) (sidechain_amount) + (peerplays_from) (peerplays_to) (peerplays_asset) + (expected_reports) (received_reports) + (confirmed) (processed) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp b/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp new file mode 100644 index 00000000..78e8655f --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class recreate_son_wallet_evaluator : public evaluator +{ +public: + typedef son_wallet_recreate_operation operation_type; + + void_result do_evaluate(const son_wallet_recreate_operation& o); + object_id_type do_apply(const son_wallet_recreate_operation& o); +}; + +class update_son_wallet_evaluator : public evaluator +{ +public: + typedef son_wallet_update_operation operation_type; + + void_result do_evaluate(const son_wallet_update_operation& o); + object_id_type do_apply(const son_wallet_update_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_object.hpp new file mode 100644 index 00000000..315def33 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_object.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_object + * @brief tracks information about a SON wallet. + * @ingroup object + */ + class son_wallet_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_object_type; + + time_point_sec valid_from; + time_point_sec expires; + + flat_map addresses; + vector sons; + }; + + struct by_valid_from; + struct by_expires; + using son_wallet_multi_index_type = multi_index_container< + son_wallet_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using son_wallet_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_object, (graphene::db::object), + (valid_from) (expires) (addresses) (sons) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp b/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp new file mode 100644 index 00000000..f5c08cd3 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +namespace graphene { namespace chain { + +class create_son_wallet_withdraw_evaluator : public evaluator +{ +public: + typedef son_wallet_withdraw_create_operation operation_type; + + void_result do_evaluate(const son_wallet_withdraw_create_operation& o); + object_id_type do_apply(const son_wallet_withdraw_create_operation& o); +}; + +class process_son_wallet_withdraw_evaluator : public evaluator +{ +public: + typedef son_wallet_withdraw_process_operation operation_type; + + void_result do_evaluate(const son_wallet_withdraw_process_operation& o); + object_id_type do_apply(const son_wallet_withdraw_process_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp new file mode 100644 index 00000000..d65f5cab --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_withdraw_object + * @brief tracks information about a SON wallet withdrawal. + * @ingroup object + */ + class son_wallet_withdraw_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_withdraw_object_type; + + time_point_sec timestamp; + uint32_t block_num; + sidechain_type sidechain = sidechain_type::unknown; + std::string peerplays_uid; + std::string peerplays_transaction_id; + chain::account_id_type peerplays_from; + chain::asset peerplays_asset; + sidechain_type withdraw_sidechain; + std::string withdraw_address; + std::string withdraw_currency; + safe withdraw_amount; + + std::map expected_reports; + std::set received_reports; + + bool confirmed = false; + + bool processed = false; + }; + + struct by_peerplays_uid; + struct by_withdraw_sidechain_and_confirmed_and_processed; + using son_wallet_withdraw_multi_index_type = multi_index_container< + son_wallet_withdraw_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member, + member + > + > + > + >; + using son_wallet_withdraw_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_withdraw_object, (graphene::db::object), + (timestamp) (block_num) (sidechain) + (peerplays_uid) (peerplays_transaction_id) (peerplays_from) (peerplays_asset) + (withdraw_sidechain) (withdraw_address) (withdraw_currency) (withdraw_amount) + (expected_reports) (received_reports) + (confirmed) (processed) ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index b03b8f7b..1e87246e 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -118,10 +118,32 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; + /** + * @brief Cant withdraw anything while balance is in this policy. + * + * This policy is needed to register SON users where balance may be claimable only after + * the SON object is deleted(plus a linear policy). + * When deleting a SON member the dormant mode will be replaced by a linear policy. + * + * @note New funds may not be added to a dormant vesting balance. + */ + struct dormant_vesting_policy + { + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; } + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context&) + { FC_THROW( "May not deposit vested into a linear vesting balance." ); } + void on_withdraw(const vesting_policy_context& ctx); + }; + typedef fc::static_variant< linear_vesting_policy, - cdd_vesting_policy - > vesting_policy; + cdd_vesting_policy, + dormant_vesting_policy + > vesting_policy; /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. @@ -140,7 +162,7 @@ namespace graphene { namespace chain { /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn vesting_policy policy; - /// We can have 2 types of vesting, gpos and all the rest + /// We can have 3 types of vesting, gpos, son and the rest vesting_balance_type balance_type = vesting_balance_type::normal; vesting_balance_object() {} @@ -224,6 +246,8 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy, (coin_seconds_earned_last_update) ) +FC_REFLECT(graphene::chain::dormant_vesting_policy, ) + FC_REFLECT_TYPENAME( graphene::chain::vesting_policy ) FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), diff --git a/libraries/chain/include/graphene/chain/vote_count.hpp b/libraries/chain/include/graphene/chain/vote_count.hpp index f76a784d..ab2f3612 100644 --- a/libraries/chain/include/graphene/chain/vote_count.hpp +++ b/libraries/chain/include/graphene/chain/vote_count.hpp @@ -63,6 +63,17 @@ struct vote_counter out_auth = auth; } + void finish_2_3( authority& out_auth ) + { + if( total_votes == 0 ) + return; + assert( total_votes <= std::numeric_limits::max() ); + uint32_t weight = uint32_t( total_votes ); + weight = (weight * 2 / 3) + 1; + auth.weight_threshold = weight; + out_auth = auth; + } + bool is_empty()const { return (total_votes == 0); diff --git a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp index 1702212d..b934fd01 100644 --- a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp @@ -30,6 +30,9 @@ namespace graphene { namespace chain { +class witness_schedule_object; +class son_schedule_object; + typedef hash_ctr_rng< /* HashClass = */ fc::sha256, /* SeedLength = */ GRAPHENE_RNG_SEED_LENGTH @@ -51,6 +54,22 @@ typedef generic_far_future_witness_scheduler< /* debug = */ true > far_future_witness_scheduler; +typedef generic_witness_scheduler< + /* WitnessID = */ son_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_son_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > son_scheduler; + +typedef generic_far_future_witness_scheduler< + /* WitnessID = */ son_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_son_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > far_future_son_scheduler; + class witness_schedule_object : public graphene::db::abstract_object { public: @@ -71,6 +90,26 @@ class witness_schedule_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_son_schedule_object_type; + + vector< son_id_type > current_shuffled_sons; + + son_scheduler scheduler; + uint32_t last_scheduling_block; + uint64_t slots_since_genesis = 0; + fc::array< char, sizeof(secret_hash_type) > rng_seed; + + /** + * Not necessary for consensus, but used for figuring out the participation rate. + * The nth bit is 0 if the nth slot was unfilled, else it is 1. + */ + fc::uint128 recent_slots_filled; +}; + } } @@ -94,6 +133,26 @@ FC_REFLECT_DERIVED( (recent_slots_filled) (current_shuffled_witnesses) ) +FC_REFLECT( graphene::chain::son_scheduler, + (_turns) + (_tokens) + (_min_token_count) + (_ineligible_waiting_for_token) + (_ineligible_no_turn) + (_eligible) + (_schedule) + (_lame_duck) + ) +FC_REFLECT_DERIVED( + graphene::chain::son_schedule_object, + (graphene::db::object), + (scheduler) + (last_scheduling_block) + (slots_since_genesis) + (rng_seed) + (recent_slots_filled) + (current_shuffled_sons) +) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::witness_scheduler ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::witness_schedule_object ) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index ace3f91b..f7f007ff 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include namespace graphene { namespace chain { @@ -18,6 +20,10 @@ void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_creat (*op.revenue_partner)(db()); FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); } + if(op.account_role) { + const auto& ar_obj = (*op.account_role)(db()); + FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached"); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -32,6 +38,7 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat obj.revenue_split = op.revenue_split; obj.is_transferable = op.is_transferable; obj.is_sellable = op.is_sellable; + obj.account_role = op.account_role; }); return new_nft_metadata_object.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -57,6 +64,10 @@ void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_updat (*op.revenue_partner)(db()); FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); } + if(op.account_role) { + const auto& ar_obj = (*op.account_role)(db()); + FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached"); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -77,6 +88,8 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o obj.is_transferable = *op.is_transferable; if( op.is_sellable.valid() ) obj.is_sellable = *op.is_sellable; + if( op.account_role.valid() ) + obj.account_role = op.account_role; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -144,6 +157,17 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf const auto& nft_meta_obj = itr_nft->nft_metadata_id(db()); FC_ASSERT( nft_meta_obj.is_transferable == true, "NFT is not transferable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.operator_, get_type()), "Account role not valid"); + FC_ASSERT(db().account_role_valid(*ar_itr, op.to), "Account role not valid"); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/offer_evaluator.cpp b/libraries/chain/offer_evaluator.cpp index 0d1b1947..1b887466 100644 --- a/libraries/chain/offer_evaluator.cpp +++ b/libraries/chain/offer_evaluator.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,6 +40,15 @@ namespace graphene } const auto &nft_meta_obj = nft_obj.nft_metadata_id(d); FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.issuer, get_type()), "Account role not valid"); + } + } } FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future"); FC_ASSERT(op.fee.amount >= 0, "Invalid fee"); @@ -100,6 +111,18 @@ namespace graphene FC_ASSERT(!is_approved, "Bidder cannot already be an approved account of the item"); FC_ASSERT(!is_approved_operator, "Bidder cannot already be an approved operator of the item"); } + + const auto &nft_meta_obj = nft_obj.nft_metadata_id(d); + FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.bidder, get_type()), "Account role not valid"); + } + } } FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch"); @@ -335,4 +358,4 @@ namespace graphene FC_CAPTURE_AND_RETHROW((op)) } } // namespace chain -} // namespace graphene \ No newline at end of file +} // namespace graphene diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index ba714c21..e7d216ef 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,7 @@ struct proposal_operation_hardfork_visitor void operator()(const vesting_balance_create_operation &vbco) const { if(block_time < HARDFORK_GPOS_TIME) - FC_ASSERT( vbco.balance_type == vesting_balance_type::normal, "balance_type in vesting create not allowed yet!" ); + FC_ASSERT( vbco.balance_type == vesting_balance_type::normal, "balance_type in vesting create not allowed yet!" ); } void operator()(const custom_permission_create_operation &v) const { @@ -196,6 +197,41 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_set_approval_for_all_operation not allowed yet!" ); } + void operator()(const account_role_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_create_operation not allowed yet!" ); + } + + void operator()(const account_role_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_update_operation not allowed yet!" ); + } + + void operator()(const account_role_delete_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_delete_operation not allowed yet!" ); + } + + void operator()(const son_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_create_operation not allowed yet!" ); + } + + void operator()(const son_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_update_operation not allowed yet!" ); + } + + void operator()(const son_deregister_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_deregister_operation not allowed yet!" ); + } + + void operator()(const son_heartbeat_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_heartbeat_operation not allowed yet!" ); + } + + void operator()(const son_report_down_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_report_down_operation not allowed yet!" ); + } + + void operator()(const son_maintenance_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_maintenance_operation not allowed yet!" ); + } // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { @@ -204,19 +240,38 @@ struct proposal_operation_hardfork_visitor } }; -void_result proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) +void son_hardfork_visitor::operator()( const son_deregister_operation &v ) +{ + db.create([&]( son_proposal_object& son_prop ) { + son_prop.proposal_type = son_proposal_type::son_deregister_proposal; + son_prop.proposal_id = prop_id; + son_prop.son_id = v.son_id; + }); +} + +void son_hardfork_visitor::operator()( const son_report_down_operation &v ) +{ + db.create([&]( son_proposal_object& son_prop ) { + son_prop.proposal_type = son_proposal_type::son_report_down_proposal; + son_prop.proposal_id = prop_id; + son_prop.son_id = v.son_id; + }); +} + +void_result proposal_create_evaluator::do_evaluate( const proposal_create_operation& o ) { try { const database& d = db(); + auto block_time = d.head_block_time(); - proposal_operation_hardfork_visitor vtor( d.head_block_time() ); + proposal_operation_hardfork_visitor vtor( block_time ); vtor( o ); const auto& global_parameters = d.get_global_properties().parameters; - FC_ASSERT( o.expiration_time > d.head_block_time(), "Proposal has already expired on creation." ); - FC_ASSERT( o.expiration_time <= d.head_block_time() + global_parameters.maximum_proposal_lifetime, + FC_ASSERT( o.expiration_time > block_time, "Proposal has already expired on creation." ); + FC_ASSERT( o.expiration_time <= block_time + global_parameters.maximum_proposal_lifetime, "Proposal expiration time is too far in the future."); - FC_ASSERT( !o.review_period_seconds || fc::seconds(*o.review_period_seconds) < (o.expiration_time - d.head_block_time()), + FC_ASSERT( !o.review_period_seconds || fc::seconds(*o.review_period_seconds) < (o.expiration_time - block_time), "Proposal review period must be less than its overall lifetime." ); { @@ -225,7 +280,8 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati vector other; for( auto& op : o.proposed_ops ) { - operation_get_required_authorities(op.op, auths, auths, other); + operation_get_required_authorities( op.op, auths, auths, other, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(block_time) ); } FC_ASSERT( other.size() == 0 ); // TODO: what about other??? @@ -248,18 +304,19 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati } } - for( const op_wrapper& op : o.proposed_ops ) + for (const op_wrapper& op : o.proposed_ops) _proposed_trx.operations.push_back(op.op); _proposed_trx.validate(); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -object_id_type proposal_create_evaluator::do_apply(const proposal_create_operation& o) +object_id_type proposal_create_evaluator::do_apply( const proposal_create_operation& o ) { try { database& d = db(); + auto chain_time = d.head_block_time(); - const proposal_object& proposal = d.create([&](proposal_object& proposal) { + const proposal_object& proposal = d.create( [&o, this, chain_time](proposal_object& proposal) { _proposed_trx.expiration = o.expiration_time; proposal.proposed_transaction = _proposed_trx; proposal.proposer = o.fee_paying_account; @@ -273,7 +330,8 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati // TODO: consider caching values from evaluate? for( auto& op : _proposed_trx.operations ) - operation_get_required_authorities(op, required_active, proposal.required_owner_approvals, other); + operation_get_required_authorities( op, required_active, proposal.required_owner_approvals, other, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time) ); //All accounts which must provide both owner and active authority should be omitted from the active authority set; //owner authority approval implies active authority approval. @@ -282,10 +340,16 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati std::inserter(proposal.required_active_approvals, proposal.required_active_approvals.begin())); }); + son_hardfork_visitor son_vtor(d, proposal.id); + for(auto& op: o.proposed_ops) + { + op.op.visit(son_vtor); + } + return proposal.id; } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result proposal_update_evaluator::do_evaluate(const proposal_update_operation& o) +void_result proposal_update_evaluator::do_evaluate( const proposal_update_operation& o ) { try { database& d = db(); diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index a2f6d1ae..662c700a 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -24,12 +24,13 @@ #include #include #include +#include namespace graphene { namespace chain { -bool proposal_object::is_authorized_to_execute(database& db) const +bool proposal_object::is_authorized_to_execute( database& db ) const { - transaction_evaluation_state dry_run_eval(&db); + transaction_evaluation_state dry_run_eval( &db ); try { verify_authority( proposed_transaction.operations, @@ -38,6 +39,7 @@ bool proposal_object::is_authorized_to_execute(database& db) const [&]( account_id_type id ){ return &id(db).owner; }, [&]( account_id_type id, const operation& op ){ return db.get_account_custom_authorities(id, op); }, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ), db.get_global_properties().parameters.max_authority_depth, true, /* allow committee */ available_active_approvals, diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index c79811b9..2405369a 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -174,15 +174,22 @@ void account_options::validate() const { auto needed_witnesses = num_witness; auto needed_committee = num_committee; + auto needed_sons = num_son; for( vote_id_type id : votes ) if( id.type() == vote_id_type::witness && needed_witnesses ) --needed_witnesses; else if ( id.type() == vote_id_type::committee && needed_committee ) --needed_committee; + else if ( id.type() == vote_id_type::son && needed_sons ) + --needed_sons; - FC_ASSERT( needed_witnesses == 0 && needed_committee == 0, - "May not specify fewer witnesses or committee members than the number voted for."); + FC_ASSERT( needed_witnesses == 0, + "May not specify fewer witnesses than the number voted for."); + FC_ASSERT( needed_committee == 0, + "May not specify fewer committee members than the number voted for."); + FC_ASSERT( needed_sons == 0, + "May not specify fewer SONs than the number voted for."); } void affiliate_reward_distribution::validate() const diff --git a/libraries/chain/protocol/account_role.cpp b/libraries/chain/protocol/account_role.cpp new file mode 100644 index 00000000..1d71d6cc --- /dev/null +++ b/libraries/chain/protocol/account_role.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace graphene +{ + namespace chain + { + + void account_role_create_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(allowed_operations.size() > 0, "Allowed operations should be positive"); + FC_ASSERT(whitelisted_accounts.size() > 0, "Whitelisted accounts should be positive"); + } + + void account_role_update_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + for (auto aop : allowed_operations_to_add) + { + FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid"); + FC_ASSERT(allowed_operations_to_remove.find(aop) == allowed_operations_to_remove.end(), + "Cannot add and remove allowed operation at the same time."); + } + for (auto aop : allowed_operations_to_remove) + { + FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid"); + FC_ASSERT(allowed_operations_to_add.find(aop) == allowed_operations_to_add.end(), + "Cannot add and remove allowed operation at the same time."); + } + + for (auto acc : accounts_to_add) + { + FC_ASSERT(accounts_to_remove.find(acc) == accounts_to_remove.end(), + "Cannot add and remove accounts at the same time."); + } + + for (auto acc : accounts_to_remove) + { + FC_ASSERT(accounts_to_add.find(acc) == accounts_to_add.end(), + "Cannot add and remove accounts at the same time."); + } + } + + void account_role_delete_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + } + + share_type account_role_create_operation::calculate_fee(const fee_parameters_type &k) const + { + return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte); + } + + share_type account_role_update_operation::calculate_fee(const fee_parameters_type &k) const + { + return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte); + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/protocol/nft.cpp b/libraries/chain/protocol/nft.cpp index 802bf425..4a66f330 100644 --- a/libraries/chain/protocol/nft.cpp +++ b/libraries/chain/protocol/nft.cpp @@ -45,7 +45,6 @@ void nft_metadata_create_operation::validate() const FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); FC_ASSERT(is_valid_nft_token_name(name), "Invalid NFT name provided"); FC_ASSERT(is_valid_nft_token_name(symbol), "Invalid NFT symbol provided"); - FC_ASSERT(base_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI"); } void nft_metadata_update_operation::validate() const @@ -55,14 +54,11 @@ void nft_metadata_update_operation::validate() const FC_ASSERT(is_valid_nft_token_name(*name), "Invalid NFT name provided"); if(symbol) FC_ASSERT(is_valid_nft_token_name(*symbol), "Invalid NFT symbol provided"); - if(base_uri) - FC_ASSERT((*base_uri).length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI"); } void nft_mint_operation::validate() const { FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); - FC_ASSERT(token_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Token URI"); } share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_type &k) const @@ -72,7 +68,7 @@ share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_typ share_type nft_metadata_update_operation::calculate_fee(const fee_parameters_type &k) const { - return k.fee; + return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } share_type nft_mint_operation::calculate_fee(const fee_parameters_type &k) const diff --git a/libraries/chain/protocol/operations.cpp b/libraries/chain/protocol/operations.cpp index 7db51078..385e14dc 100644 --- a/libraries/chain/protocol/operations.cpp +++ b/libraries/chain/protocol/operations.cpp @@ -53,25 +53,38 @@ struct operation_validator struct operation_get_required_auth { - typedef void result_type; + using result_type = void; flat_set& active; flat_set& owner; vector& other; + bool ignore_custom_op_reqd_auths; operation_get_required_auth( flat_set& a, - flat_set& own, - vector& oth ):active(a),owner(own),other(oth){} + flat_set& own, + vector& oth, + bool ignore_custom_operation_required_auths ) + : active( a ), owner( own ), other( oth ), + ignore_custom_op_reqd_auths( ignore_custom_operation_required_auths ) + {} template - void operator()( const T& v )const - { + void operator()( const T& v ) const { active.insert( v.fee_payer() ); - v.get_required_active_authorities( active ); - v.get_required_owner_authorities( owner ); + v.get_required_active_authorities( active ); + v.get_required_owner_authorities( owner ); v.get_required_authorities( other ); } + + void operator()( const custom_operation& op ) const { + active.insert( op.fee_payer() ); + if( !ignore_custom_op_reqd_auths ) { + op.get_required_active_authorities( active ); + op.get_required_owner_authorities( owner ); + op.get_required_authorities( other ); + } + } }; void operation_validate( const operation& op ) @@ -79,12 +92,13 @@ void operation_validate( const operation& op ) op.visit( operation_validator() ); } -void operation_get_required_authorities( const operation& op, +void operation_get_required_authorities( const operation& op, flat_set& active, flat_set& owner, - vector& other ) + vector& other, + bool ignore_custom_operation_required_auths ) { - op.visit( operation_get_required_auth( active, owner, other ) ); + op.visit( operation_get_required_auth( active, owner, other, ignore_custom_operation_required_auths ) ); } } } // namespace graphene::chain diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index 62419948..98e17238 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -95,10 +95,13 @@ void transaction::set_reference_block( const block_id_type& reference_block ) ref_block_prefix = reference_block._hash[1]; } -void transaction::get_required_authorities( flat_set& active, flat_set& owner, vector& other )const +void transaction::get_required_authorities( flat_set& active, + flat_set& owner, + vector& other, + bool ignore_custom_operation_required_auths )const { - for( const auto& op : operations ) - operation_get_required_authorities( op, active, owner, other ); + for ( const auto& op : operations ) + operation_get_required_authorities( op, active, owner, other, ignore_custom_operation_required_auths ); } @@ -249,8 +252,9 @@ void verify_authority( const vector& ops, const flat_set& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion_depth, - bool allow_committe, + bool allow_committee, const flat_set& active_aprovals, const flat_set& owner_approvals ) { try { @@ -276,7 +280,8 @@ void verify_authority( const vector& ops, const flat_set operation_required_active; - operation_get_required_authorities( op, operation_required_active, required_owner, other ); + operation_get_required_authorities( op, operation_required_active, required_owner, other, + ignore_custom_operation_required_auths ); auto itr = operation_required_active.begin(); while ( itr != operation_required_active.end() ) { @@ -289,7 +294,7 @@ void verify_authority( const vector& ops, const flat_set signed_transaction::get_required_signatures( const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_authorities, uint32_t max_recursion_depth )const { flat_set required_active; @@ -370,7 +376,7 @@ set signed_transaction::get_required_signatures( for( const auto& op : operations ) { flat_set operation_required_active; - operation_get_required_authorities( op, operation_required_active, required_owner, other ); + operation_get_required_authorities( op, operation_required_active, required_owner, other, ignore_custom_operation_required_authorities ); auto itr = operation_required_active.begin(); while ( itr != operation_required_active.end() ) { @@ -383,7 +389,7 @@ set signed_transaction::get_required_signatures( required_active.insert( operation_required_active.begin(), operation_required_active.end() ); } - for( const auto& auth : other ) + for (const auto& auth : other) s.check_authority(&auth); for( auto& owner : required_owner ) s.check_authority( get_owner( owner ) ); @@ -407,10 +413,12 @@ set signed_transaction::minimize_required_signatures( const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion ) const { - set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner, get_custom, max_recursion ); + set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner, get_custom, + ignore_custom_operation_required_auths, max_recursion ); flat_set< public_key_type > result( s.begin(), s.end() ); for( const public_key_type& k : s ) @@ -418,7 +426,8 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::chain::verify_authority( operations, result, get_active, get_owner, get_custom, max_recursion ); + graphene::chain::verify_authority( operations, result, get_active, get_owner, get_custom, + ignore_custom_operation_required_auths, max_recursion ); continue; // element stays erased if verify_authority is ok } catch( const tx_missing_owner_auth& e ) {} @@ -434,6 +443,7 @@ void signed_transaction::verify_authority( const std::function& get_active, const std::function& get_owner, const std::function(account_id_type, const operation&)>& get_custom, + bool ignore_custom_operation_required_auths, uint32_t max_recursion )const { try { graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, get_custom, max_recursion ); diff --git a/libraries/chain/sidechain_address_evaluator.cpp b/libraries/chain/sidechain_address_evaluator.cpp new file mode 100644 index 00000000..ae802627 --- /dev/null +++ b/libraries/chain/sidechain_address_evaluator.cpp @@ -0,0 +1,114 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result add_sidechain_address_evaluator::do_evaluate(const sidechain_address_add_operation& op) +{ try{ + FC_ASSERT( op.deposit_public_key.length() > 0 && op.deposit_address.length() == 0 && op.deposit_address_data.length() == 0, "User should add a valid deposit public key and a null deposit address"); + const auto& sdpke_idx = db().get_index_type().indices().get(); + FC_ASSERT( sdpke_idx.find(boost::make_tuple(op.sidechain, op.deposit_public_key, time_point_sec::maximum())) == sdpke_idx.end(), "An active deposit key already exists" ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type add_sidechain_address_evaluator::do_apply(const sidechain_address_add_operation& op) +{ try { + const auto &sidechain_addresses_idx = db().get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(op.sidechain_address_account, op.sidechain, time_point_sec::maximum())); + + if (addr_itr != sidechain_addresses_idx.end()) + { + db().modify(*addr_itr, [&](sidechain_address_object &sao) { + sao.expires = db().head_block_time(); + }); + } + + const auto& new_sidechain_address_object = db().create( [&]( sidechain_address_object& obj ){ + obj.sidechain_address_account = op.sidechain_address_account; + obj.sidechain = op.sidechain; + obj.deposit_public_key = op.deposit_public_key; + obj.deposit_address = ""; + obj.deposit_address_data = ""; + obj.withdraw_public_key = op.withdraw_public_key; + obj.withdraw_address = op.withdraw_address; + obj.valid_from = db().head_block_time(); + obj.expires = time_point_sec::maximum(); + }); + return new_sidechain_address_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_sidechain_address_evaluator::do_evaluate(const sidechain_address_update_operation& op) +{ try { + const auto& sidx = db().get_index_type().indices().get(); + const auto& son_obj = sidx.find(op.payer); + FC_ASSERT( son_obj != sidx.end() && db().is_son_active(son_obj->id), "Non active SON trying to update deposit address object" ); + const auto& sdpke_idx = db().get_index_type().indices().get(); + FC_ASSERT( op.deposit_address.valid() && op.deposit_public_key.valid() && op.deposit_address_data.valid(), "Update operation by SON is not valid"); + FC_ASSERT( (*op.deposit_address).length() > 0 && (*op.deposit_public_key).length() > 0 && (*op.deposit_address_data).length() > 0, "SON should create a valid deposit address with valid deposit public key"); + FC_ASSERT( sdpke_idx.find(boost::make_tuple(op.sidechain, *op.deposit_public_key, time_point_sec::maximum())) != sdpke_idx.end(), "Invalid update operation by SON" ); + FC_ASSERT( db().get(op.sidechain_address_id).sidechain_address_account == op.sidechain_address_account, "Invalid sidechain address account" ); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.sidechain_address_id) != idx.end(), "Invalid sidechain address ID" ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_sidechain_address_evaluator::do_apply(const sidechain_address_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.sidechain_address_id); + if(itr != idx.end()) + { + // Case of change of Active SONs, store the outgoing address object with proper valid_from and expires updated + if(itr->deposit_address.length() > 0) { + db().create( [&]( sidechain_address_object& obj ) { + obj.sidechain_address_account = op.sidechain_address_account; + obj.sidechain = op.sidechain; + obj.deposit_public_key = *op.deposit_public_key; + obj.deposit_address = itr->deposit_address; + obj.deposit_address_data = itr->deposit_address_data; + obj.withdraw_public_key = *op.withdraw_public_key; + obj.withdraw_address = *op.withdraw_address; + obj.valid_from = itr->valid_from; + obj.expires = db().head_block_time(); + }); + db().modify(*itr, [&](sidechain_address_object &sao) { + if(op.deposit_address.valid()) sao.deposit_address = *op.deposit_address; + if(op.deposit_address_data.valid()) sao.deposit_address_data = *op.deposit_address_data; + sao.valid_from = db().head_block_time(); + }); + } else { + // Case of SON creating deposit address for a user input + db().modify(*itr, [&op](sidechain_address_object &sao) { + if(op.deposit_address.valid()) sao.deposit_address = *op.deposit_address; + if(op.deposit_address_data.valid()) sao.deposit_address_data = *op.deposit_address_data; + }); + } + } + return op.sidechain_address_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_sidechain_address_evaluator::do_evaluate(const sidechain_address_delete_operation& op) +{ try { + FC_ASSERT(db().get(op.sidechain_address_id).sidechain_address_account == op.sidechain_address_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.sidechain_address_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_sidechain_address_evaluator::do_apply(const sidechain_address_delete_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto sidechain_address = idx.find(op.sidechain_address_id); + if(sidechain_address != idx.end()) { + db().modify(*sidechain_address, [&](sidechain_address_object &sao) { + sao.expires = db().head_block_time(); + }); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/sidechain_transaction_evaluator.cpp b/libraries/chain/sidechain_transaction_evaluator.cpp new file mode 100644 index 00000000..12bf2f42 --- /dev/null +++ b/libraries/chain/sidechain_transaction_evaluator.cpp @@ -0,0 +1,159 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result sidechain_transaction_create_evaluator::do_evaluate(const sidechain_transaction_create_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + FC_ASSERT((op.object_id.is() || op.object_id.is() || op.object_id.is()), "Invalid object id"); + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.object_id); + FC_ASSERT(sto_obj == sto_idx.end(), "Sidechain transaction for a given object is already created"); + + FC_ASSERT(!op.transaction.empty(), "Sidechain transaction data not set"); + FC_ASSERT(op.signers.size() > 0, "Sidechain transaction signers not set"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_create_evaluator::do_apply(const sidechain_transaction_create_operation &op) +{ try { + const auto &new_sidechain_transaction_object = db().create([&](sidechain_transaction_object &sto) { + sto.sidechain = op.sidechain; + sto.object_id = op.object_id; + sto.transaction = op.transaction; + sto.signers = op.signers; + std::transform(op.signers.begin(), op.signers.end(), std::inserter(sto.signatures, sto.signatures.end()), [](const son_info &si) { + return std::make_pair(si.son_id, std::string()); + }); + for (const auto &si : op.signers) { + sto.total_weight = sto.total_weight + si.weight; + } + sto.sidechain_transaction = ""; + sto.current_weight = 0; + sto.threshold = sto.total_weight * 2 / 3 + 1; + sto.status = sidechain_transaction_status::valid; + }); + return new_sidechain_transaction_object.id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +void_result sidechain_transaction_sign_evaluator::do_evaluate(const sidechain_transaction_sign_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.sidechain_transaction_id); + FC_ASSERT(sto_obj != sto_idx.end(), "Sidechain transaction object not found"); + + const auto &son_idx = db().get_index_type().indices().get(); + const auto &son_obj = son_idx.find(op.signer); + FC_ASSERT(son_obj != son_idx.end(), "SON object not found"); + + bool expected = false; + for (auto signature : sto_obj->signatures) { + if (signature.first == son_obj->id) { + expected = signature.second.empty(); + } + } + FC_ASSERT(expected, "Signer not expected"); + + FC_ASSERT(sto_obj->status == sidechain_transaction_status::valid, "Invalid transaction status"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_sign_evaluator::do_apply(const sidechain_transaction_sign_operation &op) +{ try { + const auto &sto_idx = db().get_index_type().indices().get(); + auto sto_obj = sto_idx.find(op.sidechain_transaction_id); + + const auto &son_idx = db().get_index_type().indices().get(); + auto son_obj = son_idx.find(op.signer); + + db().modify(*sto_obj, [&](sidechain_transaction_object &sto) { + for (size_t i = 0; i < sto.signatures.size(); i++) { + if (sto.signatures.at(i).first == son_obj->id) { + sto.signatures.at(i).second = op.signature; + } + } + for (size_t i = 0; i < sto.signers.size(); i++) { + if (sto.signers.at(i).son_id == son_obj->id) { + sto.current_weight = sto.current_weight + sto.signers.at(i).weight; + } + } + if (sto.current_weight >= sto.threshold) { + sto.status = sidechain_transaction_status::complete; + } + }); + + db().modify(son_obj->statistics(db()), [&](son_statistics_object& sso) { + sso.txs_signed += 1; + }); + + return op.sidechain_transaction_id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +void_result sidechain_transaction_send_evaluator::do_evaluate(const sidechain_transaction_send_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.sidechain_transaction_id); + FC_ASSERT(sto_obj != sto_idx.end(), "Sidechain transaction object not found"); + + FC_ASSERT(sto_obj->status == sidechain_transaction_status::complete, "Invalid transaction status"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_send_evaluator::do_apply(const sidechain_transaction_send_operation &op) +{ try { + const auto &sto_idx = db().get_index_type().indices().get(); + auto sto_obj = sto_idx.find(op.sidechain_transaction_id); + + db().modify(*sto_obj, [&](sidechain_transaction_object &sto) { + sto.sidechain_transaction = op.sidechain_transaction; + sto.status = sidechain_transaction_status::sent; + }); + + return op.sidechain_transaction_id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + + +void_result sidechain_transaction_settle_evaluator::do_evaluate(const sidechain_transaction_settle_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.sidechain_transaction_id); + FC_ASSERT(sto_obj != sto_idx.end(), "Sidechain transaction object not found"); + + FC_ASSERT(sto_obj->status == sidechain_transaction_status::sent, "Invalid transaction status"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_settle_evaluator::do_apply(const sidechain_transaction_settle_operation &op) +{ try { + const auto &sto_idx = db().get_index_type().indices().get(); + auto sto_obj = sto_idx.find(op.sidechain_transaction_id); + + db().modify(*sto_obj, [&](sidechain_transaction_object &sto) { + sto.status = sidechain_transaction_status::settled; + }); + + return op.sidechain_transaction_id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp new file mode 100644 index 00000000..9c48c3e0 --- /dev/null +++ b/libraries/chain/son_evaluator.cpp @@ -0,0 +1,243 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_evaluator::do_evaluate(const son_create_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT(db().get(op.owner_account).is_lifetime_member(), "Only Lifetime members may register a SON."); + FC_ASSERT(op.deposit(db()).policy.which() == vesting_policy::tag::value, + "Deposit balance must have dormant vesting policy"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_evaluator::do_apply(const son_create_operation& op) +{ try { + vote_id_type vote_id; + db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) { + vote_id = get_next_vote_id(p, vote_id_type::son); + }); + + const auto& new_son_object = db().create( [&]( son_object& obj ){ + obj.son_account = op.owner_account; + obj.vote_id = vote_id; + obj.url = op.url; + obj.deposit = op.deposit; + obj.signing_key = op.signing_key; + obj.sidechain_public_keys = op.sidechain_public_keys; + obj.pay_vb = op.pay_vb; + obj.statistics = db().create([&](son_statistics_object& s){s.owner = obj.id;}).id; + }); + return new_son_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_son_evaluator::do_evaluate(const son_update_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_son_evaluator::do_apply(const son_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_object &so) { + if(op.new_url.valid()) so.url = *op.new_url; + if(op.new_deposit.valid()) so.deposit = *op.new_deposit; + if(op.new_signing_key.valid()) so.signing_key = *op.new_signing_key; + if(op.new_sidechain_public_keys.valid()) so.sidechain_public_keys = *op.new_sidechain_public_keys; + if(op.new_pay_vb.valid()) so.pay_vb = *op.new_pay_vb; + }); + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result deregister_son_evaluator::do_evaluate(const son_deregister_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON_HARDFORK"); // can be removed after HF date pass + // Only son account can deregister + FC_ASSERT(db().is_son_dereg_valid(op.son_id) && op.payer == db().get_global_properties().parameters.son_account()); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result deregister_son_evaluator::do_apply(const son_deregister_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + const auto& ss_idx = db().get_index_type().indices().get(); + auto son = idx.find(op.son_id); + if(son != idx.end()) { + vesting_balance_object deposit = son->deposit(db()); + linear_vesting_policy new_vesting_policy; + new_vesting_policy.begin_timestamp = db().head_block_time(); + new_vesting_policy.vesting_cliff_seconds = db().get_global_properties().parameters.son_vesting_period(); + new_vesting_policy.begin_balance = deposit.balance.amount; + + db().modify(son->deposit(db()), [&new_vesting_policy](vesting_balance_object &vbo) { + vbo.policy = new_vesting_policy; + }); + + db().modify(*son, [&op](son_object &so) { + so.status = son_status::deregistered; + }); + + auto stats_obj = ss_idx.find(son->statistics); + if(stats_obj != ss_idx.end()) { + db().modify(*stats_obj, [&]( son_statistics_object& sso) { + sso.deregistered_timestamp = db().head_block_time(); + }); + } + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result son_heartbeat_evaluator::do_evaluate(const son_heartbeat_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + FC_ASSERT( itr != idx.end() ); + FC_ASSERT(itr->son_account == op.owner_account); + auto stats = itr->statistics( db() ); + // Inactive SONs need not send heartbeats + FC_ASSERT((itr->status == son_status::active) || (itr->status == son_status::in_maintenance) || (itr->status == son_status::request_maintenance), "Inactive SONs need not send heartbeats"); + // Account for network delays + fc::time_point_sec min_ts = db().head_block_time() - fc::seconds(5 * db().block_interval()); + // Account for server ntp sync difference + fc::time_point_sec max_ts = db().head_block_time() + fc::seconds(5 * db().block_interval()); + FC_ASSERT(op.ts > stats.last_active_timestamp, "Heartbeat sent without waiting minimum time"); + FC_ASSERT(op.ts > stats.last_down_timestamp, "Heartbeat sent is invalid can't be <= last down timestamp"); + FC_ASSERT(op.ts >= min_ts, "Heartbeat ts is behind the min threshold"); + FC_ASSERT(op.ts <= max_ts, "Heartbeat ts is above the max threshold"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + const global_property_object& gpo = db().get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + + auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), op.son_id); + bool is_son_active = true; + + if(it_son == active_son_ids.end()) { + is_son_active = false; + } + + if(itr->status == son_status::in_maintenance) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.current_interval_downtime += op.ts.sec_since_epoch() - sso.last_down_timestamp.sec_since_epoch(); + sso.last_active_timestamp = op.ts; + } ); + + db().modify(*itr, [&is_son_active](son_object &so) { + if(is_son_active) { + so.status = son_status::active; + } else { + so.status = son_status::inactive; + } + }); + } else if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.last_active_timestamp = op.ts; + } ); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result son_report_down_evaluator::do_evaluate(const son_report_down_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer."); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + auto itr = idx.find(op.son_id); + auto stats = itr->statistics( db() ); + FC_ASSERT(itr->status == son_status::active || itr->status == son_status::request_maintenance, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down"); + FC_ASSERT(op.down_ts >= stats.last_active_timestamp, "down_ts should be greater than last_active_timestamp"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_report_down_evaluator::do_apply(const son_report_down_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.last_down_timestamp = op.down_ts; + }); + + db().modify(*itr, [&op](son_object &so) { + so.status = son_status::in_maintenance; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result son_maintenance_evaluator::do_evaluate(const son_maintenance_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + FC_ASSERT( itr != idx.end() ); + // Inactive SONs can't go to maintenance, toggle between active and request_maintenance states + if(op.request_type == son_maintenance_request_type::request_maintenance) { + FC_ASSERT(itr->status == son_status::active, "Inactive SONs can't request for maintenance"); + } else if(op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + FC_ASSERT(itr->status == son_status::request_maintenance, "Only maintenance requested SONs can cancel the request"); + } else { + FC_ASSERT(false, "Invalid maintenance operation"); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_maintenance_evaluator::do_apply(const son_maintenance_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + if(itr->status == son_status::active && op.request_type == son_maintenance_request_type::request_maintenance) { + db().modify(*itr, [](son_object &so) { + so.status = son_status::request_maintenance; + }); + } else if(itr->status == son_status::request_maintenance && op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + db().modify(*itr, [](son_object &so) { + so.status = son_status::active; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/son_object.cpp b/libraries/chain/son_object.cpp new file mode 100644 index 00000000..bf54c833 --- /dev/null +++ b/libraries/chain/son_object.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace graphene { namespace chain { + void son_object::pay_son_fee(share_type pay, database& db) { + db.adjust_balance(son_account, pay); + } + + bool son_object::has_valid_config()const { + return ((std::string(signing_key).length() > 0) && + (sidechain_public_keys.size() > 0) && + (sidechain_public_keys.find( sidechain_type::bitcoin ) != sidechain_public_keys.end()) && + (sidechain_public_keys.at(sidechain_type::bitcoin).length() > 0)); + } +}} diff --git a/libraries/chain/son_wallet_deposit_evaluator.cpp b/libraries/chain/son_wallet_deposit_evaluator.cpp new file mode 100644 index 00000000..24a87e47 --- /dev/null +++ b/libraries/chain/son_wallet_deposit_evaluator.cpp @@ -0,0 +1,157 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_deposit_create_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + + const auto &son_idx = db().get_index_type().indices().get(); + const auto so = son_idx.find(op.son_id); + FC_ASSERT(so != son_idx.end(), "SON not found"); + FC_ASSERT(so->son_account == op.payer, "Payer is not SON account owner"); + + const auto &ss_idx = db().get_index_type().indices().get(); + FC_ASSERT(ss_idx.find(op.son_id) != ss_idx.end(), "Statistic object for a given SON ID does not exists"); + + const auto &swdo_idx = db().get_index_type().indices().get(); + const auto swdo = swdo_idx.find(op.sidechain_uid); + if (swdo == swdo_idx.end()) { + auto &gpo = db().get_global_properties(); + bool expected = false; + for (auto &si : gpo.active_sons) { + if (op.son_id == si.son_id) { + expected = true; + break; + } + } + FC_ASSERT(expected, "Only active SON can create deposit"); + } else { + bool exactly_the_same = true; + exactly_the_same = exactly_the_same && (swdo->sidechain == op.sidechain); + exactly_the_same = exactly_the_same && (swdo->sidechain_uid == op.sidechain_uid); + exactly_the_same = exactly_the_same && (swdo->sidechain_transaction_id == op.sidechain_transaction_id); + exactly_the_same = exactly_the_same && (swdo->sidechain_from == op.sidechain_from); + exactly_the_same = exactly_the_same && (swdo->sidechain_to == op.sidechain_to); + exactly_the_same = exactly_the_same && (swdo->sidechain_currency == op.sidechain_currency); + exactly_the_same = exactly_the_same && (swdo->sidechain_amount == op.sidechain_amount); + exactly_the_same = exactly_the_same && (swdo->peerplays_from == op.peerplays_from); + exactly_the_same = exactly_the_same && (swdo->peerplays_to == op.peerplays_to); + exactly_the_same = exactly_the_same && (swdo->peerplays_asset == op.peerplays_asset); + FC_ASSERT(exactly_the_same, "Invalid withdraw confirmation"); + + bool expected = false; + for (auto &exp : swdo->expected_reports) { + if (op.son_id == exp.first) { + expected = true; + break; + } + } + FC_ASSERT(expected, "Confirmation from this SON not expected"); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_wallet_deposit_evaluator::do_apply(const son_wallet_deposit_create_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.sidechain_uid); + if (itr == idx.end()) { + const auto& new_son_wallet_deposit_object = db().create( [&]( son_wallet_deposit_object& swdo ){ + swdo.timestamp = op.timestamp; + swdo.block_num = op.block_num; + swdo.sidechain = op.sidechain; + swdo.sidechain_uid = op.sidechain_uid; + swdo.sidechain_transaction_id = op.sidechain_transaction_id; + swdo.sidechain_from = op.sidechain_from; + swdo.sidechain_to = op.sidechain_to; + swdo.sidechain_currency = op.sidechain_currency; + swdo.sidechain_amount = op.sidechain_amount; + swdo.peerplays_from = op.peerplays_from; + swdo.peerplays_to = op.peerplays_to; + swdo.peerplays_asset = op.peerplays_asset; + + auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons) { + swdo.expected_reports.insert(std::make_pair(si.son_id, si.weight)); + + auto stats_itr = db().get_index_type().indices().get().find(si.son_id); + db().modify(*stats_itr, [&op, &si](son_statistics_object &sso) { + sso.total_sidechain_txs_reported = sso.total_sidechain_txs_reported + 1; + if (si.son_id == op.son_id) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + } + }); + } + + swdo.received_reports.insert(op.son_id); + + uint64_t total_weight = 0; + for (const auto exp : swdo.expected_reports) { + total_weight = total_weight + exp.second; + } + uint64_t current_weight = 0; + for (const auto rec : swdo.received_reports) { + current_weight = current_weight + swdo.expected_reports.find(rec)->second; + } + swdo.confirmed = (current_weight > (total_weight * 2 / 3)); + + swdo.processed = false; + }); + return new_son_wallet_deposit_object.id; + } else { + db().modify(*itr, [&op](son_wallet_deposit_object &swdo) { + swdo.received_reports.insert(op.son_id); + + uint64_t total_weight = 0; + for (const auto exp : swdo.expected_reports) { + total_weight = total_weight + exp.second; + } + uint64_t current_weight = 0; + for (const auto rec : swdo.received_reports) { + current_weight = current_weight + swdo.expected_reports.find(rec)->second; + } + swdo.confirmed = (current_weight > (total_weight * 2 / 3)); + }); + auto stats_itr = db().get_index_type().indices().get().find(op.son_id); + db().modify(*stats_itr, [&op](son_statistics_object &sso) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + }); + return (*itr).id; + } +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result process_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_deposit_process_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + FC_ASSERT(db().get_global_properties().active_sons.size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); + + const auto& idx = db().get_index_type().indices().get(); + const auto& itr = idx.find(op.son_wallet_deposit_id); + FC_ASSERT(itr != idx.end(), "Son wallet deposit not found"); + FC_ASSERT(!itr->processed, "Son wallet deposit is already processed"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type process_son_wallet_deposit_evaluator::do_apply(const son_wallet_deposit_process_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_deposit_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_wallet_deposit_object &swdo) { + swdo.processed = true; + }); + } + return op.son_wallet_deposit_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/son_wallet_evaluator.cpp b/libraries/chain/son_wallet_evaluator.cpp new file mode 100644 index 00000000..0baed1cb --- /dev/null +++ b/libraries/chain/son_wallet_evaluator.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include + +namespace graphene { namespace chain { + +void_result recreate_son_wallet_evaluator::do_evaluate(const son_wallet_recreate_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.rbegin(); + if(itr != idx.rend()) + { + // Compare current wallet SONs and to-be lists of active sons + auto cur_wallet_sons = (*itr).sons; + auto new_wallet_sons = op.sons; + + bool son_sets_equal = (cur_wallet_sons.size() == new_wallet_sons.size()); + if (son_sets_equal) { + for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { + son_sets_equal = son_sets_equal && cur_wallet_sons.at(i) == new_wallet_sons.at(i); + } + } + + FC_ASSERT(son_sets_equal == false, "Wallet recreation not needed, active SONs set is not changed."); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type recreate_son_wallet_evaluator::do_apply(const son_wallet_recreate_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.rbegin(); + if(itr != idx.rend()) + { + db().modify(*itr, [&, op](son_wallet_object &swo) { + swo.expires = db().head_block_time(); + }); + } + + const auto& new_son_wallet_object = db().create( [&]( son_wallet_object& obj ){ + obj.valid_from = db().head_block_time(); + obj.expires = time_point_sec::maximum(); + obj.sons = op.sons; + }); + return new_son_wallet_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_son_wallet_evaluator::do_evaluate(const son_wallet_update_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_wallet_id) != idx.end() ); + //auto itr = idx.find(op.son_wallet_id); + //FC_ASSERT( itr->addresses.find(op.sidechain) == itr->addresses.end() || + // itr->addresses.at(op.sidechain).empty(), "Sidechain wallet address already set"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_son_wallet_evaluator::do_apply(const son_wallet_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_id); + if (itr != idx.end()) + { + if (itr->addresses.find(op.sidechain) == itr->addresses.end()) { + db().modify(*itr, [&op](son_wallet_object &swo) { + swo.addresses[op.sidechain] = op.address; + }); + } + } + return op.son_wallet_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/son_wallet_withdraw_evaluator.cpp b/libraries/chain/son_wallet_withdraw_evaluator.cpp new file mode 100644 index 00000000..bf6adaf9 --- /dev/null +++ b/libraries/chain/son_wallet_withdraw_evaluator.cpp @@ -0,0 +1,155 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_create_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + + const auto &son_idx = db().get_index_type().indices().get(); + const auto so = son_idx.find(op.son_id); + FC_ASSERT(so != son_idx.end(), "SON not found"); + FC_ASSERT(so->son_account == op.payer, "Payer is not SON account owner"); + + const auto &ss_idx = db().get_index_type().indices().get(); + FC_ASSERT(ss_idx.find(op.son_id) != ss_idx.end(), "Statistic object for a given SON ID does not exists"); + + const auto &swwo_idx = db().get_index_type().indices().get(); + const auto swwo = swwo_idx.find(op.peerplays_uid); + if (swwo == swwo_idx.end()) { + auto &gpo = db().get_global_properties(); + bool expected = false; + for (auto &si : gpo.active_sons) { + if (op.son_id == si.son_id) { + expected = true; + break; + } + } + FC_ASSERT(expected, "Only active SON can create deposit"); + } else { + bool exactly_the_same = true; + exactly_the_same = exactly_the_same && (swwo->sidechain == op.sidechain); + exactly_the_same = exactly_the_same && (swwo->peerplays_uid == op.peerplays_uid); + exactly_the_same = exactly_the_same && (swwo->peerplays_transaction_id == op.peerplays_transaction_id); + exactly_the_same = exactly_the_same && (swwo->peerplays_from == op.peerplays_from); + exactly_the_same = exactly_the_same && (swwo->peerplays_asset == op.peerplays_asset); + exactly_the_same = exactly_the_same && (swwo->withdraw_sidechain == op.withdraw_sidechain); + exactly_the_same = exactly_the_same && (swwo->withdraw_address == op.withdraw_address); + exactly_the_same = exactly_the_same && (swwo->withdraw_currency == op.withdraw_currency); + exactly_the_same = exactly_the_same && (swwo->withdraw_amount == op.withdraw_amount); + FC_ASSERT(exactly_the_same, "Invalid withdraw confirmation"); + + bool expected = false; + for (auto &exp : swwo->expected_reports) { + if (op.son_id == exp.first) { + expected = true; + break; + } + } + FC_ASSERT(expected, "Confirmation from this SON not expected"); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_wallet_withdraw_evaluator::do_apply(const son_wallet_withdraw_create_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.peerplays_uid); + if (itr == idx.end()) { + const auto& new_son_wallet_withdraw_object = db().create( [&]( son_wallet_withdraw_object& swwo ){ + swwo.timestamp = op.timestamp; + swwo.block_num = op.block_num; + swwo.sidechain = op.sidechain; + swwo.peerplays_uid = op.peerplays_uid; + swwo.peerplays_transaction_id = op.peerplays_transaction_id; + swwo.peerplays_from = op.peerplays_from; + swwo.peerplays_asset = op.peerplays_asset; + swwo.withdraw_sidechain = op.withdraw_sidechain; + swwo.withdraw_address = op.withdraw_address; + swwo.withdraw_currency = op.withdraw_currency; + swwo.withdraw_amount = op.withdraw_amount; + + auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons) { + swwo.expected_reports.insert(std::make_pair(si.son_id, si.weight)); + + auto stats_itr = db().get_index_type().indices().get().find(si.son_id); + db().modify(*stats_itr, [&op, &si](son_statistics_object &sso) { + sso.total_sidechain_txs_reported = sso.total_sidechain_txs_reported + 1; + if (si.son_id == op.son_id) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + } + }); + } + + swwo.received_reports.insert(op.son_id); + + uint64_t total_weight = 0; + for (const auto exp : swwo.expected_reports) { + total_weight = total_weight + exp.second; + } + uint64_t current_weight = 0; + for (const auto rec : swwo.received_reports) { + current_weight = current_weight + swwo.expected_reports.find(rec)->second; + } + swwo.confirmed = (current_weight > (total_weight * 2 / 3)); + + swwo.processed = false; + }); + return new_son_wallet_withdraw_object.id; + } else { + db().modify(*itr, [&op](son_wallet_withdraw_object &swwo) { + swwo.received_reports.insert(op.son_id); + + uint64_t total_weight = 0; + for (const auto exp : swwo.expected_reports) { + total_weight = total_weight + exp.second; + } + uint64_t current_weight = 0; + for (const auto rec : swwo.received_reports) { + current_weight = current_weight + swwo.expected_reports.find(rec)->second; + } + swwo.confirmed = (current_weight > (total_weight * 2 / 3)); + }); + auto stats_itr = db().get_index_type().indices().get().find(op.son_id); + db().modify(*stats_itr, [&op](son_statistics_object &sso) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + }); + return (*itr).id; + } +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result process_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_process_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + FC_ASSERT(db().get_global_properties().active_sons.size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); + + const auto& idx = db().get_index_type().indices().get(); + const auto& itr = idx.find(op.son_wallet_withdraw_id); + FC_ASSERT(itr != idx.end(), "Son wallet withdraw not found"); + FC_ASSERT(!itr->processed, "Son wallet withdraw is already processed"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type process_son_wallet_withdraw_evaluator::do_apply(const son_wallet_withdraw_process_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_withdraw_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_wallet_withdraw_object &swwo) { + swwo.processed = true; + }); + } + return op.son_wallet_withdraw_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index e81383b6..56eef193 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -42,6 +42,9 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount ); FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() ); + if(d.head_block_time() >= HARDFORK_SON_TIME && op.balance_type == vesting_balance_type::son) // Todo: hf check can be removed after pass + FC_ASSERT( op.amount.amount >= d.get_global_properties().parameters.son_vesting_amount() ); + if(d.head_block_time() < HARDFORK_GPOS_TIME) // Todo: can be removed after gpos hf time pass FC_ASSERT( op.balance_type == vesting_balance_type::normal); @@ -79,6 +82,11 @@ struct init_policy_visitor policy.coin_seconds_earned_last_update = now; p = policy; } + void operator()( const dormant_vesting_policy_initializer& i )const + { + dormant_vesting_policy policy; + p = policy; + } }; object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op ) @@ -110,8 +118,6 @@ object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance } obj.balance_type = op.balance_type; } ); - - return vbo.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -133,7 +139,7 @@ void_result vesting_balance_withdraw_evaluator::do_evaluate( const vesting_balan const time_point_sec now = d.head_block_time(); const vesting_balance_object& vbo = op.vesting_balance( d ); - if(vbo.balance_type == vesting_balance_type::normal) + if(vbo.balance_type == vesting_balance_type::normal || vbo.balance_type == vesting_balance_type::son) { FC_ASSERT( op.owner == vbo.owner, "", ("op.owner", op.owner)("vbo.owner", vbo.owner) ); FC_ASSERT( vbo.is_withdraw_allowed( now, op.amount ), "Account has insufficient ${balance_type} Vested Balance to withdraw", @@ -173,7 +179,7 @@ void_result vesting_balance_withdraw_evaluator::do_apply( const vesting_balance_ //Handling all GPOS withdrawls separately from normal and SONs(future extension). // One request/transaction would be sufficient to withdraw from multiple vesting balance ids const vesting_balance_object& vbo = op.vesting_balance( d ); - if(vbo.balance_type == vesting_balance_type::normal) + if(vbo.balance_type == vesting_balance_type::normal || vbo.balance_type == vesting_balance_type::son) { // Allow zero balance objects to stick around, (1) to comply // with the chain's "objects live forever" design principle, (2) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 4674c974..3334d4f6 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -169,6 +169,33 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c return (ctx.amount <= get_allowed_withdraw(ctx)); } +asset dormant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +{ + share_type allowed_withdraw = 0; + return asset( allowed_withdraw, ctx.balance.asset_id ); +} + +void dormant_vesting_policy::on_deposit(const vesting_policy_context& ctx) +{ +} + +bool dormant_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && sum_below_max_shares(ctx.amount, ctx.balance); +} + +void dormant_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ +} + +bool dormant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && (ctx.amount <= get_allowed_withdraw(ctx)); +} + + #define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ diff --git a/libraries/fc b/libraries/fc index 9fa98d9a..fb27454c 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 9fa98d9a93ada3e84f48812b5c42b053b0dbf9da +Subproject commit fb27454cdf1626e5e108e42848bd1bcfe60c9540 diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 58728a9e..d2a5be16 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -10,4 +10,5 @@ add_subdirectory( generate_genesis ) add_subdirectory( generate_uia_sharedrop_genesis ) add_subdirectory( debug_witness ) add_subdirectory( snapshot ) +add_subdirectory( peerplays_sidechain ) add_subdirectory( es_objects ) diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 67322f80..d1aa2f71 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -123,12 +124,15 @@ void account_history_plugin_impl::update_account_histories( const signed_block& // get the set of accounts this operation applies to flat_set impacted; vector other; - operation_get_required_authorities( op.op, impacted, impacted, other ); // fee_payer is added here + // fee payer is added here + operation_get_required_authorities( op.op, impacted, impacted, other, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); if( op.op.which() == operation::tag< account_create_operation >::value ) impacted.insert( op.result.get() ); else - graphene::chain::operation_get_impacted_accounts( op.op, impacted ); + graphene::chain::operation_get_impacted_accounts( op.op, impacted, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(db.head_block_time()) ); if( op.op.which() == operation::tag< lottery_end_operation >::value ) { auto lop = op.op.get< lottery_end_operation >(); diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index d49129b0..3eadda34 100644 --- a/libraries/plugins/delayed_node/delayed_node_plugin.cpp +++ b/libraries/plugins/delayed_node/delayed_node_plugin.cpp @@ -65,7 +65,7 @@ void delayed_node_plugin::plugin_set_program_options(bpo::options_description& c void delayed_node_plugin::connect() { - my->client_connection = std::make_shared(*my->client.connect(my->remote_endpoint), GRAPHENE_MAX_NESTED_OBJECTS); + my->client_connection = std::make_shared(my->client.connect(my->remote_endpoint), GRAPHENE_MAX_NESTED_OBJECTS); my->database_api = my->client_connection->get_remote_api(0); my->client_connection_closed = my->client_connection->closed.connect([this] { connection_failed(); diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index 484aef9c..dc167b05 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include namespace graphene { namespace elasticsearch { @@ -157,12 +157,15 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b // get the set of accounts this operation applies to flat_set impacted; vector other; - operation_get_required_authorities( op.op, impacted, impacted, other ); // fee_payer is added here + // fee_payer is added here + operation_get_required_authorities( op.op, impacted, impacted, other, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); if( op.op.which() == operation::tag< account_create_operation >::value ) impacted.insert( op.result.get() ); else - graphene::chain::operation_get_impacted_accounts( op.op, impacted ); + operation_get_impacted_accounts( op.op, impacted, + MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); for( auto& a : other ) for( auto& item : a.account_auths ) diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt new file mode 100755 index 00000000..3e37b6fe --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -0,0 +1,51 @@ +file(GLOB_RECURSE HEADERS "include/graphene/peerplays_sidechain/*.hpp") + +add_library( peerplays_sidechain + peerplays_sidechain_plugin.cpp + sidechain_net_manager.cpp + sidechain_net_handler.cpp + sidechain_net_handler_bitcoin.cpp + sidechain_net_handler_peerplays.cpp + bitcoin/bech32.cpp + bitcoin/bitcoin_address.cpp + bitcoin/bitcoin_script.cpp + bitcoin/bitcoin_transaction.cpp + bitcoin/segwit_addr.cpp + bitcoin/utils.cpp + bitcoin/sign_bitcoin_transaction.cpp + ) + +if (ENABLE_DEV_FEATURES) + set(ENABLE_MULTIPLE_SONS 1) + set(ENABLE_PEERPLAYS_ASSET_DEPOSITS 1) +endif() +unset(ENABLE_DEV_FEATURES) +unset(ENABLE_DEV_FEATURES CACHE) + +if (ENABLE_MULTIPLE_SONS) + message ("Multiple SONs per software instance are supported") + target_compile_definitions(peerplays_sidechain PRIVATE ENABLE_MULTIPLE_SONS) +endif() +unset(ENABLE_MULTIPLE_SONS) +unset(ENABLE_MULTIPLE_SONS CACHE) + +if (ENABLE_PEERPLAYS_ASSET_DEPOSITS) + message ("Depositing Peerplays assets enabled") + target_compile_definitions(peerplays_sidechain PRIVATE ENABLE_PEERPLAYS_ASSET_DEPOSITS) +endif() +unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS) +unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS CACHE) + +target_link_libraries( peerplays_sidechain graphene_chain graphene_app fc zmq ) +target_include_directories( peerplays_sidechain + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +install( TARGETS + peerplays_sidechain + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/peerplays_sidechain" ) + diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp new file mode 100644 index 00000000..69d157e4 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bech32.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +// #include + +namespace { + +typedef std::vector data; + +/** The Bech32 character set for encoding. */ +const char *CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** The Bech32 character set for decoding. */ +const int8_t CHARSET_REV[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; + +/** Concatenate two byte arrays. */ +data Cat(data x, const data &y) { + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to + * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher + * bits correspond to earlier values. */ +uint32_t PolyMod(const data &v) { + // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an + // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = + // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that + // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. + + // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of + // v(x) mod g(x), where g(x) is the Bech32 generator, + // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way + // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a + // window of 1023 characters. Among the various possible BCH codes, one was selected to in + // fact guarantee detection of up to 4 errors within a window of 89 characters. + + // Note that the coefficients are elements of GF(32), here represented as decimal numbers + // between {}. In this finite field, addition is just XOR of the corresponding numbers. For + // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires + // treating the bits of values themselves as coefficients of a polynomial over a smaller field, + // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = + // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a + // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + + // During the course of the loop below, `c` contains the bitpacked coefficients of the + // polynomial constructed from just the values of v that were processed so far, mod g(x). In + // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value + // for `c`. + uint32_t c = 1; + for (auto v_i : v) { + // We want to update `c` to correspond to a polynomial with one extra term. If the initial + // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to + // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to + // process. Simplifying: + // c'(x) = (f(x) * x + v_i) mod g(x) + // ((f(x) mod g(x)) * x + v_i) mod g(x) + // (c(x) * x + v_i) mod g(x) + // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute + // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) + // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) + // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i + // If we call (x^6 mod g(x)) = k(x), this can be written as + // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) + + // First, determine the value of c0: + uint8_t c0 = c >> 25; + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: + c = ((c & 0x1ffffff) << 5) ^ v_i; + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 1) + c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18} + if (c0 & 2) + c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13} + if (c0 & 4) + c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26} + if (c0 & 8) + c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29} + if (c0 & 16) + c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19} + } + return c; +} + +/** Convert to lower case. */ +inline unsigned char LowerCase(unsigned char c) { + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; +} + +/** Expand a HRP for use in checksum computation. */ +data ExpandHRP(const std::string &hrp) { + data ret; + ret.reserve(hrp.size() + 90); + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; +} + +/** Verify a checksum. */ +bool VerifyChecksum(const std::string &hrp, const data &values) { + // PolyMod computes what value to xor into the final values to make the checksum 0. However, + // if we required that the checksum was 0, it would be the case that appending a 0 to a valid + // list of values would result in a new valid list. For that reason, Bech32 requires the + // resulting checksum to be 1 instead. + return PolyMod(Cat(ExpandHRP(hrp), values)) == 1; +} + +/** Create a checksum. */ +data CreateChecksum(const std::string &hrp, const data &values) { + data enc = Cat(ExpandHRP(hrp), values); + enc.resize(enc.size() + 6); // Append 6 zeroes + uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + data ret(6); + for (size_t i = 0; i < 6; ++i) { + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +} // namespace + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace bech32 { + +/** Encode a Bech32 string. */ +std::string Encode(const std::string &hrp, const data &values) { + data checksum = CreateChecksum(hrp, values); + data combined = Cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (auto c : combined) { + ret += CHARSET[c]; + } + return ret; +} + +/** Decode a Bech32 string. */ +std::pair Decode(const std::string &str) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c < 33 || c > 126) + return {}; + if (c >= 'a' && c <= 'z') + lower = true; + if (c >= 'A' && c <= 'Z') + upper = true; + } + if (lower && upper) + return {}; + size_t pos = str.rfind('1'); + if (str.size() > 90 || pos == str.npos || pos == 0 || pos + 7 > str.size()) { + return {}; + } + data values(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + int8_t rev = (c < 33 || c > 126) ? -1 : CHARSET_REV[c]; + if (rev == -1) { + return {}; + } + values[i] = rev; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += LowerCase(str[i]); + } + if (!VerifyChecksum(hrp, values)) { + return {}; + } + return {hrp, data(values.begin(), values.end() - 6)}; +} + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::bech32 diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp new file mode 100644 index 00000000..4c10899f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +bool bitcoin_address::operator==(const bitcoin_address &btc_addr) const { + return (this->address == btc_addr.address) && + (this->type == btc_addr.type) && + (this->raw_address == btc_addr.raw_address) && + (this->network_type == btc_addr.network_type); +} + +bytes bitcoin_address::get_script() const { + switch (type) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << raw_address; + case payment_type::P2PK: + return script_builder() << raw_address << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << raw_address << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << raw_address << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << raw_address; + default: + return raw_address; + } +} + +payment_type bitcoin_address::determine_type() { + if (is_p2pk()) { + return payment_type::P2PK; + } else if (is_p2wpkh()) { + return payment_type::P2WPKH; + } else if (is_p2wsh()) { + return payment_type::P2WSH; + } else if (is_p2pkh()) { + return payment_type::P2PKH; + } else if (is_p2sh()) { + return payment_type::P2SH; + } else { + return payment_type::NULLDATA; + } +} + +bytes bitcoin_address::determine_raw_address() { + bytes result; + switch (type) { + case payment_type::P2PK: { + result = parse_hex(address); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + std::string prefix(address.compare(0, 4, "bcrt") == 0 ? std::string(address.begin(), address.begin() + 4) : std::string(address.begin(), address.begin() + 2)); + const auto &decode_bech32 = segwit_addr::decode(prefix, address); + result = bytes(decode_bech32.second.begin(), decode_bech32.second.end()); + break; + } + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + case payment_type::P2PKH: + case payment_type::P2SH: { + bytes hex_addr = fc::from_base58(address); + result = bytes(hex_addr.begin() + 1, hex_addr.begin() + 21); + break; + } + case payment_type::NULLDATA: + return result; + } + return result; +} + +bool bitcoin_address::check_segwit_address(const size_segwit_address &size) const { + if (!address.compare(0, 4, "bcrt") || !address.compare(0, 2, "bc") || !address.compare(0, 2, "tb")) { + std::string prefix(!address.compare(0, 4, "bcrt") ? std::string(address.begin(), address.begin() + 4) : std::string(address.begin(), address.begin() + 2)); + + const auto &decode_bech32 = segwit_addr::decode(prefix, address); + + if (decode_bech32.first == -1 || decode_bech32.second.size() != size) { + return false; + } + + return true; + } + return false; +} + +bool bitcoin_address::is_p2pk() const { + try { + bool prefix = !address.compare(0, 2, "02") || !address.compare(0, 2, "03"); + if (address.size() == 66 && prefix) { + parse_hex(address); + return true; + } + } catch (fc::exception e) { + return false; + } + return false; +} + +bool bitcoin_address::is_p2wpkh() const { + return check_segwit_address(size_segwit_address::P2WPKH); +} + +bool bitcoin_address::is_p2wsh() const { + return check_segwit_address(size_segwit_address::P2WSH); +} + +bool bitcoin_address::is_p2pkh() const { + try { + bytes hex_addr = fc::from_base58(address); + if (hex_addr.size() == 25 && (static_cast(hex_addr[0]) == 0x00 || + static_cast(hex_addr[0]) == 0x6f)) { + return true; + } + return false; + } catch (fc::exception e) { + return false; + } +} + +bool bitcoin_address::is_p2sh() const { + try { + bytes hex_addr = fc::from_base58(address); + if (hex_addr.size() == 25 && (static_cast(hex_addr[0]) == 0x05 || + static_cast(hex_addr[0]) == 0xc4)) { + return true; + } + return false; + } catch (fc::exception e) { + return false; + } +} + +btc_multisig_address::btc_multisig_address(const size_t n_required, const accounts_keys &keys) : + keys_required(n_required), + witnesses_keys(keys) { + create_redeem_script(); + create_address(); + type = payment_type::P2SH; +} + +size_t btc_multisig_address::count_intersection(const accounts_keys &keys) const { + FC_ASSERT(keys.size() > 0); + + int intersections_count = 0; + for (auto &key : keys) { + auto witness_key = witnesses_keys.find(key.first); + if (witness_key == witnesses_keys.end()) + continue; + if (key.second == witness_key->second) + intersections_count++; + } + return intersections_count; +} + +void btc_multisig_address::create_redeem_script() { + FC_ASSERT(keys_required > 0); + FC_ASSERT(keys_required < witnesses_keys.size()); + redeem_script.clear(); + redeem_script.push_back(op_num[keys_required - 1]); + for (const auto &key : witnesses_keys) { + std::stringstream ss; + ss << std::hex << key.second.key_data.size(); + auto key_size_hex = parse_hex(ss.str()); + redeem_script.insert(redeem_script.end(), key_size_hex.begin(), key_size_hex.end()); + redeem_script.insert(redeem_script.end(), key.second.key_data.begin(), key.second.key_data.end()); + } + redeem_script.push_back(op_num[witnesses_keys.size() - 1]); + redeem_script.push_back(OP_CHECKMULTISIG); +} + +void btc_multisig_address::create_address() { + FC_ASSERT(redeem_script.size() > 0); + raw_address.clear(); + fc::sha256 hash256 = fc::sha256::hash(redeem_script.data(), redeem_script.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + bytes temp_addr_hash(parse_hex(hash160.str())); + + raw_address.push_back(OP_HASH160); + std::stringstream ss; + ss << std::hex << temp_addr_hash.size(); + auto address_size_hex = parse_hex(ss.str()); + raw_address.insert(raw_address.end(), address_size_hex.begin(), address_size_hex.end()); + raw_address.insert(raw_address.end(), temp_addr_hash.begin(), temp_addr_hash.end()); + raw_address.push_back(OP_EQUAL); +} + +btc_multisig_segwit_address::btc_multisig_segwit_address(const size_t n_required, const accounts_keys &keys) : + btc_multisig_address(n_required, keys) { + create_witness_script(); + create_segwit_address(); + type = payment_type::P2SH; +} + +bool btc_multisig_segwit_address::operator==(const btc_multisig_segwit_address &addr) const { + if (address != addr.address || redeem_script != addr.redeem_script || + witnesses_keys != addr.witnesses_keys || witness_script != addr.witness_script || + raw_address != addr.raw_address) + return false; + return true; +} + +std::vector btc_multisig_segwit_address::get_keys() { + std::vector keys; + for (const auto &k : witnesses_keys) { + keys.push_back(k.second); + } + return keys; +} + +void btc_multisig_segwit_address::create_witness_script() { + const auto redeem_sha256 = fc::sha256::hash(redeem_script.data(), redeem_script.size()); + witness_script.push_back(OP_0); + witness_script.push_back(0x20); // PUSH_32 + witness_script.insert(witness_script.end(), redeem_sha256.data(), redeem_sha256.data() + redeem_sha256.data_size()); +} + +void btc_multisig_segwit_address::create_segwit_address() { + fc::sha256 hash256 = fc::sha256::hash(witness_script.data(), witness_script.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + + raw_address = bytes(hash160.data(), hash160.data() + hash160.data_size()); + address = fc::to_base58(get_address_bytes(raw_address)); +} + +bytes btc_multisig_segwit_address::get_address_bytes(const bytes &script_hash) { + bytes address_bytes(1, TESTNET_SCRIPT); // 1 byte version + address_bytes.insert(address_bytes.end(), script_hash.begin(), script_hash.end()); + fc::sha256 hash256 = fc::sha256::hash(fc::sha256::hash(address_bytes.data(), address_bytes.size())); + address_bytes.insert(address_bytes.end(), hash256.data(), hash256.data() + 4); // 4 byte checksum + + return address_bytes; +} + +btc_weighted_multisig_address::btc_weighted_multisig_address(const std::vector> &keys_data, + network ntype) { + network_type = ntype; + create_redeem_script(keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} + +void btc_weighted_multisig_address::create_redeem_script(const std::vector> &keys_data) { + script_builder builder; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = total_weight * 2 / 3 + 1; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + + redeem_script_ = builder; + + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + +void btc_weighted_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + + witness_script_ = builder; +} + +void btc_weighted_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +btc_one_or_m_of_n_multisig_address::btc_one_or_m_of_n_multisig_address(const fc::ecc::public_key &user_key_data, + const uint8_t nrequired, const std::vector &keys_data, + network ntype) { + network_type = ntype; + create_redeem_script(user_key_data, nrequired, keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} +void btc_one_or_m_of_n_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, + const uint8_t nrequired, const std::vector &keys_data) { + script_builder builder; + builder << op::IF; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::ELSE; + builder << static_cast(nrequired); + for (auto &key : keys_data) { + builder << key.serialize(); + } + builder << static_cast(keys_data.size()); + builder << op::CHECKMULTISIG; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} +void btc_one_or_m_of_n_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + witness_script_ = builder; +} +void btc_one_or_m_of_n_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +btc_one_or_weighted_multisig_address::btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, + const std::vector> &keys_data, + bitcoin_address::network ntype) { + network_type = ntype; + create_redeem_script(user_key_data, keys_data); + create_witness_script(); + create_segwit_address(); + type = payment_type::P2WSH; +} + +void btc_one_or_weighted_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data) { + script_builder builder; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << op::_1; + builder << op::ELSE; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = total_weight * 2 / 3 + 1; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + +void btc_one_or_weighted_multisig_address::create_witness_script() { + script_builder builder; + builder << op::_0; + builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); + witness_script_ = builder; +} + +void btc_one_or_weighted_multisig_address::create_segwit_address() { + std::string hrp; + switch (network_type) { + case (network::mainnet): + hrp = "bc"; + break; + case (network::testnet): + hrp = "tb"; + break; + case (network::regtest): + hrp = "bcrt"; + break; + } + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); +} + +btc_timelocked_one_or_weighted_multisig_address::btc_timelocked_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, uint32_t latency, const std::vector> &keys_data, bitcoin_address::network ntype) : + btc_one_or_weighted_multisig_address(user_key_data, keys_data, ntype), + latency_(latency) { + create_redeem_script(user_key_data, keys_data); + create_witness_script(); + create_segwit_address(); +} + +void btc_timelocked_one_or_weighted_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data) { + script_builder builder; + builder << user_key_data.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(latency_); + builder << op::CHECKSEQUENCEVERIFY; + builder << op::DROP; + builder << op::_1; + builder << op::ELSE; + uint32_t total_weight = 0; + builder << uint32_t(0); + for (auto &p : keys_data) { + total_weight += p.second; + builder << op::SWAP; + builder << p.first.serialize(); + builder << op::CHECKSIG; + builder << op::IF; + builder << uint32_t(p.second); + builder << op::ADD; + builder << op::ENDIF; + } + uint32_t threshold_weight = total_weight * 2 / 3 + 1; + builder << threshold_weight; + builder << op::GREATERTHANOREQUAL; + builder << op::ENDIF; + redeem_script_ = builder; + fc::sha256 sh = fc::sha256::hash(redeem_script_); + raw_address = bytes(sh.data(), sh.data() + sh.data_size()); +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp new file mode 100644 index 00000000..07078147 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_script.cpp @@ -0,0 +1,65 @@ +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +script_builder &script_builder::operator<<(op opcode) { + const auto op_byte = static_cast(opcode); + if (op_byte < 0 || op_byte > 0xff) + throw std::runtime_error("script_builder::operator<<(OP): invalid opcode"); + script.push_back(op_byte); + return *this; +} + +script_builder &script_builder::operator<<(uint32_t number) { + if (number == 0) { + script.push_back(static_cast(op::_0)); + } else if (number <= 16) { + script.push_back(static_cast(op::_1) + number - 1); + } else { + bytes pack_buf; + while (number) { + pack_buf.push_back(number & 0xff); + number >>= 8; + } + // - If the most significant byte is >= 0x80 and the value is positive, push a + // new zero-byte to make the significant byte < 0x80 again. So, the result can + // be 5 bytes max. + if (pack_buf.back() & 0x80) + pack_buf.push_back(0); + *this << pack_buf; + } + + return *this; +} + +script_builder &script_builder::operator<<(size_t size) { + write_compact_size(script, size); + return *this; +} + +script_builder &script_builder::operator<<(const bytes &sc) { + write_compact_size(script, sc.size()); + script.insert(script.end(), sc.begin(), sc.end()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::sha256 &hash) { + write_compact_size(script, hash.data_size()); + script.insert(script.end(), hash.data(), hash.data() + hash.data_size()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::ripemd160 &hash) { + write_compact_size(script, hash.data_size()); + script.insert(script.end(), hash.data(), hash.data() + hash.data_size()); + return *this; +} + +script_builder &script_builder::operator<<(const fc::ecc::public_key_data &pubkey_data) { + write_compact_size(script, pubkey_data.size()); + script.insert(script.end(), pubkey_data.begin(), pubkey_data.begin() + pubkey_data.size()); + return *this; +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp new file mode 100644 index 00000000..b4fde6dc --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_transaction.cpp @@ -0,0 +1,257 @@ +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +bool out_point::operator==(const out_point &op) const { + if (this->hash == op.hash && + this->n == op.n) { + return true; + } + return false; +} + +bool tx_in::operator==(const tx_in &ti) const { + if (this->prevout == ti.prevout && + this->scriptSig == ti.scriptSig && + this->nSequence == ti.nSequence) { + return true; + } + return false; +} + +bool tx_out::operator==(const tx_out &to) const { + if (this->value == to.value && + this->scriptPubKey == to.scriptPubKey) { + return true; + } + return false; +} + +bool tx_out::is_p2wsh() const { + if (scriptPubKey.size() == 34 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x20)) { + return true; + } + return false; +} + +bool tx_out::is_p2wpkh() const { + if (scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x14)) { + return true; + } + return false; +} + +bool tx_out::is_p2pkh() const { + if (scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast(0x76) && scriptPubKey[1] == static_cast(0xa9) && + scriptPubKey[2] == static_cast(0x14) && scriptPubKey[23] == static_cast(0x88) && scriptPubKey[24] == static_cast(0xac)) { + return true; + } + return false; +} + +bool tx_out::is_p2sh() const { + if (scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast(0xa9) && + scriptPubKey[1] == static_cast(0x14) && scriptPubKey[22] == static_cast(0x87)) { + return true; + } + return false; +} + +bool tx_out::is_p2pk() const { + if (scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast(0x21) && scriptPubKey[34] == static_cast(0xac)) { + return true; + } + return false; +} + +bytes tx_out::get_data_or_script() const { + if (is_p2pkh()) { + return bytes(scriptPubKey.begin() + 3, scriptPubKey.begin() + 23); + } else if (is_p2sh() || is_p2wpkh()) { + return bytes(scriptPubKey.begin() + 2, scriptPubKey.begin() + 22); + } else if (is_p2wsh()) { + return bytes(scriptPubKey.begin() + 2, scriptPubKey.begin() + 34); + } else if (is_p2pk()) { + return bytes(scriptPubKey.begin() + 1, scriptPubKey.begin() + 34); + } + return scriptPubKey; +} + +bool bitcoin_transaction::operator!=(const bitcoin_transaction &bt) const { + if (this->nVersion != bt.nVersion || + this->vin != bt.vin || + this->vout != bt.vout || + this->nLockTime != bt.nLockTime) { + return true; + } + return false; +} + +fc::sha256 bitcoin_transaction::get_hash() const { + const auto bytes = pack(*this, true); + const auto hash = fc::sha256::hash(fc::sha256::hash(bytes.data(), bytes.size())); + std::reverse(hash.data(), hash.data() + hash.data_size()); + return hash; +} + +fc::sha256 bitcoin_transaction::get_txid() const { + const auto bytes = pack(*this, false); + const auto hash = fc::sha256::hash(fc::sha256::hash(bytes.data(), bytes.size())); + std::reverse(hash.data(), hash.data() + hash.data_size()); + return hash; +} + +size_t bitcoin_transaction::get_vsize() const { + static const auto witness_scale_factor = 4; + + fc::datastream no_wit_ds; + pack(no_wit_ds, *this, false); + + fc::datastream wit_ds; + pack(wit_ds, *this, true); + + const size_t weight = no_wit_ds.tellp() * (witness_scale_factor - 1) + wit_ds.tellp(); + const size_t vsize = (weight + witness_scale_factor - 1) / witness_scale_factor; + + return vsize; +} + +void bitcoin_transaction_builder::set_version(int32_t version) { + tx.nVersion = version; +} + +void bitcoin_transaction_builder::set_locktime(uint32_t lock_time) { + tx.nLockTime = lock_time; +} + +void bitcoin_transaction_builder::add_in(const fc::sha256 &txid, uint32_t n_out, const bytes &script_code, bool front, uint32_t sequence) { + add_in(payment_type::P2WSH, txid, n_out, script_code, front, sequence); +} + +void bitcoin_transaction_builder::add_in(payment_type type, const fc::sha256 &txid, uint32_t n_out, const bytes &script_code, bool front, uint32_t sequence) { + out_point prevout; + prevout.hash = txid; + prevout.n = n_out; + + tx_in txin; + txin.prevout = prevout; + txin.nSequence = sequence; + + add_in(type, txin, script_code, front); +} + +void bitcoin_transaction_builder::add_in(payment_type type, tx_in txin, const bytes &script_code, bool front) { + switch (type) { + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + FC_ASSERT(script_code != bytes()); + txin.scriptSig = script_code; + break; + default: { + if (txin.prevout.hash == fc::sha256("0000000000000000000000000000000000000000000000000000000000000000")) { //coinbase + FC_ASSERT(script_code != bytes()); + txin.scriptSig = script_code; + } + break; + } + } + + if (front) { + tx.vin.insert(tx.vin.begin(), txin); + } else { + tx.vin.push_back(txin); + } +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const std::string &base58_address, bool front) { + // TODO: add checks + const auto address_bytes = fc::from_base58(base58_address); + add_out(type, amount, bytes(address_bytes.begin() + 1, address_bytes.begin() + 1 + 20), front); +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const fc::ecc::public_key_data &pubkey, bool front) { + FC_ASSERT(is_payment_to_pubkey(type)); + + if (type == payment_type::P2PK) { + const auto pubkey_bytes = bytes(pubkey.begin(), pubkey.begin() + pubkey.size()); + add_out(type, amount, pubkey_bytes, front); + } else { + const auto hash256 = fc::sha256::hash(pubkey.begin(), pubkey.size()); + const auto hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + add_out(type, amount, bytes(hash160.data(), hash160.data() + hash160.data_size()), front); + } +} + +void bitcoin_transaction_builder::add_out(payment_type type, int64_t amount, const bytes &script_code, bool front) { + tx_out out; + out.value = amount; + out.scriptPubKey = get_script_pubkey(type, script_code); + + if (front) { + tx.vout.insert(tx.vout.begin(), out); + } else { + tx.vout.push_back(out); + } +} + +void bitcoin_transaction_builder::add_out_all_type(const uint64_t &amount, const bitcoin_address &address, bool front) { + switch (address.get_type()) { + case payment_type::P2PK: { + bytes raw_address(address.get_raw_address()); + fc::ecc::public_key_data public_key; + std::copy(raw_address.begin(), raw_address.end(), public_key.begin()); + add_out(address.get_type(), amount, public_key, front); + break; + } + case payment_type::P2PKH: + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: { + add_out(address.get_type(), amount, address.get_address(), front); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + add_out(address.get_type(), amount, address.get_raw_address(), front); + break; + } + default: + break; + } +} + +inline bool bitcoin_transaction_builder::is_payment_to_pubkey(payment_type type) { + switch (type) { + case payment_type::P2PK: + case payment_type::P2PKH: + case payment_type::P2WPKH: + case payment_type::P2SH_WPKH: + return true; + default: + return false; + } +} + +bytes bitcoin_transaction_builder::get_script_pubkey(payment_type type, const bytes &script_code) { + switch (type) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << script_code; + case payment_type::P2PK: + return script_builder() << script_code << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << script_code << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << script_code << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << script_code; + default: + return script_code; + } +} +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp new file mode 100644 index 00000000..2fd9be43 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/segwit_addr.cpp @@ -0,0 +1,82 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * 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 +#include + +namespace { + +typedef std::vector data; + +/** Convert from one power-of-2 number base to another. */ +template +bool convertbits(data &out, const data &in) { + int acc = 0; + int bits = 0; + const int maxv = (1 << tobits) - 1; + const int max_acc = (1 << (frombits + tobits - 1)) - 1; + for (size_t i = 0; i < in.size(); ++i) { + int value = in[i]; + acc = ((acc << frombits) | value) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + } + if (pad) { + if (bits) + out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + +} // namespace + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace segwit_addr { + +/** Decode a SegWit address. */ +std::pair decode(const std::string &hrp, const std::string &addr) { + std::pair dec = bech32::Decode(addr); + if (dec.first != hrp || dec.second.size() < 1) + return std::make_pair(-1, data()); + data conv; + if (!convertbits<5, 8, false>(conv, data(dec.second.begin() + 1, dec.second.end())) || + conv.size() < 2 || conv.size() > 40 || dec.second[0] > 16 || (dec.second[0] == 0 && conv.size() != 20 && conv.size() != 32)) { + return std::make_pair(-1, data()); + } + return std::make_pair(dec.second[0], conv); +} + +/** Encode a SegWit address. */ +std::string encode(const std::string &hrp, int witver, const data &witprog) { + data enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = bech32::Encode(hrp, enc); + if (decode(hrp, ret).first == -1) + return ""; + return ret; +} + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::segwit_addr diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp new file mode 100644 index 00000000..edb45c5b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp @@ -0,0 +1,153 @@ +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +const secp256k1_context_t *btc_context() { + static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &scriptCode, int64_t amount, + size_t in_index, int hash_type, bool is_witness) { + fc::datastream ps; + if (is_witness) + pack_tx_witness_signature(ps, scriptCode, tx, in_index, amount, hash_type); + else + pack_tx_signature(ps, scriptCode, tx, in_index, hash_type); + + std::vector vec(ps.tellp()); + if (!vec.empty()) { + fc::datastream ds(vec.data(), vec.size()); + if (is_witness) + pack_tx_witness_signature(ds, scriptCode, tx, in_index, amount, hash_type); + else + pack_tx_signature(ds, scriptCode, tx, in_index, hash_type); + } + + return fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); +} + +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign) { + bytes sig; + sig.resize(72); + int sig_len = sig.size(); + + FC_ASSERT(secp256k1_ecdsa_sign( + context_sign, + reinterpret_cast(hash.data()), + reinterpret_cast(sig.data()), + &sig_len, + reinterpret_cast(privkey.data()), + secp256k1_nonce_function_rfc6979, + nullptr)); // TODO: replace assert with exception + + sig.resize(sig_len); + + return sig; +} + +std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const bytes &privkey, + const secp256k1_context_t *context_sign, int hash_type) { + FC_ASSERT(tx.vin.size() == redeem_scripts.size() && tx.vin.size() == amounts.size()); + FC_ASSERT(!privkey.empty()); + + std::vector signatures; + for (size_t i = 0; i < tx.vin.size(); i++) { + const auto sighash = get_signature_hash(tx, redeem_scripts[i], static_cast(amounts[i]), i, hash_type, true); + auto sig = privkey_sign(privkey, sighash, context_sign); + sig.push_back(static_cast(hash_type)); + + signatures.push_back(sig); + } + return signatures; +} + +void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vector &redeem_scripts, bool use_mulisig_workaround) { + FC_ASSERT(tx.vin.size() == redeem_scripts.size()); + + for (size_t i = 0; i < tx.vin.size(); i++) { + if (use_mulisig_workaround) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), bytes()); // Bitcoin workaround CHECKMULTISIG bug + tx.vin[i].scriptWitness.push_back(redeem_scripts[i]); + } +} + +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context) { + std::vector sig_temp(sig.begin(), sig.end()); + std::vector pubkey_temp(pubkey.begin(), pubkey.end()); + std::vector msg_temp(msg.begin(), msg.end()); + + int result = secp256k1_ecdsa_verify(context, msg_temp.data(), sig_temp.data(), sig_temp.size(), pubkey_temp.data(), pubkey_temp.size()); + return result == 1; +} + +std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const secp256k1_context_t *context) { + FC_ASSERT(redeem_scripts.size() == amounts.size()); + + using data = std::pair; + struct comp { + bool operator()(const data &lhs, const data &rhs) const { + return lhs.first < rhs.first; + } + }; + + std::vector> new_stacks; + + for (size_t i = 0; i < redeem_scripts.size(); i++) { + const std::vector &keys = get_pubkey_from_redeemScript(redeem_scripts[i]); + const auto &sighash = get_signature_hash(tx, redeem_scripts[i], static_cast(amounts[i]), i, 1, true).str(); + bytes sighash_temp(parse_hex(sighash)); + + std::vector stack(tx.vin[i].scriptWitness); + std::vector marker(tx.vin[i].scriptWitness.size(), false); + std::set sigs; + + for (size_t j = 0; j < keys.size(); j++) { + for (size_t l = 0; l < stack.size(); l++) { + if (!verify_sig(stack[l], keys[j], sighash_temp, context) || marker[l]) + continue; + sigs.insert(std::make_pair(j, stack[l])); + marker[l] = true; + break; + } + } + + std::vector temp_sig; + for (auto s : sigs) { + temp_sig.push_back(s.second); + } + new_stacks.push_back(temp_sig); + } + return new_stacks; +} + +void add_signatures_to_transaction_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + for (unsigned int i = 0; i < signature_set.size(); i++) { + std::vector signatures = signature_set[i]; + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.push_back(signatures[i]); + } +} + +void add_signatures_to_transaction_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + for (unsigned int i = 0; i < signature_set.size(); i++) { + std::vector signatures = signature_set[i]; + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + // push signatures in reverse order because script starts to check the top signature on the stack first + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); + } +} + +void add_signatures_to_transaction_user_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set) { + add_signatures_to_transaction_weighted_multisig(tx, signature_set); + for (size_t itr = 0; itr < tx.vin.size(); itr++) { + tx.vin[0].scriptWitness.push_back(bytes()); + } +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp new file mode 100644 index 00000000..6d802a2f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/utils.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key) { + FC_ASSERT(public_key.size() == 33); + fc::ecc::public_key_data key; + for (size_t i = 0; i < 33; i++) { + key.at(i) = public_key[i]; + } + return key; +} + +bytes get_privkey_bytes(const std::string &privkey_base58) { + const auto privkey = fc::from_base58(privkey_base58); + // Remove version and hash + return bytes(privkey.cbegin() + 1, privkey.cbegin() + 1 + 32); +} + +bytes parse_hex(const std::string &str) { + bytes vec(str.size() / 2); + fc::from_hex(str, vec.data(), vec.size()); + return vec; +} + +std::vector get_pubkey_from_redeemScript(bytes script) { + FC_ASSERT(script.size() >= 37); + + script.erase(script.begin()); + script.erase(script.end() - 2, script.end()); + + std::vector result; + uint64_t count = script.size() / 34; + for (size_t i = 0; i < count; i++) { + result.push_back(bytes(script.begin() + (34 * i) + 1, script.begin() + (34 * (i + 1)))); + } + return result; +} + +bytes public_key_data_to_bytes(const fc::ecc::public_key_data &key) { + bytes result; + result.resize(key.size()); + std::copy(key.begin(), key.end(), result.begin()); + return result; +} + +std::vector read_byte_arrays_from_string(const std::string &string_buf) { + std::stringstream ss(string_buf); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::vector data; + for (auto &v : json) { + std::string hex = v.second.data(); + bytes item; + item.resize(hex.size() / 2); + fc::from_hex(hex, (char *)&item[0], item.size()); + data.push_back(item); + } + return data; +} + +std::string write_transaction_signatures(const std::vector &data) { + std::string res = "["; + for (unsigned int idx = 0; idx < data.size(); ++idx) { + res += "\"" + fc::to_hex((char *)&data[idx][0], data[idx].size()) + "\""; + if (idx != data.size() - 1) + res += ","; + } + res += "]"; + return res; +} + +void read_transaction_data(const std::string &string_buf, std::string &tx_hex, std::vector &in_amounts, std::string &redeem_script) { + std::stringstream ss(string_buf); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + tx_hex = json.get("tx_hex"); + in_amounts.clear(); + for (auto &v : json.get_child("in_amounts")) + in_amounts.push_back(fc::to_uint64(v.second.data())); + redeem_script = json.get("redeem_script"); +} + +std::string write_transaction_data(const std::string &tx, const std::vector &in_amounts, const std::string &redeem_script) { + std::string res = "{\"tx_hex\":\"" + tx + "\",\"in_amounts\":["; + for (unsigned int idx = 0; idx < in_amounts.size(); ++idx) { + res += fc::to_string(in_amounts[idx]); + if (idx != in_amounts.size() - 1) + res += ","; + } + res += "],\"redeem_script\":\"" + redeem_script + "\"}"; + return res; +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp new file mode 100644 index 00000000..c98d22bf --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bech32.hpp @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Bech32 is a string encoding format used in newer address types. +// The output consists of a human-readable part (alphanumeric), a +// separator character (1), and a base32 data section, the last +// 6 characters of which are a checksum. +// +// For more information, see BIP 173. + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace bech32 { + +/** Encode a Bech32 string. Returns the empty string in case of failure. */ +std::string Encode(const std::string &hrp, const std::vector &values); + +/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ +std::pair> Decode(const std::string &str); + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::bech32 diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp new file mode 100644 index 00000000..b9467e2f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp @@ -0,0 +1,249 @@ +#pragma once + +#include +#include + +using namespace graphene::chain; + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +const bytes op_num = {0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f}; // OP_1 - OP_15 + +class bitcoin_address { + +public: + enum network { + mainnet, + testnet, + regtest + }; + + bitcoin_address() = default; + + bitcoin_address(const std::string &addr, network ntype = network::regtest) : + address(addr), + type(determine_type()), + raw_address(determine_raw_address()), + network_type(ntype) { + } + + bool operator==(const bitcoin_address &btc_addr) const; + + payment_type get_type() const { + return type; + } + + std::string get_address() const { + return address; + } + + bytes get_raw_address() const { + return raw_address; + } + + bytes get_script() const; + + network get_network_type() const { + return network_type; + } + +private: + enum size_segwit_address { P2WSH = 32, + P2WPKH = 20 }; + + payment_type determine_type(); + + bytes determine_raw_address(); + + bool check_segwit_address(const size_segwit_address &size) const; + + bool is_p2pk() const; + + bool is_p2wpkh() const; + + bool is_p2wsh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + +public: + std::string address; + + payment_type type; + + bytes raw_address; + + network network_type; +}; + +class btc_multisig_address : public bitcoin_address { + +public: + btc_multisig_address() = default; + + btc_multisig_address(const size_t n_required, const accounts_keys &keys); + + size_t count_intersection(const accounts_keys &keys) const; + + bytes get_redeem_script() const { + return redeem_script; + } + +private: + void create_redeem_script(); + + void create_address(); + +public: + enum address_types { MAINNET_SCRIPT = 5, + TESTNET_SCRIPT = 196 }; + + enum { OP_0 = 0x00, + OP_EQUAL = 0x87, + OP_HASH160 = 0xa9, + OP_CHECKMULTISIG = 0xae }; + + bytes redeem_script; + + size_t keys_required = 0; + + accounts_keys witnesses_keys; +}; + +// multisig segwit address (P2WSH) +// https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa +class btc_multisig_segwit_address : public btc_multisig_address { + +public: + btc_multisig_segwit_address() = default; + + btc_multisig_segwit_address(const size_t n_required, const accounts_keys &keys); + + bool operator==(const btc_multisig_segwit_address &addr) const; + + bytes get_witness_script() const { + return witness_script; + } + + std::vector get_keys(); + +private: + void create_witness_script(); + + void create_segwit_address(); + + bytes get_address_bytes(const bytes &script_hash); + +public: + bytes witness_script; +}; + +class btc_weighted_multisig_address : public bitcoin_address { + +public: + btc_weighted_multisig_address() = default; + + btc_weighted_multisig_address(const std::vector> &keys_data, + network network_type = network::regtest); + + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +private: + void create_redeem_script(const std::vector> &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +class btc_one_or_m_of_n_multisig_address : public bitcoin_address { +public: + btc_one_or_m_of_n_multisig_address() = default; + btc_one_or_m_of_n_multisig_address(const fc::ecc::public_key &user_key_data, const uint8_t nrequired, const std::vector &keys_data, + network network_type = network::regtest); + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +private: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const uint8_t nrequired, const std::vector &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +class btc_one_or_weighted_multisig_address : public bitcoin_address { +public: + btc_one_or_weighted_multisig_address() = default; + btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data, + network network_type = network::regtest); + bytes get_redeem_script() const { + return redeem_script_; + } + bytes get_witness_script() const { + return witness_script_; + } + +protected: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data); + void create_witness_script(); + void create_segwit_address(); + +public: + bytes redeem_script_; + bytes witness_script_; +}; + +class btc_timelocked_one_or_weighted_multisig_address : public btc_one_or_weighted_multisig_address { +public: + btc_timelocked_one_or_weighted_multisig_address() = default; + btc_timelocked_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, uint32_t latency, const std::vector> &keys_data, + network network_type = network::regtest); + +private: + void create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data); + + uint32_t latency_; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::bitcoin_address::network, + (mainnet)(testnet)(regtest)) + +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::bitcoin_address, (address)(type)(raw_address)(network_type)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_multisig_address, (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script)(keys_required)(witnesses_keys)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_multisig_segwit_address, (graphene::peerplays_sidechain::bitcoin::btc_multisig_address), (witness_script)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_one_or_m_of_n_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_one_or_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::bitcoin_address), + (redeem_script_)(witness_script_)); + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::bitcoin::btc_timelocked_one_or_weighted_multisig_address, + (graphene::peerplays_sidechain::bitcoin::btc_one_or_weighted_multisig_address), + (latency_)); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp new file mode 100644 index 00000000..7b746449 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_script.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +enum class op { + // push value + _0 = 0x00, + _1 = 0x51, + _2 = 0x52, + _3 = 0x53, + _4 = 0x54, + _5 = 0x55, + _6 = 0x56, + _7 = 0x57, + _8 = 0x58, + _9 = 0x59, + _10 = 0x5a, + _11 = 0x5b, + _12 = 0x5c, + _13 = 0x5d, + _14 = 0x5e, + _15 = 0x5f, + _16 = 0x60, + + // control + IF = 0x63, + NOTIF = 0x64, + ELSE = 0x67, + ENDIF = 0x68, + RETURN = 0x6a, + + // stack ops + DROP = 0x75, + DUP = 0x76, + SWAP = 0x7c, + + // bit logic + EQUAL = 0x87, + EQUALVERIFY = 0x88, + ADD = 0x93, + GREATERTHAN = 0xa0, + GREATERTHANOREQUAL = 0xa2, + + // crypto + HASH160 = 0xa9, + CHECKSIG = 0xac, + CHECKMULTISIG = 0xae, + // Locktime + CHECKLOCKTIMEVERIFY = 0xb1, + CHECKSEQUENCEVERIFY = 0xb2, +}; + +class script_builder { + +public: + script_builder &operator<<(op opcode); + + script_builder &operator<<(uint32_t number); + + script_builder &operator<<(size_t size); + + script_builder &operator<<(const bytes &sc); + + script_builder &operator<<(const fc::sha256 &hash); + + script_builder &operator<<(const fc::ripemd160 &hash); + + script_builder &operator<<(const fc::ecc::public_key_data &pubkey_data); + + operator bytes() const { + return std::move(script); + } + +private: + bytes script; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp new file mode 100644 index 00000000..5668ffc2 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_transaction.hpp @@ -0,0 +1,106 @@ +#pragma once +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +struct out_point { + fc::sha256 hash; + uint32_t n = 0; + out_point() = default; + out_point(fc::sha256 _hash, uint32_t _n) : + hash(_hash), + n(_n) { + } + bool operator==(const out_point &op) const; +}; + +struct tx_in { + + bool operator==(const tx_in &ti) const; + + out_point prevout; + bytes scriptSig; + uint32_t nSequence = 0xffffffff; + std::vector scriptWitness; +}; + +struct tx_out { + int64_t value = 0; + bytes scriptPubKey; + + bool operator==(const tx_out &to) const; + + bool is_p2wsh() const; + + bool is_p2wpkh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + + bool is_p2pk() const; + + bytes get_data_or_script() const; +}; + +struct bitcoin_transaction { + + bool operator!=(const bitcoin_transaction &bt) const; + + int32_t nVersion = 1; + std::vector vin; + std::vector vout; + uint32_t nLockTime = 0; + + fc::sha256 get_hash() const; + fc::sha256 get_txid() const; + size_t get_vsize() const; +}; + +class bitcoin_transaction_builder { + +public: + bitcoin_transaction_builder() = default; + + bitcoin_transaction_builder(const bitcoin_transaction _tx) : + tx(_tx) { + } + + void set_version(int32_t version); + + void set_locktime(uint32_t lock_time); + + void add_in(const fc::sha256 &txid, uint32_t n_out, + const bytes &script_code, bool front = false, uint32_t sequence = 0xffffffff); + + void add_in(payment_type type, const fc::sha256 &txid, uint32_t n_out, + const bytes &script_code, bool front = false, uint32_t sequence = 0xffffffff); + + void add_in(payment_type type, tx_in txin, const bytes &script_code, bool front = false); + + void add_out(payment_type type, int64_t amount, const std::string &base58_address, bool front = false); + + void add_out(payment_type type, int64_t amount, const fc::ecc::public_key_data &pubkey, bool front = false); + + void add_out(payment_type type, int64_t amount, const bytes &script_code, bool front = false); + + void add_out_all_type(const uint64_t &amount, const bitcoin_address &address, bool front = false); + + bitcoin_transaction get_transaction() const { + return tx; + } + +private: + inline bool is_payment_to_pubkey(payment_type type); + + bytes get_script_pubkey(payment_type type, const bytes &script_code); + + bitcoin_transaction tx; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::out_point, (hash)(n)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::tx_in, (prevout)(scriptSig)(nSequence)(scriptWitness)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::tx_out, (value)(scriptPubKey)) +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::bitcoin_transaction, (nVersion)(vin)(vout)(nLockTime)) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp new file mode 100644 index 00000000..b7f2748e --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/segwit_addr.hpp @@ -0,0 +1,34 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * 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 +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { namespace segwit_addr { + +/** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */ +std::pair> decode(const std::string &hrp, const std::string &addr); + +/** Encode a SegWit address. Empty string means failure. */ +std::string encode(const std::string &hrp, int witver, const std::vector &witprog); + +}}}} // namespace graphene::peerplays_sidechain::bitcoin::segwit_addr \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp new file mode 100644 index 00000000..2f1dfffe --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/serialize.hpp @@ -0,0 +1,354 @@ +#pragma once + +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +inline void write_compact_size(bytes &vec, size_t size) { + bytes sb; + sb.reserve(2); + if (size < 253) { + sb.insert(sb.end(), static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + uint16_t tmp = htole16(static_cast(size)); + sb.insert(sb.end(), static_cast(253)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else if (size <= std::numeric_limits::max()) { + uint32_t tmp = htole32(static_cast(size)); + sb.insert(sb.end(), static_cast(254)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else { + uint64_t tmp = htole64(static_cast(size)); + sb.insert(sb.end(), static_cast(255)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } + vec.insert(vec.end(), sb.begin(), sb.end()); +} + +template +inline void pack_compact_size(Stream &s, size_t size) { + if (size < 253) { + fc::raw::pack(s, static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(253)); + fc::raw::pack(s, htole16(static_cast(size))); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(254)); + fc::raw::pack(s, htole32(static_cast(size))); + } else { + fc::raw::pack(s, static_cast(255)); + fc::raw::pack(s, htole64(static_cast(size))); + } +} + +template +inline uint64_t unpack_compact_size(Stream &s) { + uint8_t size; + uint64_t size_ret; + + fc::raw::unpack(s, size); + + if (size < 253) { + size_ret = size; + } else if (size == 253) { + uint16_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le16toh(tmp); + if (size_ret < 253) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else if (size == 254) { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le32toh(tmp); + if (size_ret < 0x10000u) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le64toh(tmp); + if (size_ret < 0x100000000ULL) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } + + if (size_ret > 0x08000000) + FC_THROW_EXCEPTION(fc::parse_error_exception, "unpack_compact_size(): size too large"); + + return size_ret; +} + +template +inline void unpack(Stream &s, int64_t &u, uint32_t _max_depth = FC_PACK_MAX_DEPTH) { + s.read((char *)&u, sizeof(u)); +} + +template +inline void unpack(Stream &s, int32_t &u, uint32_t _max_depth = FC_PACK_MAX_DEPTH) { + s.read((char *)&u, sizeof(u)); +} + +template +inline void pack(Stream &s, const std::vector &v) { + pack_compact_size(s, v.size()); + if (!v.empty()) + s.write(v.data(), v.size()); +} + +template +inline void unpack(Stream &s, std::vector &v) { + const auto size = unpack_compact_size(s); + v.resize(size); + if (size) + s.read(v.data(), size); +} + +template +inline void pack(Stream &s, const T &val) { + fc::raw::pack(s, val); +} + +template +inline void unpack(Stream &s, T &val) { + fc::raw::unpack(s, val); +} + +template +inline void pack(Stream &s, const out_point &op) { + fc::sha256 reversed(op.hash); + std::reverse(reversed.data(), reversed.data() + reversed.data_size()); + s.write(reversed.data(), reversed.data_size()); + pack(s, op.n); +} + +template +inline void unpack(Stream &s, out_point &op) { + uint64_t hash_size = op.hash.data_size(); + std::unique_ptr hash_data(new char[hash_size]); + s.read(hash_data.get(), hash_size); + std::reverse(hash_data.get(), hash_data.get() + hash_size); + + op.hash = fc::sha256(hash_data.get(), hash_size); + unpack(s, op.n); +} + +template +inline void pack(Stream &s, const tx_in &in) { + pack(s, in.prevout); + pack(s, in.scriptSig); + pack(s, in.nSequence); +} + +template +inline void unpack(Stream &s, tx_in &in) { + unpack(s, in.prevout); + unpack(s, in.scriptSig); + unpack(s, in.nSequence); +} + +template +inline void pack(Stream &s, const tx_out &out) { + pack(s, out.value); + pack(s, out.scriptPubKey); +} + +template +inline void unpack(Stream &s, tx_out &out) { + unpack(s, out.value); + unpack(s, out.scriptPubKey); +} + +template +inline void pack(Stream &s, const bitcoin_transaction &tx, bool with_witness = true) { + uint8_t flags = 0; + + if (with_witness) { + for (const auto &in : tx.vin) { + if (!in.scriptWitness.empty()) { + flags |= 1; + break; + } + } + } + + pack(s, tx.nVersion); + + if (flags) { + pack_compact_size(s, 0); + pack(s, flags); + } + + pack_compact_size(s, tx.vin.size()); + for (const auto &in : tx.vin) + pack(s, in); + + pack_compact_size(s, tx.vout.size()); + for (const auto &out : tx.vout) + pack(s, out); + + if (flags & 1) { + for (const auto in : tx.vin) { + pack_compact_size(s, in.scriptWitness.size()); + for (const auto &sc : in.scriptWitness) + pack(s, sc); + } + } + + pack(s, tx.nLockTime); +} + +template +inline void unpack(Stream &s, bitcoin_transaction &tx) { + uint8_t flags = 0; + + unpack(s, tx.nVersion); + + auto vin_size = unpack_compact_size(s); + if (vin_size == 0) { + unpack(s, flags); + vin_size = unpack_compact_size(s); + } + + tx.vin.reserve(vin_size); + for (size_t i = 0; i < vin_size; i++) { + tx_in in; + unpack(s, in); + tx.vin.push_back(in); + } + + const auto vout_size = unpack_compact_size(s); + tx.vout.reserve(vout_size); + for (size_t i = 0; i < vout_size; i++) { + tx_out out; + unpack(s, out); + tx.vout.push_back(out); + } + + if (flags & 1) { + for (auto &in : tx.vin) { + uint64_t stack_size = unpack_compact_size(s); + in.scriptWitness.reserve(stack_size); + for (uint64_t i = 0; i < stack_size; i++) { + std::vector script; + unpack(s, script); + in.scriptWitness.push_back(script); + } + } + } + + unpack(s, tx.nLockTime); +} + +inline std::vector pack(const bitcoin_transaction &v, bool with_witness = true) { + fc::datastream ps; + pack(ps, v, with_witness); + std::vector vec(ps.tellp()); + + if (!vec.empty()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + pack(ds, v, with_witness); + } + return vec; +} + +inline bitcoin_transaction unpack(const std::vector &s) { + try { + bitcoin_transaction tmp; + if (!s.empty()) { + fc::datastream ds(s.data(), size_t(s.size())); + unpack(ds, tmp); + } + return tmp; + } + FC_RETHROW_EXCEPTIONS(warn, "error unpacking ${type}", ("type", "transaction")) +} + +template +inline void pack_tx_signature(Stream &s, const std::vector &scriptPubKey, const bitcoin_transaction &tx, unsigned int in_index, int hash_type) { + pack(s, tx.nVersion); + + pack_compact_size(s, tx.vin.size()); + for (size_t i = 0; i < tx.vin.size(); i++) { + const auto &in = tx.vin[i]; + pack(s, in.prevout); + if (i == in_index) + pack(s, scriptPubKey); + else + pack_compact_size(s, 0); // Blank signature + pack(s, in.nSequence); + } + + pack_compact_size(s, tx.vout.size()); + for (const auto &out : tx.vout) + pack(s, out); + + pack(s, tx.nLockTime); + pack(s, hash_type); +} + +template +inline void pack_tx_witness_signature(Stream &s, const std::vector &scriptCode, const bitcoin_transaction &tx, unsigned int in_index, int64_t amount, int hash_type) { + + fc::sha256 hash_prevouts; + fc::sha256 hash_sequence; + fc::sha256 hash_output; + + { + fc::datastream ps; + for (const auto in : tx.vin) + pack(ps, in.prevout); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto in : tx.vin) + pack(ds, in.prevout); + } + + hash_prevouts = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + } + + { + fc::datastream ps; + for (const auto in : tx.vin) + pack(ps, in.nSequence); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto in : tx.vin) + pack(ds, in.nSequence); + } + + hash_sequence = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + }; + + { + fc::datastream ps; + for (const auto out : tx.vout) + pack(ps, out); + + std::vector vec(ps.tellp()); + if (vec.size()) { + fc::datastream ds(vec.data(), size_t(vec.size())); + for (const auto out : tx.vout) + pack(ds, out); + } + + hash_output = fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); + } + + pack(s, tx.nVersion); + pack(s, hash_prevouts); + pack(s, hash_sequence); + + pack(s, tx.vin[in_index].prevout); + pack(s, scriptCode); + pack(s, amount); + pack(s, tx.vin[in_index].nSequence); + + pack(s, hash_output); + pack(s, tx.nLockTime); + pack(s, hash_type); +} + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp new file mode 100644 index 00000000..41808562 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +class bitcoin_transaction; + +const secp256k1_context_t *btc_context(); + +fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &scriptPubKey, int64_t amount, + size_t in_index, int hash_type, bool is_witness); + +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign = nullptr); + +std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const bytes &privkey, + const secp256k1_context_t *context_sign = nullptr, int hash_type = 1); + +void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vector &redeem_scripts, bool use_mulisig_workaround = true); + +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context); + +std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, + const std::vector &amounts, const secp256k1_context_t *context); + +void add_signatures_to_transaction_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +void add_signatures_to_transaction_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +void add_signatures_to_transaction_user_weighted_multisig(bitcoin_transaction &tx, std::vector> &signature_set); + +}}} // namespace graphene::peerplays_sidechain::bitcoin diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp new file mode 100644 index 00000000..0997194b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/types.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +class bitcoin_transaction; + +using bytes = std::vector; +using accounts_keys = std::map; +using full_btc_transaction = std::pair; + +enum class payment_type { + NULLDATA, + P2PK, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2SH_WPKH, + P2SH_WSH +}; + +enum class sidechain_proposal_type { + ISSUE_BTC, + SEND_BTC_TRANSACTION, + REVERT_BTC_TRANSACTION +}; + +struct prev_out { + bool operator!=(const prev_out &obj) const { + if (this->hash_tx != obj.hash_tx || + this->n_vout != obj.n_vout || + this->amount != obj.amount) { + return true; + } + return false; + } + + std::string hash_tx; + uint32_t n_vout; + uint64_t amount; +}; + +}}} // namespace graphene::peerplays_sidechain::bitcoin + +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH)); +FC_REFLECT_ENUM(graphene::peerplays_sidechain::bitcoin::sidechain_proposal_type, (ISSUE_BTC)(SEND_BTC_TRANSACTION)(REVERT_BTC_TRANSACTION)); +FC_REFLECT(graphene::peerplays_sidechain::bitcoin::prev_out, (hash_tx)(n_vout)(amount)); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp new file mode 100644 index 00000000..4e1c4b58 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace bitcoin { + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key); + +bytes get_privkey_bytes(const std::string &privkey_base58); + +bytes parse_hex(const std::string &str); + +std::vector get_pubkey_from_redeemScript(bytes script); + +bytes public_key_data_to_bytes(const fc::ecc::public_key_data &key); + +std::vector read_byte_arrays_from_string(const std::string &string_buf); + +std::string write_transaction_signatures(const std::vector &data); + +void read_transaction_data(const std::string &string_buf, std::string &tx_hex, std::vector &in_amounts, std::string &redeem_script); + +std::string write_transaction_data(const std::string &tx, const std::vector &in_amounts, const std::string &redeem_script); + +}}} // namespace graphene::peerplays_sidechain::bitcoin \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp new file mode 100644 index 00000000..70618236 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +using namespace graphene::chain; + +using bytes = std::vector; + +struct prev_out { + bool operator!=(const prev_out &obj) const { + if (this->hash_tx != obj.hash_tx || + this->n_vout != obj.n_vout || + this->amount != obj.amount) { + return true; + } + return false; + } + + std::string hash_tx; + uint32_t n_vout; + uint64_t amount; +}; + +struct info_for_vin { + info_for_vin() = default; + + info_for_vin(const prev_out &_out, const std::string &_address, bytes _script = bytes(), bool _resend = false); + + bool operator!=(const info_for_vin &obj) const; + + struct comparer { + bool operator()(const info_for_vin &lhs, const info_for_vin &rhs) const; + }; + + static uint64_t count_id_info_for_vin; + uint64_t id; + + fc::sha256 identifier; + + prev_out out; + std::string address; + bytes script; + + bool used = false; + bool resend = false; +}; + +struct sidechain_event_data { + fc::time_point_sec timestamp; + uint32_t block_num; + sidechain_type sidechain; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + fc::safe sidechain_amount; + account_id_type peerplays_from; + account_id_type peerplays_to; + asset peerplays_asset; +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp new file mode 100644 index 00000000..80086c10 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +using namespace chain; + +namespace detail { +class peerplays_sidechain_plugin_impl; +} + +class peerplays_sidechain_plugin : public graphene::app::plugin { +public: + peerplays_sidechain_plugin(); + virtual ~peerplays_sidechain_plugin(); + + std::string plugin_name() const override; + virtual void plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) override; + virtual void plugin_initialize(const boost::program_options::variables_map &options) override; + virtual void plugin_startup() override; + + std::unique_ptr my; + + std::set &get_sons(); + const son_id_type get_current_son_id(); + const son_object get_current_son_object(); + const son_object get_son_object(son_id_type son_id); + bool is_active_son(son_id_type son_id); + bool is_son_deregistered(son_id_type son_id); + fc::ecc::private_key get_private_key(son_id_type son_id); + fc::ecc::private_key get_private_key(chain::public_key_type public_key); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp new file mode 100644 index 00000000..50dfd505 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler { +public: + sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler(); + + sidechain_type get_sidechain(); + std::vector get_sidechain_deposit_addresses(); + std::vector get_sidechain_withdraw_addresses(); + std::string get_private_key(std::string public_key); + + bool proposal_exists(int32_t operation_tag, const object_id_type &object_id, boost::optional proposal_op = boost::none); + bool signer_expected(const sidechain_transaction_object &sto, son_id_type signer); + bool approve_proposal(const proposal_id_type &proposal_id, const son_id_type &son_id); + void sidechain_event_data_received(const sidechain_event_data &sed); + + void process_proposals(); + void process_active_sons_change(); + void create_deposit_addresses(); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + void settle_sidechain_transactions(); + + virtual bool process_proposal(const proposal_object &po) = 0; + virtual void process_primary_wallet() = 0; + virtual void process_sidechain_addresses() = 0; + virtual bool process_deposit(const son_wallet_deposit_object &swdo) = 0; + virtual bool process_withdrawal(const son_wallet_withdraw_object &swwo) = 0; + virtual std::string process_sidechain_transaction(const sidechain_transaction_object &sto) = 0; + virtual std::string send_sidechain_transaction(const sidechain_transaction_object &sto) = 0; + virtual int64_t settle_sidechain_transaction(const sidechain_transaction_object &sto) = 0; + +protected: + peerplays_sidechain_plugin &plugin; + graphene::chain::database &database; + sidechain_type sidechain; + + std::map private_keys; + +private: +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp new file mode 100644 index 00000000..eb218a4c --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class btc_txout { +public: + std::string txid_; + unsigned int out_num_; + uint64_t amount_; +}; + +class bitcoin_rpc_client { +public: + bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password); + + std::string addmultisigaddress(const uint32_t nrequired, const std::vector public_keys); + std::string combinepsbt(const vector &psbts); + std::string createmultisig(const uint32_t nrequired, const std::vector public_keys); + std::string createpsbt(const std::vector &ins, const fc::flat_map outs); + std::string createrawtransaction(const std::vector &ins, const fc::flat_map outs); + std::string createwallet(const std::string &wallet_name); + std::string decodepsbt(std::string const &tx_psbt); + std::string decoderawtransaction(std::string const &tx_hex); + std::string encryptwallet(const std::string &passphrase); + uint64_t estimatesmartfee(uint16_t conf_target = 128); + std::string finalizepsbt(std::string const &tx_psbt); + std::string getaddressinfo(const std::string &address); + std::string getblock(const std::string &block_hash, int32_t verbosity = 2); + std::string getrawtransaction(const std::string &txid, const bool verbose = false); + std::string gettransaction(const std::string &txid, const bool include_watch_only = false); + std::string getblockchaininfo(); + void importaddress(const std::string &address_or_script, const std::string &label = "", const bool rescan = true, const bool p2sh = false); + std::vector listunspent(const uint32_t minconf = 1, const uint32_t maxconf = 9999999); + std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999); + std::string loadwallet(const std::string &filename); + std::string sendrawtransaction(const std::string &tx_hex); + std::string signrawtransactionwithwallet(const std::string &tx_hash); + std::string unloadwallet(const std::string &filename); + //std::string walletlock(); + std::string walletprocesspsbt(std::string const &tx_psbt); + //bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); + +private: + fc::http::reply send_post_request(std::string body, bool show_log = false); + + std::string ip; + uint32_t rpc_port; + std::string user; + std::string password; + std::string wallet; + std::string wallet_password; + + fc::http::header authorization; +}; + +// ============================================================================= + +class zmq_listener { +public: + zmq_listener(std::string _ip, uint32_t _zmq); + + fc::signal event_received; + +private: + void handle_zmq(); + std::vector receive_multipart(); + + std::string ip; + uint32_t zmq_port; + + zmq::context_t ctx; + zmq::socket_t socket; +}; + +// ============================================================================= + +class sidechain_net_handler_bitcoin : public sidechain_net_handler { +public: + sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler_bitcoin(); + + bool process_proposal(const proposal_object &po); + void process_primary_wallet(); + void process_sidechain_addresses(); + bool process_deposit(const son_wallet_deposit_object &swdo); + bool process_withdrawal(const son_wallet_withdraw_object &swwo); + std::string process_sidechain_transaction(const sidechain_transaction_object &sto); + std::string send_sidechain_transaction(const sidechain_transaction_object &sto); + int64_t settle_sidechain_transaction(const sidechain_transaction_object &sto); + +private: + std::string ip; + uint32_t zmq_port; + uint32_t rpc_port; + std::string rpc_user; + std::string rpc_password; + std::string wallet; + std::string wallet_password; + + std::unique_ptr bitcoin_client; + std::unique_ptr listener; + + fc::future on_changed_objects_task; + bitcoin::bitcoin_address::network network_type; + + std::string create_primary_wallet_address(const std::vector &son_pubkeys); + + std::string create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address); + std::string create_deposit_transaction(const son_wallet_deposit_object &swdo); + std::string create_withdrawal_transaction(const son_wallet_withdraw_object &swwo); + + std::string create_transaction(const std::vector &inputs, const fc::flat_map outputs, std::string &redeem_script); + std::string sign_transaction(const sidechain_transaction_object &sto); + std::string send_transaction(const sidechain_transaction_object &sto); + + void handle_event(const std::string &event_data); + std::string get_redeemscript_for_userdeposit(const std::string &user_address); + std::vector extract_info_from_block(const std::string &_block); + void on_changed_objects(const vector &ids, const flat_set &accounts); + void on_changed_objects_cb(const vector &ids, const flat_set &accounts); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp new file mode 100644 index 00000000..aa094d95 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler_peerplays : public sidechain_net_handler { +public: + sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler_peerplays(); + + bool process_proposal(const proposal_object &po); + void process_primary_wallet(); + void process_sidechain_addresses(); + bool process_deposit(const son_wallet_deposit_object &swdo); + bool process_withdrawal(const son_wallet_withdraw_object &swwo); + std::string process_sidechain_transaction(const sidechain_transaction_object &sto); + std::string send_sidechain_transaction(const sidechain_transaction_object &sto); + int64_t settle_sidechain_transaction(const sidechain_transaction_object &sto); + +private: + void on_applied_block(const signed_block &b); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp new file mode 100644 index 00000000..c2d40e14 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_manager { +public: + sidechain_net_manager(peerplays_sidechain_plugin &_plugin); + virtual ~sidechain_net_manager(); + + bool create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options); + void process_proposals(); + void process_active_sons_change(); + void create_deposit_addresses(); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + void settle_sidechain_transactions(); + +private: + peerplays_sidechain_plugin &plugin; + graphene::chain::database &database; + std::vector> net_handlers; +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp new file mode 100644 index 00000000..93dfe1c8 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -0,0 +1,698 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace bpo = boost::program_options; + +namespace graphene { namespace peerplays_sidechain { + +namespace detail { + +class peerplays_sidechain_plugin_impl { +public: + peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin &_plugin); + virtual ~peerplays_sidechain_plugin_impl(); + + void plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg); + void plugin_initialize(const boost::program_options::variables_map &opt); + void plugin_startup(); + + std::set &get_sons(); + const son_id_type get_current_son_id(); + const son_object get_current_son_object(); + const son_object get_son_object(son_id_type son_id); + bool is_active_son(son_id_type son_id); + bool is_son_deregistered(son_id_type son_id); + bool is_son_deregister_op_valid(const chain::operation &op); + bool is_son_down_op_valid(const chain::operation &op); + bool is_valid_son_proposal(const chain::proposal_object &proposal); + fc::ecc::private_key get_private_key(son_id_type son_id); + fc::ecc::private_key get_private_key(chain::public_key_type public_key); + + void schedule_heartbeat_loop(); + void heartbeat_loop(); + void schedule_son_processing(); + void son_processing(); + void approve_proposals(); + void create_son_down_proposals(); + void create_son_deregister_proposals(); + + void process_proposals(); + void process_active_sons_change(); + void create_deposit_addresses(); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + void settle_sidechain_transactions(); + +private: + peerplays_sidechain_plugin &plugin; + + boost::program_options::variables_map options; + + bool config_ready_son; + bool config_ready_bitcoin; + bool config_ready_peerplays; + + son_id_type current_son_id; + + std::unique_ptr net_manager; + std::set sons; + std::map private_keys; + fc::future _heartbeat_task; + fc::future _son_processing_task; + + bool first_block_skipped; + void on_applied_block(const signed_block &b); +}; + +peerplays_sidechain_plugin_impl::peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin &_plugin) : + plugin(_plugin), + config_ready_son(false), + config_ready_bitcoin(false), + config_ready_peerplays(false), + current_son_id(son_id_type(std::numeric_limits().max())), + net_manager(nullptr), + first_block_skipped(false) { +} + +peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() { + try { + if (_heartbeat_task.valid()) + _heartbeat_task.cancel_and_wait(__FUNCTION__); + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } + + try { + if (_son_processing_task.valid()) + _son_processing_task.cancel_and_wait(__FUNCTION__); + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } +} + +void peerplays_sidechain_plugin_impl::plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) { + auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))); + string son_id_example = fc::json::to_string(chain::son_id_type(5)); + string son_id_example2 = fc::json::to_string(chain::son_id_type(6)); + + cli.add_options()("son-id", bpo::value>(), ("ID of SON controlled by this node (e.g. " + son_id_example + ", quotes are required)").c_str()); + cli.add_options()("son-ids", bpo::value(), ("IDs of multiple SONs controlled by this node (e.g. [" + son_id_example + ", " + son_id_example2 + "], quotes are required)").c_str()); + cli.add_options()("peerplays-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))), + "Tuple of [PublicKey, WIF private key] (may specify multiple times)"); + cli.add_options()("bitcoin-node-ip", bpo::value()->default_value("127.0.0.1"), "IP address of Bitcoin node"); + cli.add_options()("bitcoin-node-zmq-port", bpo::value()->default_value(11111), "ZMQ port of Bitcoin node"); + cli.add_options()("bitcoin-node-rpc-port", bpo::value()->default_value(8332), "RPC port of Bitcoin node"); + cli.add_options()("bitcoin-node-rpc-user", bpo::value()->default_value("1"), "Bitcoin RPC user"); + cli.add_options()("bitcoin-node-rpc-password", bpo::value()->default_value("1"), "Bitcoin RPC password"); + cli.add_options()("bitcoin-wallet", bpo::value(), "Bitcoin wallet"); + cli.add_options()("bitcoin-wallet-password", bpo::value(), "Bitcoin wallet password"); + cli.add_options()("bitcoin-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")), + "Tuple of [Bitcoin public key, Bitcoin private key] (may specify multiple times)"); + cfg.add(cli); +} + +void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_options::variables_map &opt) { + options = opt; + config_ready_son = (options.count("son-id") || options.count("son-ids")) && options.count("peerplays-private-key"); + if (config_ready_son) { + LOAD_VALUE_SET(options, "son-id", sons, chain::son_id_type) + if (options.count("son-ids")) + boost::insert(sons, fc::json::from_string(options.at("son-ids").as()).as>(5)); + config_ready_son = config_ready_son && !sons.empty(); + +#ifndef ENABLE_MULTIPLE_SONS + if (sons.size() > 1) { + FC_THROW("Invalid configuration, multiple SON IDs provided"); + } +#endif + + if (options.count("peerplays-private-key")) { + const std::vector key_id_to_wif_pair_strings = options["peerplays-private-key"].as>(); + for (const std::string &key_id_to_wif_pair_string : key_id_to_wif_pair_strings) { + auto key_id_to_wif_pair = graphene::app::dejsonify>(key_id_to_wif_pair_string, 5); + ilog("Public Key: ${public}", ("public", key_id_to_wif_pair.first)); + fc::optional private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second); + if (!private_key) { + // the key isn't in WIF format; see if they are still passing the old native private key format. This is + // just here to ease the transition, can be removed soon + try { + private_key = fc::variant(key_id_to_wif_pair.second, 2).as(1); + } catch (const fc::exception &) { + FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second)); + } + } + private_keys[key_id_to_wif_pair.first] = *private_key; + } + config_ready_son = config_ready_son && !private_keys.empty(); + } + } + if (!config_ready_son) { + wlog("Haven't set up SON parameters"); + throw; + } + + config_ready_bitcoin = options.count("bitcoin-node-ip") && + options.count("bitcoin-node-zmq-port") && options.count("bitcoin-node-rpc-port") && + options.count("bitcoin-node-rpc-user") && options.count("bitcoin-node-rpc-password") && + /*options.count( "bitcoin-wallet" ) && options.count( "bitcoin-wallet-password" ) &&*/ + options.count("bitcoin-private-key"); + if (!config_ready_bitcoin) { + wlog("Haven't set up Bitcoin sidechain parameters"); + } + + //config_ready_ethereum = options.count( "ethereum-node-ip" ) && + // options.count( "ethereum-address" ) && options.count( "ethereum-public-key" ) && options.count( "ethereum-private-key" ); + //if (!config_ready_ethereum) { + // wlog("Haven't set up Ethereum sidechain parameters"); + //} + + config_ready_peerplays = true; + if (!config_ready_peerplays) { + wlog("Haven't set up Peerplays sidechain parameters"); + } + + if (!(config_ready_bitcoin /*&& config_ready_ethereum*/ && config_ready_peerplays)) { + wlog("Haven't set up any sidechain parameters"); + throw; + } +} + +void peerplays_sidechain_plugin_impl::plugin_startup() { + + if (config_ready_son) { + ilog("Starting ${n} SON instances", ("n", sons.size())); + + schedule_heartbeat_loop(); + } else { + elog("No sons configured! Please add SON IDs and private keys to configuration."); + } + + net_manager = std::unique_ptr(new sidechain_net_manager(plugin)); + + if (config_ready_bitcoin) { + net_manager->create_handler(sidechain_type::bitcoin, options); + ilog("Bitcoin sidechain handler running"); + } + + //if (config_ready_ethereum) { + // net_manager->create_handler(sidechain_type::ethereum, options); + // ilog("Ethereum sidechain handler running"); + //} + + if (config_ready_peerplays) { + net_manager->create_handler(sidechain_type::peerplays, options); + ilog("Peerplays sidechain handler running"); + } + + plugin.database().applied_block.connect([&](const signed_block &b) { + on_applied_block(b); + }); +} + +std::set &peerplays_sidechain_plugin_impl::get_sons() { + return sons; +} + +const son_id_type peerplays_sidechain_plugin_impl::get_current_son_id() { + return current_son_id; +} + +const son_object peerplays_sidechain_plugin_impl::get_current_son_object() { + return get_son_object(current_son_id); +} + +const son_object peerplays_sidechain_plugin_impl::get_son_object(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return {}; + return *son_obj; +} + +bool peerplays_sidechain_plugin_impl::is_active_son(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return false; + + const chain::global_property_object &gpo = plugin.database().get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + auto it = std::find(active_son_ids.begin(), active_son_ids.end(), son_id); + + return (it != active_son_ids.end()); +} + +bool peerplays_sidechain_plugin_impl::is_son_deregistered(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return true; + + if(son_obj->status == chain::son_status::deregistered) { + return true; + } + + return false; +} + +bool peerplays_sidechain_plugin_impl::is_son_deregister_op_valid(const chain::operation &op) { + son_deregister_operation deregister_op = op.get(); + return plugin.database().is_son_dereg_valid(deregister_op.son_id); +} + +bool peerplays_sidechain_plugin_impl::is_son_down_op_valid(const chain::operation &op) { + chain::database &d = plugin.database(); + const chain::global_property_object &gpo = d.get_global_properties(); + const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties(); + const auto &idx = d.get_index_type().indices().get(); + son_report_down_operation down_op = op.get(); + auto son_obj = idx.find(down_op.son_id); + if (son_obj == idx.end()) { + return false; + } + auto stats = son_obj->statistics(d); + fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; + fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); + int64_t down_threshold = gpo.parameters.son_down_time(); + if (((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && + ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { + return true; + } + return false; +} + +fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(son_id_type son_id) { + return get_private_key(get_son_object(son_id).signing_key); +} + +fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(chain::public_key_type public_key) { + auto private_key_itr = private_keys.find(public_key); + if (private_key_itr != private_keys.end()) { + return private_key_itr->second; + } + return {}; +} + +void peerplays_sidechain_plugin_impl::schedule_heartbeat_loop() { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_heartbeat = plugin.database().get_global_properties().parameters.son_heartbeat_frequency(); + + fc::time_point next_wakeup(now + fc::seconds(time_to_next_heartbeat)); + + _heartbeat_task = fc::schedule([this] { + heartbeat_loop(); + }, + next_wakeup, "SON Heartbeat Production"); +} + +void peerplays_sidechain_plugin_impl::heartbeat_loop() { + schedule_heartbeat_loop(); + chain::database &d = plugin.database(); + + for (son_id_type son_id : sons) { + if (is_active_son(son_id) || get_son_object(son_id).status == chain::son_status::in_maintenance) { + + ilog("Sending heartbeat for SON ${son}", ("son", son_id)); + chain::son_heartbeat_operation op; + op.owner_account = get_son_object(son_id).son_account; + op.son_id = son_id; + op.ts = fc::time_point::now() + fc::seconds(0); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(son_id), op); + fc::future fut = fc::async([&]() { + try { + trx.validate(); + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending heartbeat failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } +} + +void peerplays_sidechain_plugin_impl::schedule_son_processing() { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_son_processing = 500000; + + fc::time_point next_wakeup(now + fc::microseconds(time_to_next_son_processing)); + + _son_processing_task = fc::schedule([this] { + son_processing(); + }, + next_wakeup, "SON Processing"); +} + +void peerplays_sidechain_plugin_impl::son_processing() { + if (plugin.database().get_global_properties().active_sons.size() <= 0) { + return; + } + + fc::time_point now_fine = fc::time_point::now(); + fc::time_point_sec now = now_fine + fc::microseconds(500000); + if (plugin.database().get_slot_time(1) < now) { + return; // Not synced + } + + chain::son_id_type scheduled_son_id = plugin.database().get_scheduled_son(1); + ilog("Scheduled SON: ${scheduled_son_id} Now: ${now} ", + ("scheduled_son_id", scheduled_son_id)("now", now)); + + for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_son_deregistered(son_id)) { + continue; + } + current_son_id = son_id; + + // These tasks are executed by + // - All active SONs, no matter if scheduled + // - All previously active SONs + approve_proposals(); + process_proposals(); + process_sidechain_transactions(); + + if (plugin.is_active_son(son_id)) { + // Tasks that are executed by scheduled and active SON only + if (current_son_id == scheduled_son_id) { + + create_son_down_proposals(); + + create_son_deregister_proposals(); + + process_active_sons_change(); + + create_deposit_addresses(); + + process_deposits(); + + process_withdrawals(); + + process_sidechain_transactions(); + + send_sidechain_transactions(); + + settle_sidechain_transactions(); + } + } + } +} + +bool peerplays_sidechain_plugin_impl::is_valid_son_proposal(const chain::proposal_object &proposal) { + if (proposal.proposed_transaction.operations.size() == 1) { + int32_t op_idx_0 = proposal.proposed_transaction.operations[0].which(); + chain::operation op = proposal.proposed_transaction.operations[0]; + + if (op_idx_0 == chain::operation::tag::value) { + return is_son_down_op_valid(op); + } + + if (op_idx_0 == chain::operation::tag::value) { + return is_son_deregister_op_valid(op); + } + } + + return false; +} + +void peerplays_sidechain_plugin_impl::approve_proposals() { + + auto check_approve_proposal = [&](const chain::son_id_type &son_id, const chain::proposal_object &proposal) { + if (!is_valid_son_proposal(proposal)) { + return; + } + ilog("Sending approval for ${p} from ${s}", ("p", proposal.id)("s", son_id)); + chain::proposal_update_operation puo; + puo.fee_paying_account = get_son_object(son_id).son_account; + puo.proposal = proposal.id; + puo.active_approvals_to_add = {get_son_object(son_id).son_account}; + chain::signed_transaction trx = plugin.database().create_signed_transaction(plugin.get_private_key(son_id), puo); + fc::future fut = fc::async([&]() { + try { + trx.validate(); + plugin.database().push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending approval failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + }; + + const auto &idx = plugin.database().get_index_type().indices().get(); + vector proposals; + for (const auto &proposal : idx) { + proposals.push_back(proposal.id); + } + + for (const auto proposal_id : proposals) { + + const object *obj = plugin.database().find_object(proposal_id); + const chain::proposal_object *proposal_ptr = dynamic_cast(obj); + if (proposal_ptr == nullptr) { + continue; + } + const proposal_object proposal = *proposal_ptr; + + if (proposal.available_active_approvals.find(get_current_son_object().son_account) != proposal.available_active_approvals.end()) { + continue; + } + + check_approve_proposal(get_current_son_id(), proposal); + } +} + +void peerplays_sidechain_plugin_impl::create_son_down_proposals() { + auto create_son_down_proposal = [&](chain::son_id_type son_id, fc::time_point_sec last_active_ts) { + chain::database &d = plugin.database(); + const chain::global_property_object &gpo = d.get_global_properties(); + + chain::son_report_down_operation son_down_op; + son_down_op.payer = d.get_global_properties().parameters.son_account(); + son_down_op.son_id = son_id; + son_down_op.down_ts = last_active_ts; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(op_wrapper(son_down_op)); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(d.head_block_time().sec_since_epoch() + lifetime); + return proposal_op; + }; + + chain::database &d = plugin.database(); + const chain::global_property_object &gpo = d.get_global_properties(); + const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties(); + const auto &idx = d.get_index_type().indices().get(); + std::set sons_being_reported_down = d.get_sons_being_reported_down(); + chain::son_id_type my_son_id = get_current_son_id(); + for (auto son_inf : gpo.active_sons) { + if (my_son_id == son_inf.son_id || (sons_being_reported_down.find(son_inf.son_id) != sons_being_reported_down.end())) { + continue; + } + auto son_obj = idx.find(son_inf.son_id); + auto stats = son_obj->statistics(d); + fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; + fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); + int64_t down_threshold = gpo.parameters.son_down_time(); + if (((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && + ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { + ilog("Sending son down proposal for ${t} from ${s}", ("t", std::string(object_id_type(son_obj->id)))("s", std::string(object_id_type(my_son_id)))); + chain::proposal_create_operation op = create_son_down_proposal(son_inf.son_id, last_active_ts); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), op); + fc::future fut = fc::async([&]() { + try { + trx.validate(); + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending son down proposal failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } +} + +void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() { + chain::database &d = plugin.database(); + std::set sons_to_be_dereg = d.get_sons_to_be_deregistered(); + chain::son_id_type my_son_id = get_current_son_id(); + + if (sons_to_be_dereg.size() > 0) { + // We shouldn't raise proposals for the SONs for which a de-reg + // proposal is already raised. + std::set sons_being_dereg = d.get_sons_being_deregistered(); + for (auto &son : sons_to_be_dereg) { + // New SON to be deregistered + if (sons_being_dereg.find(son) == sons_being_dereg.end() && my_son_id != son) { + // Creating the de-reg proposal + auto op = d.create_son_deregister_proposal(son, get_son_object(my_son_id).son_account); + if (op.valid()) { + // Signing and pushing into the txs to be included in the block + ilog("Sending son deregister proposal for ${p} from ${s}", ("p", son)("s", my_son_id)); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), *op); + fc::future fut = fc::async([&]() { + try { + trx.validate(); + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending son deregister proposal failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } + } + } +} + +void peerplays_sidechain_plugin_impl::process_proposals() { + net_manager->process_proposals(); +} + +void peerplays_sidechain_plugin_impl::process_active_sons_change() { + net_manager->process_active_sons_change(); +} + +void peerplays_sidechain_plugin_impl::create_deposit_addresses() { + net_manager->create_deposit_addresses(); +} + +void peerplays_sidechain_plugin_impl::process_deposits() { + net_manager->process_deposits(); +} + +void peerplays_sidechain_plugin_impl::process_withdrawals() { + net_manager->process_withdrawals(); +} + +void peerplays_sidechain_plugin_impl::process_sidechain_transactions() { + net_manager->process_sidechain_transactions(); +} + +void peerplays_sidechain_plugin_impl::send_sidechain_transactions() { + net_manager->send_sidechain_transactions(); +} + +void peerplays_sidechain_plugin_impl::settle_sidechain_transactions() { + net_manager->settle_sidechain_transactions(); +} + +void peerplays_sidechain_plugin_impl::on_applied_block(const signed_block &b) { + if (first_block_skipped) { + schedule_son_processing(); + } else { + first_block_skipped = true; + } +} + +} // namespace detail + +peerplays_sidechain_plugin::peerplays_sidechain_plugin() : + my(new detail::peerplays_sidechain_plugin_impl(*this)) { +} + +peerplays_sidechain_plugin::~peerplays_sidechain_plugin() { + return; +} + +std::string peerplays_sidechain_plugin::plugin_name() const { + return "peerplays_sidechain"; +} + +void peerplays_sidechain_plugin::plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) { + my->plugin_set_program_options(cli, cfg); +} + +void peerplays_sidechain_plugin::plugin_initialize(const boost::program_options::variables_map &options) { + ilog("peerplays sidechain plugin: plugin_initialize() begin"); + my->plugin_initialize(options); + ilog("peerplays sidechain plugin: plugin_initialize() end"); +} + +void peerplays_sidechain_plugin::plugin_startup() { + ilog("peerplays sidechain plugin: plugin_startup() begin"); + my->plugin_startup(); + ilog("peerplays sidechain plugin: plugin_startup() end"); +} + +std::set &peerplays_sidechain_plugin::get_sons() { + return my->get_sons(); +} + +const son_id_type peerplays_sidechain_plugin::get_current_son_id() { + return my->get_current_son_id(); +} + +const son_object peerplays_sidechain_plugin::get_current_son_object() { + return my->get_current_son_object(); +} + +const son_object peerplays_sidechain_plugin::get_son_object(son_id_type son_id) { + return my->get_son_object(son_id); +} + +bool peerplays_sidechain_plugin::is_active_son(son_id_type son_id) { + return my->is_active_son(son_id); +} + +bool peerplays_sidechain_plugin::is_son_deregistered(son_id_type son_id) { + return my->is_son_deregistered(son_id); +} + +fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(son_id_type son_id) { + return my->get_private_key(son_id); +} + +fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(chain::public_key_type public_key) { + return my->get_private_key(public_key); +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp new file mode 100644 index 00000000..ed404efe --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -0,0 +1,569 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler::sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + plugin(_plugin), + database(_plugin.database()) { +} + +sidechain_net_handler::~sidechain_net_handler() { +} + +sidechain_type sidechain_net_handler::get_sidechain() { + return sidechain; +} + +std::vector sidechain_net_handler::get_sidechain_deposit_addresses() { + std::vector result; + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&result](const sidechain_address_object &sao) { + result.push_back(sao.deposit_address); + }); + return result; +} + +std::vector sidechain_net_handler::get_sidechain_withdraw_addresses() { + std::vector result; + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&result](const sidechain_address_object &sao) { + result.push_back(sao.withdraw_address); + }); + return result; +} + +std::string sidechain_net_handler::get_private_key(std::string public_key) { + auto private_key_itr = private_keys.find(public_key); + if (private_key_itr != private_keys.end()) { + return private_key_itr->second; + } + return std::string(); +} + +bool sidechain_net_handler::proposal_exists(int32_t operation_tag, const object_id_type &object_id, boost::optional proposal_op) { + + bool result = false; + + const auto &idx = database.get_index_type().indices().get(); + vector proposals; + for (const auto &proposal : idx) { + proposals.push_back(proposal.id); + } + + for (const auto proposal_id : proposals) { + const auto &idx = database.get_index_type().indices().get(); + const auto po = idx.find(proposal_id); + if (po != idx.end()) { + + int32_t op_idx_0 = -1; + chain::operation op_obj_idx_0; + + if (po->proposed_transaction.operations.size() >= 1) { + op_idx_0 = po->proposed_transaction.operations[0].which(); + op_obj_idx_0 = po->proposed_transaction.operations[0]; + } + + switch (op_idx_0) { + case chain::operation::tag::value: { + result = (op_obj_idx_0.get().son_wallet_id == object_id); + break; + } + + case chain::operation::tag::value: { + result = (op_obj_idx_0.get().son_wallet_deposit_id == object_id); + break; + } + + case chain::operation::tag::value: { + result = (op_obj_idx_0.get().son_wallet_withdraw_id == object_id); + break; + } + + case chain::operation::tag::value: { + if (proposal_op) { + chain::operation proposal_op_obj_0 = proposal_op.get(); + result = ((proposal_op_obj_0.get().sidechain_transaction_id == op_obj_idx_0.get().sidechain_transaction_id) && + (proposal_op_obj_0.get().signer == op_obj_idx_0.get().signer) && + (proposal_op_obj_0.get().signature == op_obj_idx_0.get().signature)); + } + break; + } + + default: + return false; + } + } + + if (result) { + break; + } + } + return result; +} + +bool sidechain_net_handler::signer_expected(const sidechain_transaction_object &sto, son_id_type signer) { + bool expected = false; + for (auto signature : sto.signatures) { + if (signature.first == signer) { + expected = signature.second.empty(); + } + } + return expected; +} + +bool sidechain_net_handler::approve_proposal(const proposal_id_type &proposal_id, const son_id_type &son_id) { + + proposal_update_operation op; + op.fee_paying_account = plugin.get_son_object(son_id).son_account; + op.proposal = proposal_id; + op.active_approvals_to_add = {plugin.get_son_object(son_id).son_account}; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(son_id), op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending approval from ${son_id} for proposal ${proposal_id} failed with exception ${e}", + ("son_id", son_id)("proposal_id", proposal_id)("e", e.what())); + return false; + } +} + +void sidechain_net_handler::sidechain_event_data_received(const sidechain_event_data &sed) { + ilog("sidechain_event_data:"); + ilog(" timestamp: ${timestamp}", ("timestamp", sed.timestamp)); + ilog(" block_num: ${block_num}", ("block_num", sed.block_num)); + ilog(" sidechain: ${sidechain}", ("sidechain", sed.sidechain)); + ilog(" sidechain_uid: ${uid}", ("uid", sed.sidechain_uid)); + ilog(" sidechain_transaction_id: ${transaction_id}", ("transaction_id", sed.sidechain_transaction_id)); + ilog(" sidechain_from: ${from}", ("from", sed.sidechain_from)); + ilog(" sidechain_to: ${to}", ("to", sed.sidechain_to)); + ilog(" sidechain_currency: ${currency}", ("currency", sed.sidechain_currency)); + ilog(" sidechain_amount: ${amount}", ("amount", sed.sidechain_amount)); + ilog(" peerplays_from: ${peerplays_from}", ("peerplays_from", sed.peerplays_from)); + ilog(" peerplays_to: ${peerplays_to}", ("peerplays_to", sed.peerplays_to)); + ilog(" peerplays_asset: ${peerplays_asset}", ("peerplays_asset", sed.peerplays_asset)); + + const chain::global_property_object &gpo = database.get_global_properties(); + + asset_id_type btc_asset_id = database.get_global_properties().parameters.btc_asset(); + std::string btc_asset_id_str = fc::to_string(btc_asset_id.space_id) + "." + + fc::to_string(btc_asset_id.type_id) + "." + + fc::to_string((uint64_t)btc_asset_id.instance); + +#ifdef ENABLE_PEERPLAYS_ASSET_DEPOSITS + // Accepts BTC and peerplays asset deposits + bool deposit_condition = ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare(btc_asset_id_str) != 0)); + bool withdraw_condition = ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare(btc_asset_id_str) == 0)); +#else + // Accepts BTC deposits only + bool deposit_condition = ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare("BTC") == 0)); + bool withdraw_condition = ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare(btc_asset_id_str) == 0)); +#endif + + // Deposit request + if (deposit_condition) { + + for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_active_son(son_id)) { + + son_wallet_deposit_create_operation op; + op.payer = plugin.get_son_object(son_id).son_account; + op.son_id = son_id; + op.timestamp = sed.timestamp; + op.block_num = sed.block_num; + op.sidechain = sed.sidechain; + op.sidechain_uid = sed.sidechain_uid; + op.sidechain_transaction_id = sed.sidechain_transaction_id; + op.sidechain_from = sed.sidechain_from; + op.sidechain_to = sed.sidechain_to; + op.sidechain_currency = sed.sidechain_currency; + op.sidechain_amount = sed.sidechain_amount; + op.peerplays_from = sed.peerplays_from; + op.peerplays_to = sed.peerplays_to; + op.peerplays_asset = sed.peerplays_asset; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(son_id), op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending son wallet deposit create operation by ${son} failed with exception ${e}", ("son", son_id)("e", e.what())); + } + } + } + return; + } + + // Withdrawal request + if (withdraw_condition) { + // BTC Payout only (for now) + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sed.peerplays_from, sidechain_type::bitcoin, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) + return; + + for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_active_son(son_id)) { + + son_wallet_withdraw_create_operation op; + op.payer = plugin.get_son_object(son_id).son_account; + op.son_id = son_id; + op.timestamp = sed.timestamp; + op.block_num = sed.block_num; + op.sidechain = sed.sidechain; + op.peerplays_uid = sed.sidechain_uid; + op.peerplays_transaction_id = sed.sidechain_transaction_id; + op.peerplays_from = sed.peerplays_from; + op.peerplays_asset = sed.peerplays_asset; + // BTC payout only (for now) + op.withdraw_sidechain = sidechain_type::bitcoin; + op.withdraw_address = addr_itr->withdraw_address; + op.withdraw_currency = "BTC"; + price btc_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; + op.withdraw_amount = sed.peerplays_asset.amount * btc_price.quote.amount / btc_price.base.amount; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(son_id), op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending son wallet withdraw create operation by ${son} failed with exception ${e}", ("son", son_id)("e", e.what())); + } + } + } + return; + } +} + +void sidechain_net_handler::process_proposals() { + const auto &idx = database.get_index_type().indices().get(); + vector proposals; + for (const auto &proposal : idx) { + proposals.push_back(proposal.id); + } + + for (const auto proposal_id : proposals) { + const auto &idx = database.get_index_type().indices().get(); + const auto po = idx.find(proposal_id); + if (po != idx.end()) { + + if (po->available_active_approvals.find(plugin.get_current_son_object().son_account) != po->available_active_approvals.end()) { + continue; + } + + bool should_process = false; + + int32_t op_idx_0 = -1; + chain::operation op_obj_idx_0; + + if (po->proposed_transaction.operations.size() >= 1) { + op_idx_0 = po->proposed_transaction.operations[0].which(); + op_obj_idx_0 = po->proposed_transaction.operations[0]; + } + + int32_t op_idx_1 = -1; + chain::operation op_obj_idx_1; + + if (po->proposed_transaction.operations.size() >= 2) { + op_idx_1 = po->proposed_transaction.operations[1].which(); + op_obj_idx_1 = po->proposed_transaction.operations[1]; + } + + switch (op_idx_0) { + case chain::operation::tag::value: { + should_process = (op_obj_idx_0.get().sidechain == sidechain); + break; + } + + case chain::operation::tag::value: { + son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get().son_wallet_deposit_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swdo = idx.find(swdo_id); + if (swdo != idx.end()) { + should_process = (swdo->sidechain == sidechain); + } + break; + } + + case chain::operation::tag::value: { + son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get().son_wallet_withdraw_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swwo = idx.find(swwo_id); + if (swwo != idx.end()) { + should_process = (swwo->withdraw_sidechain == sidechain); + } + break; + } + + case chain::operation::tag::value: { + sidechain_transaction_id_type st_id = op_obj_idx_0.get().sidechain_transaction_id; + son_id_type signer = op_obj_idx_0.get().signer; + const auto &idx = database.get_index_type().indices().get(); + const auto sto = idx.find(st_id); + if (sto != idx.end()) { + should_process = ((sto->sidechain == sidechain) && (sto->status == sidechain_transaction_status::valid) && signer_expected(*sto, signer)); + } + break; + } + + case chain::operation::tag::value: { + sidechain_transaction_id_type st_id = op_obj_idx_0.get().sidechain_transaction_id; + const auto &idx = database.get_index_type().indices().get(); + const auto sto = idx.find(st_id); + if (sto != idx.end()) { + should_process = (sto->sidechain == sidechain); + } + break; + } + + default: + should_process = false; + elog("=================================================="); + elog("Proposal not processed ${po}", ("po", *po)); + elog("=================================================="); + } + + if (should_process) { + bool should_approve = process_proposal(*po); + if (should_approve) { + approve_proposal(po->id, plugin.get_current_son_id()); + } + } + } + } +} + +void sidechain_net_handler::process_active_sons_change() { + process_primary_wallet(); +} + +void sidechain_net_handler::create_deposit_addresses() { + if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + return; + } + process_sidechain_addresses(); +} + +void sidechain_net_handler::process_deposits() { + if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + return; + } + + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_deposit_object &swdo) { + if (swdo.id == object_id_type(0, 0, 0)) { + return; + } + //Ignore the deposits which are not valid anymore, considered refunds. + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, swdo.sidechain_to, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) { + return; + } + + ilog("Deposit to process: ${swdo}", ("swdo", swdo)); + + bool process_deposit_result = process_deposit(swdo); + + if (!process_deposit_result) { + wlog("Deposit not processed: ${swdo}", ("swdo", swdo)); + return; + } + }); +} + +void sidechain_net_handler::process_withdrawals() { + if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + return; + } + + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_withdraw_object &swwo) { + if (swwo.id == object_id_type(0, 0, 0)) { + return; + } + + ilog("Withdraw to process: ${swwo}", ("swwo", swwo)); + + bool process_withdrawal_result = process_withdrawal(swwo); + + if (!process_withdrawal_result) { + wlog("Withdraw not processed: ${swwo}", ("swwo", swwo)); + return; + } + }); +} + +void sidechain_net_handler::process_sidechain_transactions() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::valid)); + + std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + if ((sto.id == object_id_type(0, 0, 0)) || !signer_expected(sto, plugin.get_current_son_id())) { + return; + } + + ilog("Sidechain transaction to process: ${sto}", ("sto", sto.id)); + + std::string processed_sidechain_tx = process_sidechain_transaction(sto); + + if (processed_sidechain_tx.empty()) { + wlog("Sidechain transaction not processed: ${sto}", ("sto", sto.id)); + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + sidechain_transaction_sign_operation sts_op; + sts_op.signer = plugin.get_current_son_id(); + sts_op.payer = gpo.parameters.son_account(); + sts_op.sidechain_transaction_id = sto.id; + sts_op.signature = processed_sidechain_tx; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + proposal_op.proposed_ops.emplace_back(sts_op); + + if (proposal_exists(chain::operation::tag::value, sto.id, proposal_op.proposed_ops[0].op)) { + return; + } + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for sidechain transaction sign operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::send_sidechain_transactions() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::complete)); + + std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + if (sto.id == object_id_type(0, 0, 0)) { + return; + } + + ilog("Sidechain transaction to send: ${sto}", ("sto", sto.id)); + + std::string sidechain_transaction = send_sidechain_transaction(sto); + + if (sidechain_transaction.empty()) { + wlog("Sidechain transaction not sent: ${sto}", ("sto", sto.id)); + return; + } + + sidechain_transaction_send_operation sts_op; + sts_op.payer = plugin.get_current_son_object().son_account; + sts_op.sidechain_transaction_id = sto.id; + sts_op.sidechain_transaction = sidechain_transaction; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), sts_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for sidechain transaction send operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::settle_sidechain_transactions() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::sent)); + + std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + if (sto.id == object_id_type(0, 0, 0)) { + return; + } + + if (proposal_exists(chain::operation::tag::value, sto.id)) { + return; + } + + ilog("Sidechain transaction to settle: ${sto}", ("sto", sto.id)); + + int64_t settle_amount = settle_sidechain_transaction(sto); + + if (settle_amount < 0) { + wlog("Sidechain transaction not settled: ${sto}", ("sto", sto.id)); + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + sidechain_transaction_settle_operation sts_op; + sts_op.payer = gpo.parameters.son_account(); + sts_op.sidechain_transaction_id = sto.id; + proposal_op.proposed_ops.emplace_back(sts_op); + + if (settle_amount != 0) { + if (sto.object_id.is()) { + asset_issue_operation ai_op; + ai_op.fee = asset(2001000); + ai_op.issuer = gpo.parameters.son_account(); + ai_op.asset_to_issue = asset(settle_amount, database.get_global_properties().parameters.btc_asset()); + ai_op.issue_to_account = database.get(sto.object_id).peerplays_from; + proposal_op.proposed_ops.emplace_back(ai_op); + } + + if (sto.object_id.is()) { + asset_reserve_operation ar_op; + ar_op.fee = asset(2001000); + ar_op.payer = gpo.parameters.son_account(); + ar_op.amount_to_reserve = asset(settle_amount, database.get_global_properties().parameters.btc_asset()); + proposal_op.proposed_ops.emplace_back(ar_op); + } + } + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for sidechain transaction settle operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp new file mode 100644 index 00000000..48f641c3 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -0,0 +1,1911 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +// ============================================================================= + +bitcoin_rpc_client::bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password) : + ip(_ip), + rpc_port(_rpc), + user(_user), + password(_password), + wallet(_wallet), + wallet_password(_wallet_password) { + authorization.key = "Authorization"; + authorization.val = "Basic " + fc::base64_encode(user + ":" + password); +} + +std::string bitcoin_rpc_client::addmultisigaddress(const uint32_t nrequired, const std::vector public_keys) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"addmultisigaddress\", " + "\"method\": \"addmultisigaddress\", \"params\": ["); + std::string params = std::to_string(nrequired) + ", ["; + std::string pubkeys = ""; + for (std::string pubkey : public_keys) { + if (!pubkeys.empty()) { + pubkeys = pubkeys + ","; + } + pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); + } + params = params + pubkeys + std::string("]"); + body = body + params + std::string(", null, \"bech32\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::combinepsbt(const vector &psbts) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"combinepsbt\", \"method\": " + "\"combinepsbt\", \"params\": [["); + std::string params = ""; + for (std::string psbt : psbts) { + if (!params.empty()) { + params = params + ","; + } + params = params + std::string("\"") + psbt + std::string("\""); + } + body = body + params + std::string("]] }"); + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createmultisig(const uint32_t nrequired, const std::vector public_keys) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createmultisig\", " + "\"method\": \"createmultisig\", \"params\": ["); + std::string params = std::to_string(nrequired) + ", ["; + std::string pubkeys = ""; + for (std::string pubkey : public_keys) { + if (!pubkeys.empty()) { + pubkeys = pubkeys + ","; + } + pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); + } + params = params + pubkeys + std::string("]"); + body = body + params + std::string(", \"p2sh-segwit\" ] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createpsbt(const std::vector &ins, const fc::flat_map outs) { + std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createpsbt\", " + "\"method\": \"createpsbt\", \"params\": ["); + body += "["; + bool first = true; + for (const auto &entry : ins) { + if (!first) + body += ","; + body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; + first = false; + } + body += "],["; + first = true; + for (const auto &entry : outs) { + if (!first) + body += ","; + body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; + first = false; + } + body += std::string("]] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + return json.get("result"); + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createrawtransaction(const std::vector &ins, const fc::flat_map outs) { + std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createrawtransaction\", " + "\"method\": \"createrawtransaction\", \"params\": ["); + body += "["; + bool first = true; + for (const auto &entry : ins) { + if (!first) + body += ","; + body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; + first = false; + } + body += "],["; + first = true; + for (const auto &entry : outs) { + if (!first) + body += ","; + body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; + first = false; + } + body += std::string("]] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + return json.get("result"); + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createwallet(const std::string &wallet_name) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createwallet\", \"method\": " + "\"createwallet\", \"params\": [\"" + + wallet_name + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::decodepsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decodepsbt\", \"method\": " + "\"decodepsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::decoderawtransaction(std::string const &tx_hex) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decoderawtransaction\", \"method\": " + "\"decoderawtransaction\", \"params\": [\"" + + tx_hex + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::encryptwallet(const std::string &passphrase) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"encryptwallet\", \"method\": " + "\"encryptwallet\", \"params\": [\"" + + passphrase + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +uint64_t bitcoin_rpc_client::estimatesmartfee(uint16_t conf_target) { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimatesmartfee\", " + "\"method\": \"estimatesmartfee\", \"params\": [" + + std::to_string(conf_target) + std::string("] }")); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return 0; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + auto json_result = json.get_child("result"); + if (json_result.find("feerate") != json_result.not_found()) { + auto feerate_str = json_result.get("feerate"); + feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); + return std::stoll(feerate_str); + } + + if (json_result.find("errors") != json_result.not_found()) { + wlog("Bitcoin RPC call ${function} with body ${body} executed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return 20000; +} + +std::string bitcoin_rpc_client::finalizepsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"finalizepsbt\", \"method\": " + "\"finalizepsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getaddressinfo(const std::string &address) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getaddressinfo\", \"method\": " + "\"getaddressinfo\", \"params\": [\"" + + address + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t verbosity) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblock\", \"method\": " + "\"getblock\", \"params\": [\"" + + block_hash + "\", " + std::to_string(verbosity) + "] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getrawtransaction(const std::string &txid, const bool verbose) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getrawtransaction\", \"method\": " + "\"getrawtransaction\", \"params\": ["); + + std::string params = "\"" + txid + "\", " + (verbose ? "true" : "false"); + body = body + params + "] }"; + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bool include_watch_only) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"gettransaction\", \"method\": " + "\"gettransaction\", \"params\": ["); + + std::string params = "\"" + txid + "\", " + (include_watch_only ? "true" : "false"); + body = body + params + "] }"; + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getblockchaininfo() { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblockchaininfo\", \"method\": " + "\"getblockchaininfo\", \"params\": [] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +void bitcoin_rpc_client::importaddress(const std::string &address_or_script, const std::string &label, const bool rescan, const bool p2sh) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " + "\"method\": \"importaddress\", \"params\": ["); + + std::string params = "\"" + address_or_script + "\", " + + "\"" + label + "\", " + + (rescan ? "true" : "false") + ", " + + (p2sh ? "true" : "false"); + body = body + params + "] }"; + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return; + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } +} + +std::vector bitcoin_rpc_client::listunspent(const uint32_t minconf, const uint32_t maxconf) { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " + "\"listunspent\", \"params\": [" + + std::to_string(minconf) + "," + std::to_string(maxconf) + "] }"); + + const auto reply = send_post_request(body); + + std::vector result; + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return result; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return result; +} + +std::vector bitcoin_rpc_client::listunspent_by_address_and_amount(const std::string &address, double minimum_amount, const uint32_t minconf, const uint32_t maxconf) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " + "\"listunspent\", \"params\": [" + + std::to_string(minconf) + "," + std::to_string(maxconf) + ","); + body += std::string("[\""); + body += address; + body += std::string("\"],true,{\"minimumAmount\":"); + body += std::to_string(minimum_amount); + body += std::string("} ] }"); + + const auto reply = send_post_request(body); + + std::vector result; + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return result; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return result; +} + +std::string bitcoin_rpc_client::loadwallet(const std::string &filename) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"loadwallet\", \"method\": " + "\"loadwallet\", \"params\": [\"" + + filename + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::sendrawtransaction(const std::string &tx_hex) { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"sendrawtransaction\", " + "\"method\": \"sendrawtransaction\", \"params\": [") + + std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return json.get("result"); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::signrawtransactionwithwallet(const std::string &tx_hash) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"signrawtransactionwithwallet\", " + "\"method\": \"signrawtransactionwithwallet\", \"params\": ["); + std::string params = "\"" + tx_hash + "\""; + body = body + params + std::string("]}"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::unloadwallet(const std::string &filename) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"unloadwallet\", \"method\": " + "\"unloadwallet\", \"params\": [\"" + + filename + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +//std::string bitcoin_rpc_client::walletlock() { +// std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " +// "\"walletlock\", \"params\": [] }"); +// +// const auto reply = send_post_request(body); +// +// if (reply.body.empty()) { +// wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); +// return ""; +// } +// +// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); +// boost::property_tree::ptree json; +// boost::property_tree::read_json(ss, json); +// +// if (reply.status == 200) { +// std::stringstream ss; +// boost::property_tree::json_parser::write_json(ss, json.get_child("result")); +// return ss.str(); +// } +// +// if (json.count("error") && !json.get_child("error").empty()) { +// wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); +// } +// return ""; +//} + +std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletprocesspsbt\", \"method\": " + "\"walletprocesspsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +//bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { +// std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " +// "\"walletpassphrase\", \"params\": [\"" + +// passphrase + "\", " + std::to_string(timeout) + "] }"); +// +// const auto reply = send_post_request(body); +// +// if (reply.body.empty()) { +// wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); +// return false; +// } +// +// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); +// boost::property_tree::ptree json; +// boost::property_tree::read_json(ss, json); +// +// if (reply.status == 200) { +// return true; +// } +// +// if (json.count("error") && !json.get_child("error").empty()) { +// wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); +// } +// return false; +//} + +fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) { + fc::http::connection conn; + conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); + + std::string url = "http://" + ip + ":" + std::to_string(rpc_port); + + if (wallet.length() > 0) { + url = url + "/wallet/" + wallet; + } + + fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); + + if (show_log) { + ilog("### Request URL: ${url}", ("url", url)); + ilog("### Request: ${body}", ("body", body)); + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + ilog("### Response: ${ss}", ("ss", ss.str())); + } + + return reply; +} + +// ============================================================================= + +zmq_listener::zmq_listener(std::string _ip, uint32_t _zmq) : + ip(_ip), + zmq_port(_zmq), + ctx(1), + socket(ctx, ZMQ_SUB) { + std::thread(&zmq_listener::handle_zmq, this).detach(); +} + +std::vector zmq_listener::receive_multipart() { + std::vector msgs; + + int32_t more; + size_t more_size = sizeof(more); + while (true) { + zmq::message_t msg; + socket.recv(&msg, 0); + socket.getsockopt(ZMQ_RCVMORE, &more, &more_size); + + if (!more) + break; + msgs.push_back(std::move(msg)); + } + + return msgs; +} + +void zmq_listener::handle_zmq() { + int linger = 0; + socket.setsockopt(ZMQ_SUBSCRIBE, "hashblock", 9); + socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); + //socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); + //socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); + //socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); + socket.connect("tcp://" + ip + ":" + std::to_string(zmq_port)); + + while (true) { + try { + auto msg = receive_multipart(); + const auto header = std::string(static_cast(msg[0].data()), msg[0].size()); + const auto block_hash = boost::algorithm::hex(std::string(static_cast(msg[1].data()), msg[1].size())); + event_received(block_hash); + } catch (zmq::error_t &e) { + } + } +} + +// ============================================================================= + +sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain_net_handler(_plugin, options) { + sidechain = sidechain_type::bitcoin; + + ip = options.at("bitcoin-node-ip").as(); + zmq_port = options.at("bitcoin-node-zmq-port").as(); + rpc_port = options.at("bitcoin-node-rpc-port").as(); + rpc_user = options.at("bitcoin-node-rpc-user").as(); + rpc_password = options.at("bitcoin-node-rpc-password").as(); + wallet = ""; + if (options.count("bitcoin-wallet")) { + wallet = options.at("bitcoin-wallet").as(); + } + wallet_password = ""; + if (options.count("bitcoin-wallet-password")) { + wallet_password = options.at("bitcoin-wallet-password").as(); + } + + if (options.count("bitcoin-private-key")) { + const std::vector pub_priv_keys = options["bitcoin-private-key"].as>(); + for (const std::string &itr_key_pair : pub_priv_keys) { + auto key_pair = graphene::app::dejsonify>(itr_key_pair, 5); + ilog("Bitcoin Public Key: ${public}", ("public", key_pair.first)); + if (!key_pair.first.length() || !key_pair.second.length()) { + FC_THROW("Invalid public private key pair."); + } + private_keys[key_pair.first] = key_pair.second; + } + } + + fc::http::connection conn; + try { + conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); + } catch (fc::exception e) { + elog("No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", ip)("port", rpc_port)); + FC_ASSERT(false); + } + + bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(ip, rpc_port, rpc_user, rpc_password, wallet, wallet_password)); + if (!wallet.empty()) { + bitcoin_client->loadwallet(wallet); + } + + std::string blockchain_info = bitcoin_client->getblockchaininfo(); + std::stringstream bci_ss(std::string(blockchain_info.begin(), blockchain_info.end())); + boost::property_tree::ptree bci_json; + boost::property_tree::read_json(bci_ss, bci_json); + using namespace bitcoin; + network_type = bitcoin_address::network::mainnet; + if (bci_json.count("chain")) { + std::string chain = bci_json.get("chain"); + if (chain == "test") { + network_type = bitcoin_address::network::testnet; + } else if (chain == "regtest") { + network_type = bitcoin_address::network::regtest; + } + } + + listener = std::unique_ptr(new zmq_listener(ip, zmq_port)); + listener->event_received.connect([this](const std::string &event_data) { + std::thread(&sidechain_net_handler_bitcoin::handle_event, this, event_data).detach(); + }); + + database.changed_objects.connect([this](const vector &ids, const flat_set &accounts) { + on_changed_objects(ids, accounts); + }); +} + +sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { + try { + if (on_changed_objects_task.valid()) { + on_changed_objects_task.cancel_and_wait(__FUNCTION__); + } + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } +} + +bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) { + + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + + bool should_approve = false; + + const chain::global_property_object &gpo = database.get_global_properties(); + + int32_t op_idx_0 = -1; + chain::operation op_obj_idx_0; + + if (po.proposed_transaction.operations.size() >= 1) { + op_idx_0 = po.proposed_transaction.operations[0].which(); + op_obj_idx_0 = po.proposed_transaction.operations[0]; + } + + int32_t op_idx_1 = -1; + chain::operation op_obj_idx_1; + + if (po.proposed_transaction.operations.size() >= 2) { + op_idx_1 = po.proposed_transaction.operations[1].which(); + op_obj_idx_1 = po.proposed_transaction.operations[1]; + } + + switch (op_idx_0) { + + case chain::operation::tag::value: { + bool address_ok = false; + bool transaction_ok = false; + std::string new_pw_address = ""; + son_wallet_id_type swo_id = op_obj_idx_0.get().son_wallet_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swo = idx.find(swo_id); + if (swo != idx.end()) { + + auto active_sons = gpo.active_sons; + vector wallet_sons = swo->sons; + + bool son_sets_equal = (active_sons.size() == wallet_sons.size()); + + if (son_sets_equal) { + for (size_t i = 0; i < active_sons.size(); i++) { + son_sets_equal = son_sets_equal && active_sons.at(i) == wallet_sons.at(i); + } + } + + if (son_sets_equal) { + auto active_sons = gpo.active_sons; + vector son_pubkeys_bitcoin; + for (const son_info &si : active_sons) { + son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); + } + + string reply_str = create_primary_wallet_address(active_sons); + + std::stringstream active_pw_ss(reply_str); + boost::property_tree::ptree active_pw_pt; + boost::property_tree::read_json(active_pw_ss, active_pw_pt); + if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) { + std::stringstream res; + boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result")); + new_pw_address = active_pw_pt.get("result.address"); + + address_ok = (op_obj_idx_0.get().address == res.str()); + } + } + + if (po.proposed_transaction.operations.size() >= 2) { + object_id_type object_id = op_obj_idx_1.get().object_id; + std::string op_tx_str = op_obj_idx_1.get().transaction; + + const auto &st_idx = database.get_index_type().indices().get(); + const auto st = st_idx.find(object_id); + if (st == st_idx.end()) { + + std::string tx_str = ""; + + if (object_id.is()) { + const auto &idx = database.get_index_type().indices().get(); + const auto swo = idx.find(object_id); + if (swo != idx.end()) { + tx_str = create_primary_wallet_transaction(*swo, new_pw_address); + } + } + + transaction_ok = (op_tx_str == tx_str); + } + } else { + transaction_ok = true; + } + } + + should_approve = address_ok && + transaction_ok; + break; + } + + case chain::operation::tag::value: { + bool process_ok = false; + bool transaction_ok = false; + son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get().son_wallet_deposit_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swdo = idx.find(swdo_id); + if (swdo != idx.end()) { + + std::string swdo_txid = swdo->sidechain_transaction_id; + std::string swdo_address = swdo->sidechain_to; + uint64_t swdo_amount = swdo->sidechain_amount.value; + uint64_t swdo_vout = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-") + 1)); + + std::string tx_str = bitcoin_client->getrawtransaction(swdo_txid, true); + std::stringstream tx_ss(tx_str); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(tx_ss, tx_json); + + if (tx_json.count("error") && tx_json.get_child("error").empty()) { + + std::string tx_txid = tx_json.get("result.txid"); + uint32_t tx_confirmations = tx_json.get("result.confirmations"); + std::string tx_address = ""; + int64_t tx_amount = -1; + int64_t tx_vout = -1; + + for (auto &input : tx_json.get_child("result.vout")) { + std::string tx_vout_s = input.second.get("n"); + tx_vout = std::stoll(tx_vout_s); + if (tx_vout == swdo_vout) { + for (auto &address : input.second.get_child("scriptPubKey.addresses")) { + if (address.second.data() == swdo_address) { + tx_address = address.second.data(); + break; + } + } + std::string tx_amount_s = input.second.get("value"); + tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); + tx_amount = std::stoll(tx_amount_s); + break; + } + } + + process_ok = (swdo_txid == tx_txid) && + (swdo_address == tx_address) && + (swdo_amount == tx_amount) && + (swdo_vout == tx_vout) && + (gpo.parameters.son_bitcoin_min_tx_confirmations() <= tx_confirmations); + } + + object_id_type object_id = op_obj_idx_1.get().object_id; + std::string op_tx_str = op_obj_idx_1.get().transaction; + + const auto &st_idx = database.get_index_type().indices().get(); + const auto st = st_idx.find(object_id); + if (st == st_idx.end()) { + + std::string tx_str = ""; + + if (object_id.is()) { + const auto &idx = database.get_index_type().indices().get(); + const auto swdo = idx.find(object_id); + if (swdo != idx.end()) { + tx_str = create_deposit_transaction(*swdo); + } + } + + transaction_ok = (op_tx_str == tx_str); + } + } + + should_approve = process_ok && + transaction_ok; + break; + } + + case chain::operation::tag::value: { + bool process_ok = false; + bool transaction_ok = false; + son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get().son_wallet_withdraw_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swwo = idx.find(swwo_id); + if (swwo != idx.end()) { + + uint32_t swwo_block_num = swwo->block_num; + std::string swwo_peerplays_transaction_id = swwo->peerplays_transaction_id; + uint32_t swwo_op_idx = std::stoll(swwo->peerplays_uid.substr(swwo->peerplays_uid.find_last_of("-") + 1)); + + const auto &block = database.fetch_block_by_number(swwo_block_num); + + for (const auto &tx : block->transactions) { + if (tx.id().str() == swwo_peerplays_transaction_id) { + operation op = tx.operations[swwo_op_idx]; + transfer_operation t_op = op.get(); + + price asset_price = database.get(t_op.amount.asset_id).options.core_exchange_rate; + asset peerplays_asset = asset(t_op.amount.amount * asset_price.base.amount / asset_price.quote.amount); + + process_ok = (t_op.to == gpo.parameters.son_account()) && + (swwo->peerplays_from == t_op.from) && + (swwo->peerplays_asset == peerplays_asset); + break; + } + } + + object_id_type object_id = op_obj_idx_1.get().object_id; + std::string op_tx_str = op_obj_idx_1.get().transaction; + + const auto &st_idx = database.get_index_type().indices().get(); + const auto st = st_idx.find(object_id); + if (st == st_idx.end()) { + + std::string tx_str = ""; + + if (object_id.is()) { + const auto &idx = database.get_index_type().indices().get(); + const auto swwo = idx.find(object_id); + if (swwo != idx.end()) { + tx_str = create_withdrawal_transaction(*swwo); + } + } + + transaction_ok = (op_tx_str == tx_str); + } + } + + should_approve = process_ok && + transaction_ok; + break; + } + + case chain::operation::tag::value: { + using namespace bitcoin; + should_approve = true; + son_id_type signer = op_obj_idx_0.get().signer; + std::string signature = op_obj_idx_0.get().signature; + sidechain_transaction_id_type sidechain_transaction_id = op_obj_idx_0.get().sidechain_transaction_id; + std::vector in_amounts; + std::string tx_hex; + std::string redeem_script; + const auto &st_idx = database.get_index_type().indices().get(); + const auto sto = st_idx.find(sidechain_transaction_id); + if (sto == st_idx.end()) { + should_approve = false; + break; + } + + const auto &s_idx = database.get_index_type().indices().get(); + const auto son = s_idx.find(signer); + if (son == s_idx.end()) { + should_approve = false; + break; + } + + read_transaction_data(sto->transaction, tx_hex, in_amounts, redeem_script); + bitcoin_transaction tx = unpack(parse_hex(tx_hex)); + bitcoin::bytes pubkey = parse_hex(son->sidechain_public_keys.at(sidechain_type::bitcoin)); + vector sigs = read_byte_arrays_from_string(signature); + for (size_t i = 0; i < tx.vin.size(); i++) { + const auto &sighash_str = get_signature_hash(tx, parse_hex(redeem_script), static_cast(in_amounts[i]), i, 1, true).str(); + const bitcoin::bytes &sighash_hex = parse_hex(sighash_str); + should_approve = should_approve && verify_sig(sigs[i], pubkey, sighash_hex, btc_context()); + } + break; + } + + case chain::operation::tag::value: { + should_approve = true; + break; + } + + default: + should_approve = false; + elog("=================================================="); + elog("Proposal not considered for approval ${po}", ("po", po)); + elog("=================================================="); + } + + return should_approve; +} + +void sidechain_net_handler_bitcoin::process_primary_wallet() { + const auto &swi = database.get_index_type().indices().get(); + const auto &active_sw = swi.rbegin(); + if (active_sw != swi.rend()) { + + if ((active_sw->addresses.find(sidechain_type::bitcoin) == active_sw->addresses.end()) || + (active_sw->addresses.at(sidechain_type::bitcoin).empty())) { + + if (proposal_exists(chain::operation::tag::value, active_sw->id)) { + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + auto active_sons = gpo.active_sons; + string reply_str = create_primary_wallet_address(active_sons); + + std::stringstream active_pw_ss(reply_str); + boost::property_tree::ptree active_pw_pt; + boost::property_tree::read_json(active_pw_ss, active_pw_pt); + if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) { + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + std::stringstream res; + boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result")); + + son_wallet_update_operation swu_op; + swu_op.payer = gpo.parameters.son_account(); + swu_op.son_wallet_id = active_sw->id; + swu_op.sidechain = sidechain_type::bitcoin; + swu_op.address = res.str(); + + proposal_op.proposed_ops.emplace_back(swu_op); + + const auto &prev_sw = std::next(active_sw); + if (prev_sw != swi.rend()) { + std::string new_pw_address = active_pw_pt.get("result.address"); + std::string tx_str = create_primary_wallet_transaction(*prev_sw, new_pw_address); + if (!tx_str.empty()) { + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = prev_sw->id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = prev_sw->sons; + proposal_op.proposed_ops.emplace_back(stc_op); + } + } + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what())); + return; + } + } + } + } +} + +void sidechain_net_handler_bitcoin::process_sidechain_addresses() { + using namespace bitcoin; + + const chain::global_property_object &gpo = database.get_global_properties(); + std::vector> pubkeys; + for (auto &son : gpo.active_sons) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkeys.push_back(std::make_pair(pubkey, son.weight)); + } + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&](const sidechain_address_object &sao) { + if (sao.expires == time_point_sec::maximum()) { + auto usr_pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(sao.deposit_public_key))); + + btc_one_or_weighted_multisig_address addr(usr_pubkey, pubkeys, network_type); + std::string address_data = "{ \"redeemScript\": \"" + fc::to_hex(addr.get_redeem_script()) + + "\", \"witnessScript\": \"" + fc::to_hex(addr.get_witness_script()) + "\" }"; + + if (addr.get_address() != sao.deposit_address) { + sidechain_address_update_operation op; + op.payer = plugin.get_current_son_object().son_account; + op.sidechain_address_id = sao.id; + op.sidechain_address_account = sao.sidechain_address_account; + op.sidechain = sao.sidechain; + op.deposit_public_key = sao.deposit_public_key; + op.deposit_address = addr.get_address(); + op.deposit_address_data = address_data; + op.withdraw_public_key = sao.withdraw_public_key; + op.withdraw_address = sao.withdraw_address; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + } + }); +} + +bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_object &swdo) { + + if (proposal_exists(chain::operation::tag::value, swdo.id)) { + return false; + } + + std::string tx_str = create_deposit_transaction(swdo); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + son_wallet_deposit_process_operation swdp_op; + swdp_op.payer = gpo.parameters.son_account(); + swdp_op.son_wallet_deposit_id = swdo.id; + proposal_op.proposed_ops.emplace_back(swdp_op); + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swdo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = gpo.active_sons; + proposal_op.proposed_ops.emplace_back(stc_op); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw_object &swwo) { + + if (proposal_exists(chain::operation::tag::value, swwo.id)) { + return false; + } + + std::string tx_str = create_withdrawal_transaction(swwo); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + son_wallet_withdraw_process_operation swwp_op; + swwp_op.payer = gpo.parameters.son_account(); + swwp_op.son_wallet_withdraw_id = swwo.id; + proposal_op.proposed_ops.emplace_back(swwp_op); + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swwo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = gpo.active_sons; + proposal_op.proposed_ops.emplace_back(stc_op); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for withdraw sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +std::string sidechain_net_handler_bitcoin::process_sidechain_transaction(const sidechain_transaction_object &sto) { + return sign_transaction(sto); +} + +std::string sidechain_net_handler_bitcoin::send_sidechain_transaction(const sidechain_transaction_object &sto) { + return send_transaction(sto); +} + +int64_t sidechain_net_handler_bitcoin::settle_sidechain_transaction(const sidechain_transaction_object &sto) { + + if (sto.object_id.is()) { + return 0; + } + + int64_t settle_amount = -1; + + if (sto.sidechain_transaction.empty()) { + return settle_amount; + } + + std::string tx_str = bitcoin_client->getrawtransaction(sto.sidechain_transaction, true); + std::stringstream tx_ss(tx_str); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(tx_ss, tx_json); + + if ((tx_json.count("error")) && (!tx_json.get_child("error").empty())) { + return settle_amount; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + using namespace bitcoin; + std::vector> pubkey_weights; + for (auto si : sto.signers) { + std::string pub_key_str = si.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkey_weights.push_back(std::make_pair(pub_key, si.weight)); + } + btc_weighted_multisig_address addr(pubkey_weights, network_type); + + std::string tx_txid = tx_json.get("result.txid"); + uint32_t tx_confirmations = tx_json.get("result.confirmations"); + std::string tx_address = addr.get_address(); + int64_t tx_amount = -1; + + if (tx_confirmations >= gpo.parameters.son_bitcoin_min_tx_confirmations()) { + if (sto.object_id.is()) { + for (auto &input : tx_json.get_child("result.vout")) { + for (auto &address : input.second.get_child("scriptPubKey.addresses")) { + if (address.second.data() == tx_address) { + std::string tx_amount_s = input.second.get("value"); + tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); + tx_amount = std::stoll(tx_amount_s); + break; + } + } + } + settle_amount = tx_amount; + } + + if (sto.object_id.is()) { + auto swwo = database.get(sto.object_id); + settle_amount = swwo.withdraw_amount.value; + } + } + return settle_amount; +} + +std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const std::vector &son_pubkeys) { + using namespace bitcoin; + + std::vector> pubkey_weights; + for (auto &son : son_pubkeys) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); + } + + btc_weighted_multisig_address addr(pubkey_weights, network_type); + + std::stringstream ss; + + ss << "{\"result\": {\"address\": \"" << addr.get_address() << "\", \"redeemScript\": \"" << fc::to_hex(addr.get_redeem_script()) << "\"" + << "}, \"error\":null}"; + + std::string res = ss.str(); + return res; +} + +std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address) { + + std::stringstream prev_sw_ss(prev_swo.addresses.find(sidechain_type::bitcoin)->second); + boost::property_tree::ptree prev_sw_pt; + boost::property_tree::read_json(prev_sw_ss, prev_sw_pt); + std::string prev_pw_address = prev_sw_pt.get("address"); + std::string prev_redeem_script = prev_sw_pt.get("redeemScript"); + + if (prev_pw_address == new_sw_address) { + wlog("BTC previous and new primary wallet addresses are same. No funds moving needed [from ${prev_sw} to ${new_sw_address}]", ("prev_swo", prev_swo.id)("active_sw", new_sw_address)); + return ""; + } + + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + uint64_t total_amount = 0.0; + std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0); + + if (inputs.size() == 0) { + elog("Failed to find UTXOs to spend for ${pw}", ("pw", prev_pw_address)); + return ""; + } else { + for (const auto &utx : inputs) { + total_amount += utx.amount_; + } + + if (fee_rate >= total_amount) { + elog("Failed not enough BTC to transfer from ${fa}", ("fa", prev_pw_address)); + return ""; + } + } + + fc::flat_map outputs; + outputs[new_sw_address] = double(total_amount - fee_rate) / 100000000.0; + + return create_transaction(inputs, outputs, prev_redeem_script); +} + +std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_wallet_deposit_object &swdo) { + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return ""; + } + //Get redeem script for deposit address + std::string redeem_script = get_redeemscript_for_userdeposit(swdo.sidechain_to); + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::string pw_address = json.get("address"); + + std::string txid = swdo.sidechain_transaction_id; + std::string suid = swdo.sidechain_uid; + std::string nvout = suid.substr(suid.find_last_of("-") + 1); + uint64_t deposit_amount = swdo.sidechain_amount.value; + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + deposit_amount -= fee_rate; // Deduct minimum relay fee + double transfer_amount = (double)deposit_amount / 100000000.0; + + std::vector inputs; + fc::flat_map outputs; + + btc_txout utxo; + utxo.txid_ = txid; + utxo.out_num_ = std::stoul(nvout); + utxo.amount_ = swdo.sidechain_amount.value; + + inputs.push_back(utxo); + + outputs[pw_address] = transfer_amount; + + return create_transaction(inputs, outputs, redeem_script); +} + +std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) { + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return ""; + } + + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::string pw_address = json.get("address"); + std::string redeem_script = json.get("redeemScript"); + + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + uint64_t total_amount = 0; + std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0); + + if (inputs.size() == 0) { + elog("Failed to find UTXOs to spend for ${pw}", ("pw", pw_address)); + return ""; + } else { + for (const auto &utx : inputs) { + total_amount += utx.amount_; + } + + if ((swwo.withdraw_amount.value > total_amount) || (fee_rate >= swwo.withdraw_amount.value)) { + elog("Failed not enough BTC to spend for ${pw}", ("pw", pw_address)); + return ""; + } + } + + fc::flat_map outputs; + outputs[swwo.withdraw_address] = (swwo.withdraw_amount.value - fee_rate) / 100000000.0; + if ((total_amount - swwo.withdraw_amount.value) > 0.0) { + outputs[pw_address] = double(total_amount - swwo.withdraw_amount.value) / 100000000.0; + } + + return create_transaction(inputs, outputs, redeem_script); +} + +std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector &inputs, const fc::flat_map outputs, std::string &redeem_script) { + using namespace bitcoin; + + bitcoin_transaction_builder tb; + std::vector in_amounts; + + tb.set_version(2); + // Set vins + for (auto in : inputs) { + tb.add_in(fc::sha256(in.txid_), in.out_num_, bitcoin::bytes()); + in_amounts.push_back(in.amount_); + } + // Set vouts + for (auto out : outputs) { + uint64_t satoshis = out.second * 100000000.0; + tb.add_out_all_type(satoshis, out.first); + } + + const auto tx = tb.get_transaction(); + std::string hex_tx = fc::to_hex(pack(tx)); + std::string tx_raw = write_transaction_data(hex_tx, in_amounts, redeem_script); + return tx_raw; +} + +std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_transaction_object &sto) { + using namespace bitcoin; + std::string pubkey = plugin.get_current_son_object().sidechain_public_keys.at(sidechain); + std::string prvkey = get_private_key(pubkey); + std::vector in_amounts; + std::string tx_hex; + std::string redeem_script; + + fc::optional btc_private_key = graphene::utilities::wif_to_key(prvkey); + if (!btc_private_key) { + elog("Invalid private key ${pk}", ("pk", prvkey)); + return ""; + } + const auto secret = btc_private_key->get_secret(); + bitcoin::bytes privkey_signing(secret.data(), secret.data() + secret.data_size()); + + read_transaction_data(sto.transaction, tx_hex, in_amounts, redeem_script); + + bitcoin_transaction tx = unpack(parse_hex(tx_hex)); + std::vector redeem_scripts(tx.vin.size(), parse_hex(redeem_script)); + auto sigs = sign_witness_transaction_part(tx, redeem_scripts, in_amounts, privkey_signing, btc_context(), 1); + std::string tx_signature = write_transaction_signatures(sigs); + + return tx_signature; +} + +std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto) { + using namespace bitcoin; + std::vector in_amounts; + std::string tx_hex; + std::string redeem_script; + + read_transaction_data(sto.transaction, tx_hex, in_amounts, redeem_script); + + bitcoin_transaction tx = unpack(parse_hex(tx_hex)); + + std::vector redeem_scripts(tx.vin.size(), parse_hex(redeem_script)); + + uint32_t inputs_number = in_amounts.size(); + vector dummy; + dummy.resize(inputs_number); + //Organise weighted address signatures + //Add dummies for empty signatures + vector> signatures; + for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) { + if (sto.signatures[idx].second.empty()) + signatures.push_back(dummy); + else + signatures.push_back(read_byte_arrays_from_string(sto.signatures[idx].second)); + } + //Add empty sig for user signature for Deposit transaction + if (sto.object_id.type() == son_wallet_deposit_object::type_id) { + add_signatures_to_transaction_user_weighted_multisig(tx, signatures); + } else { + add_signatures_to_transaction_weighted_multisig(tx, signatures); + } + //Add redeemscripts to vins and make tx ready for sending + sign_witness_transaction_finalize(tx, redeem_scripts, false); + std::string final_tx_hex = fc::to_hex(pack(tx)); + std::string res = bitcoin_client->sendrawtransaction(final_tx_hex); + + return res; +} + +void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { + std::string block = bitcoin_client->getblock(event_data); + if (block != "") { + const auto &vins = extract_info_from_block(block); + + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + + for (const auto &v : vins) { + // !!! EXTRACT DEPOSIT ADDRESS FROM SIDECHAIN ADDRESS OBJECT + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, v.address, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) + continue; + + std::stringstream ss; + ss << "bitcoin" + << "-" << v.out.hash_tx << "-" << v.out.n_vout; + std::string sidechain_uid = ss.str(); + + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.block_num = database.head_block_num(); + sed.sidechain = addr_itr->sidechain; + sed.sidechain_uid = sidechain_uid; + sed.sidechain_transaction_id = v.out.hash_tx; + sed.sidechain_from = ""; + sed.sidechain_to = v.address; + sed.sidechain_currency = "BTC"; + sed.sidechain_amount = v.out.amount; + sed.peerplays_from = addr_itr->sidechain_address_account; + sed.peerplays_to = database.get_global_properties().parameters.son_account(); + price btc_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; + sed.peerplays_asset = asset(sed.sidechain_amount * btc_price.base.amount / btc_price.quote.amount); + sidechain_event_data_received(sed); + } + } +} + +std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(const std::string &user_address) { + using namespace bitcoin; + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, user_address, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) { + return ""; + } + + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return ""; + } + + std::vector> pubkey_weights; + for (auto &son : obj->sons) { + std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); + } + auto user_pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(addr_itr->deposit_public_key))); + btc_one_or_weighted_multisig_address deposit_addr(user_pub_key, pubkey_weights, network_type); + return fc::to_hex(deposit_addr.get_redeem_script()); +} + +std::vector sidechain_net_handler_bitcoin::extract_info_from_block(const std::string &_block) { + std::stringstream ss(_block); + boost::property_tree::ptree block; + boost::property_tree::read_json(ss, block); + + std::vector result; + + for (const auto &tx_child : block.get_child("tx")) { + const auto &tx = tx_child.second; + + for (const auto &o : tx.get_child("vout")) { + const auto script = o.second.get_child("scriptPubKey"); + + if (!script.count("addresses")) + continue; + + for (const auto &addr : script.get_child("addresses")) { // in which cases there can be more addresses? + const auto address_base58 = addr.second.get_value(); + info_for_vin vin; + vin.out.hash_tx = tx.get_child("txid").get_value(); + string amount = o.second.get_child("value").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + vin.out.amount = std::stoll(amount); + vin.out.n_vout = o.second.get_child("n").get_value(); + vin.address = address_base58; + result.push_back(vin); + } + } + } + + return result; +} + +void sidechain_net_handler_bitcoin::on_changed_objects(const vector &ids, const flat_set &accounts) { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_changed_objects_processing = 5000; + + fc::time_point next_wakeup(now + fc::microseconds(time_to_next_changed_objects_processing)); + + on_changed_objects_task = fc::schedule([this, ids, accounts] { + on_changed_objects_cb(ids, accounts); + }, + next_wakeup, "SON Processing"); +} + +void sidechain_net_handler_bitcoin::on_changed_objects_cb(const vector &ids, const flat_set &accounts) { + for (auto id : ids) { + if (id.is()) { + const auto &swi = database.get_index_type().indices().get(); + auto swo = swi.find(id); + if (swo != swi.end()) { + std::stringstream pw_ss(swo->addresses.at(sidechain)); + boost::property_tree::ptree pw_pt; + boost::property_tree::read_json(pw_ss, pw_pt); + + if (pw_pt.count("address")) { + std::string pw_address = pw_pt.get("address"); + bitcoin_client->importaddress(pw_address); + } + + if (pw_pt.count("redeemScript")) { + std::string pw_redeem_script = pw_pt.get("redeemScript"); + bitcoin_client->importaddress(pw_redeem_script, "", true, true); + } + } + } + } +} + +// ============================================================================= +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp new file mode 100644 index 00000000..3388527a --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp @@ -0,0 +1,306 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler_peerplays::sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain_net_handler(_plugin, options) { + sidechain = sidechain_type::peerplays; + + if (options.count("peerplays-private-key")) { + const std::vector pub_priv_keys = options["peerplays-private-key"].as>(); + for (const std::string &itr_key_pair : pub_priv_keys) { + auto key_pair = graphene::app::dejsonify>(itr_key_pair, 5); + ilog("Peerplays Public Key: ${public}", ("public", key_pair.first)); + if (!key_pair.first.length() || !key_pair.second.length()) { + FC_THROW("Invalid public private key pair."); + } + private_keys[key_pair.first] = key_pair.second; + } + } + + database.applied_block.connect([&](const signed_block &b) { + on_applied_block(b); + }); +} + +sidechain_net_handler_peerplays::~sidechain_net_handler_peerplays() { +} + +bool sidechain_net_handler_peerplays::process_proposal(const proposal_object &po) { + + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + + bool should_approve = false; + + const chain::global_property_object &gpo = database.get_global_properties(); + + int32_t op_idx_0 = -1; + chain::operation op_obj_idx_0; + + if (po.proposed_transaction.operations.size() >= 1) { + op_idx_0 = po.proposed_transaction.operations[0].which(); + op_obj_idx_0 = po.proposed_transaction.operations[0]; + } + + switch (op_idx_0) { + + case chain::operation::tag::value: { + should_approve = false; + break; + } + + case chain::operation::tag::value: { + son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get().son_wallet_deposit_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swdo = idx.find(swdo_id); + if (swdo != idx.end()) { + + uint32_t swdo_block_num = swdo->block_num; + std::string swdo_sidechain_transaction_id = swdo->sidechain_transaction_id; + uint32_t swdo_op_idx = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-") + 1)); + + const auto &block = database.fetch_block_by_number(swdo_block_num); + + for (const auto &tx : block->transactions) { + if (tx.id().str() == swdo_sidechain_transaction_id) { + operation op = tx.operations[swdo_op_idx]; + transfer_operation t_op = op.get(); + + asset sidechain_asset = asset(swdo->sidechain_amount, fc::variant(swdo->sidechain_currency, 1).as(1)); + price sidechain_asset_price = database.get(sidechain_asset.asset_id).options.core_exchange_rate; + asset peerplays_asset = asset(sidechain_asset.amount * sidechain_asset_price.base.amount / sidechain_asset_price.quote.amount); + + should_approve = (gpo.parameters.son_account() == t_op.to) && + (swdo->peerplays_from == t_op.from) && + (sidechain_asset == t_op.amount) && + (swdo->peerplays_asset == peerplays_asset); + break; + } + } + } + break; + } + + case chain::operation::tag::value: { + should_approve = false; + break; + } + + case chain::operation::tag::value: { + should_approve = true; + break; + } + + default: + should_approve = false; + elog("=================================================="); + elog("Proposal not considered for approval ${po}", ("po", po)); + elog("=================================================="); + } + + return should_approve; +} + +void sidechain_net_handler_peerplays::process_primary_wallet() { + return; +} + +void sidechain_net_handler_peerplays::process_sidechain_addresses() { + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&](const sidechain_address_object &sao) { + if (sao.expires == time_point_sec::maximum()) { + if (sao.deposit_address == "") { + sidechain_address_update_operation op; + op.payer = plugin.get_current_son_object().son_account; + op.sidechain_address_id = sao.id; + op.sidechain_address_account = sao.sidechain_address_account; + op.sidechain = sao.sidechain; + op.deposit_public_key = sao.deposit_public_key; + op.deposit_address = sao.withdraw_address; + op.deposit_address_data = sao.withdraw_address; + op.withdraw_public_key = sao.withdraw_public_key; + op.withdraw_address = sao.withdraw_address; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending transaction for update deposit address operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + } + }); + return; +} + +bool sidechain_net_handler_peerplays::process_deposit(const son_wallet_deposit_object &swdo) { + + const chain::global_property_object &gpo = database.get_global_properties(); + + asset_issue_operation ai_op; + ai_op.issuer = gpo.parameters.son_account(); + price btc_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; + ai_op.asset_to_issue = asset(swdo.peerplays_asset.amount * btc_price.quote.amount / btc_price.base.amount, database.get_global_properties().parameters.btc_asset()); + ai_op.issue_to_account = swdo.peerplays_from; + + signed_transaction tx; + auto dyn_props = database.get_dynamic_global_properties(); + tx.set_reference_block(dyn_props.head_block_id); + tx.set_expiration(database.head_block_time() + gpo.parameters.maximum_time_until_expiration); + tx.operations.push_back(ai_op); + database.current_fee_schedule().set_fee(tx.operations.back()); + + std::stringstream ss; + fc::raw::pack(ss, tx, 1000); + std::string tx_str = boost::algorithm::hex(ss.str()); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swdo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = gpo.active_sons; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(stc_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +bool sidechain_net_handler_peerplays::process_withdrawal(const son_wallet_withdraw_object &swwo) { + return true; +} + +std::string sidechain_net_handler_peerplays::process_sidechain_transaction(const sidechain_transaction_object &sto) { + + std::stringstream ss_trx(boost::algorithm::unhex(sto.transaction)); + signed_transaction trx; + fc::raw::unpack(ss_trx, trx, 1000); + + fc::optional privkey = graphene::utilities::wif_to_key(get_private_key(plugin.get_current_son_object().sidechain_public_keys.at(sidechain))); + signature_type st = trx.sign(*privkey, database.get_chain_id()); + + std::stringstream ss_st; + fc::raw::pack(ss_st, st, 1000); + std::string st_str = boost::algorithm::hex(ss_st.str()); + + return st_str; +} + +std::string sidechain_net_handler_peerplays::send_sidechain_transaction(const sidechain_transaction_object &sto) { + + std::stringstream ss_trx(boost::algorithm::unhex(sto.transaction)); + signed_transaction trx; + fc::raw::unpack(ss_trx, trx, 1000); + + for (auto signature : sto.signatures) { + if (!signature.second.empty()) { + std::stringstream ss_st(boost::algorithm::unhex(signature.second)); + signature_type st; + fc::raw::unpack(ss_st, st, 1000); + + trx.signatures.push_back(st); + trx.signees.clear(); + } + } + + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return trx.id().str(); + } catch (fc::exception e) { + elog("Sidechain transaction failed with exception ${e}", ("e", e.what())); + return ""; + } + + return ""; +} + +int64_t sidechain_net_handler_peerplays::settle_sidechain_transaction(const sidechain_transaction_object &sto) { + int64_t settle_amount = 0; + return settle_amount; +} + +void sidechain_net_handler_peerplays::on_applied_block(const signed_block &b) { + for (const auto &trx : b.transactions) { + size_t operation_index = -1; + for (auto op : trx.operations) { + operation_index = operation_index + 1; + if (op.which() == operation::tag::value) { + transfer_operation transfer_op = op.get(); + if (transfer_op.to != plugin.database().get_global_properties().parameters.son_account()) { + continue; + } + + std::stringstream ss; + ss << "peerplays" + << "-" << trx.id().str() << "-" << operation_index; + std::string sidechain_uid = ss.str(); + + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.block_num = database.head_block_num(); + sed.sidechain = sidechain_type::peerplays; + sed.sidechain_uid = sidechain_uid; + sed.sidechain_transaction_id = trx.id().str(); + sed.sidechain_from = fc::to_string(transfer_op.from.space_id) + "." + fc::to_string(transfer_op.from.type_id) + "." + fc::to_string((uint64_t)transfer_op.from.instance); + sed.sidechain_to = fc::to_string(transfer_op.to.space_id) + "." + fc::to_string(transfer_op.to.type_id) + "." + fc::to_string((uint64_t)transfer_op.to.instance); + sed.sidechain_currency = fc::to_string(transfer_op.amount.asset_id.space_id) + "." + fc::to_string(transfer_op.amount.asset_id.type_id) + "." + fc::to_string((uint64_t)transfer_op.amount.asset_id.instance); + sed.sidechain_amount = transfer_op.amount.amount; + sed.peerplays_from = transfer_op.from; + sed.peerplays_to = transfer_op.to; + price asset_price = database.get(transfer_op.amount.asset_id).options.core_exchange_rate; + sed.peerplays_asset = asset(transfer_op.amount.amount * asset_price.base.amount / asset_price.quote.amount); + sidechain_event_data_received(sed); + } + } + } +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp new file mode 100644 index 00000000..962488a6 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp @@ -0,0 +1,90 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_manager::sidechain_net_manager(peerplays_sidechain_plugin &_plugin) : + plugin(_plugin), + database(_plugin.database()) { +} + +sidechain_net_manager::~sidechain_net_manager() { +} + +bool sidechain_net_manager::create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options) { + + bool ret_val = false; + + switch (sidechain) { + case sidechain_type::bitcoin: { + std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_bitcoin(plugin, options)); + net_handlers.push_back(std::move(h)); + ret_val = true; + break; + } + case sidechain_type::peerplays: { + std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_peerplays(plugin, options)); + net_handlers.push_back(std::move(h)); + ret_val = true; + break; + } + default: + assert(false); + } + + return ret_val; +} + +void sidechain_net_manager::process_proposals() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_proposals(); + } +} + +void sidechain_net_manager::process_active_sons_change() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_active_sons_change(); + } +} + +void sidechain_net_manager::create_deposit_addresses() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->create_deposit_addresses(); + } +} + +void sidechain_net_manager::process_deposits() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_deposits(); + } +} + +void sidechain_net_manager::process_withdrawals() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_withdrawals(); + } +} + +void sidechain_net_manager::process_sidechain_transactions() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_sidechain_transactions(); + } +} + +void sidechain_net_manager::send_sidechain_transactions() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->send_sidechain_transactions(); + } +} + +void sidechain_net_manager::settle_sidechain_transactions() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->settle_sidechain_transactions(); + } +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 7f591328..4887f35b 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1294,6 +1294,12 @@ class wallet_api */ map list_committee_members(const string& lowerbound, uint32_t limit); + /** Returns information about the given SON. + * @param owner_account the name or id of the SON account owner, or the id of the SON + * @returns the information about the SON stored in the block chain + */ + son_object get_son(string owner_account); + /** Returns information about the given witness. * @param owner_account the name or id of the witness account owner, or the id of the witness * @returns the information about the witness stored in the block chain @@ -1312,6 +1318,179 @@ class wallet_api */ committee_member_object get_committee_member(string owner_account); + + /** Creates a SON object owned by the given account. + * + * An account can have at most one SON object. + * + * @param owner_account the name or id of the account which is creating the SON + * @param url a URL to include in the SON record in the blockchain. Clients may + * display this when showing a list of SONs. May be blank. + * @param deposit_id vesting balance id for SON deposit + * @param pay_vb_id vesting balance id for SON pay_vb + * @param sidechain_public_keys The new set of sidechain public keys. + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction registering a SON + */ + signed_transaction create_son(string owner_account, + string url, + vesting_balance_id_type deposit_id, + vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, + bool broadcast = false); + + /** Creates a SON object owned by the given account. + * + * Tries to create a SON object owned by the given account using + * existing vesting balances, fails if can't quess matching + * vesting balance objects. If several vesting balance objects matches + * this function uses the recent one. + * + * @param owner_account the name or id of the account which is creating the SON + * @param url a URL to include in the SON record in the blockchain. Clients may + * display this when showing a list of SONs. May be blank. + * @param sidechain_public_keys The new set of sidechain public keys. + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction registering a SON + */ + signed_transaction try_create_son(string owner_account, + string url, + flat_map sidechain_public_keys, + bool broadcast = false); + + /** + * Update a SON object owned by the given account. + * + * @param owner_account The name of the SON's owner account. Also accepts the ID of the owner account or the ID of the SON. + * @param url Same as for create_son. The empty string makes it remain the same. + * @param block_signing_key The new block signing public key. The empty string makes it remain the same. + * @param sidechain_public_keys The new set of sidechain public keys. The empty string makes it remain the same. + * @param broadcast true if you wish to broadcast the transaction. + */ + signed_transaction update_son(string owner_account, + string url, + string block_signing_key, + flat_map sidechain_public_keys, + bool broadcast = false); + + /** Modify status of the SON owned by the given account to maintenance. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction request_son_maintenance(string owner_account, + bool broadcast = false); + + /** Modify status of the SON owned by the given account back to active. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction cancel_request_son_maintenance(string owner_account, + bool broadcast = false); + + /** Lists all SONs in the blockchain. + * This returns a list of all account names that own SON, and the associated SON id, + * sorted by name. This lists SONs whether they are currently voted in or not. + * + * Use the \c lowerbound and limit parameters to page through the list. To retrieve all SONs, + * start by setting \c lowerbound to the empty string \c "", and then each iteration, pass + * the last SON name returned as the \c lowerbound for the next \c list_sons() call. + * + * @param lowerbound the name of the first SON to return. If the named SON does not exist, + * the list will start at the SON that comes after \c lowerbound + * @param limit the maximum number of SON to return (max: 1000) + * @returns a list of SON mapping SON names to SON ids + */ + map list_sons(const string& lowerbound, uint32_t limit); + + /** Lists active at the moment SONs. + * This returns a list of all account names that own active SON, and the associated SON id, + * sorted by name. + * @returns a list of active SONs mapping SON names to SON ids + */ + map list_active_sons(); + + /** + * @brief Get active SON wallet + * @return Active SON wallet object + */ + optional get_active_son_wallet(); + + /** + * @brief Get SON wallet that was active for a given time point + * @param time_point Time point + * @return SON wallet object, for the wallet that was active for a given time point + */ + optional get_son_wallet_by_time_point(time_point_sec time_point); + + /** + * @brief Get full list of SON wallets + * @param limit Maximum number of results to return + * @return A list of SON wallet objects + */ + vector> get_son_wallets(uint32_t limit); + + /** Adds sidechain address owned by the given account for a given sidechain. + * + * An account can have at most one sidechain address for one sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain a sidechain to whom address belongs + * @param deposit_public_key sidechain public key used for deposit address + * @param withdraw_public_key sidechain public key used for withdraw address + * @param withdraw_address sidechain address for withdrawals + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction adding sidechain address + */ + signed_transaction add_sidechain_address(string account, + sidechain_type sidechain, + string deposit_public_key, + string withdraw_public_key, + string withdraw_address, + bool broadcast = false); + + /** Deletes existing sidechain address owned by the given account for a given sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain a sidechain to whom address belongs + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction updating sidechain address + */ + signed_transaction delete_sidechain_address(string account, + sidechain_type sidechain, + bool broadcast = false); + + /** Retrieves all sidechain addresses owned by given account. + * + * @param account the name or id of the account who owns the address + * @returns the list of all sidechain addresses owned by given account. + */ + vector> get_sidechain_addresses_by_account(string account); + + /** Retrieves all sidechain addresses registered for a given sidechain. + * + * @param sidechain the name of the sidechain + * @returns the list of all sidechain addresses registered for a given sidechain. + */ + vector> get_sidechain_addresses_by_sidechain(sidechain_type sidechain); + + /** Retrieves sidechain address owned by given account for a given sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain the name of the sidechain + * @returns the sidechain address owned by given account for a given sidechain. + */ + fc::optional get_sidechain_address_by_account_and_sidechain(string account, sidechain_type sidechain); + + /** Retrieves the total number of sidechain addresses registered in the system. + * + * @returns the total number of sidechain addresses registered in the system. + */ + uint64_t get_sidechain_addresses_count(); + /** Creates a witness object owned by the given account. * * An account can have at most one witness object. @@ -1376,6 +1555,21 @@ class wallet_api bool broadcast = false ); + /** Creates a vesting deposit owned by the given account. + * + * @param owner_account vesting balance owner and creator (the name or id) + * @param amount amount to vest + * @param asset_symbol the symbol of the asset to vest + * @param vesting_type "normal", "gpos" or "son" + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction registering a vesting object + */ + signed_transaction create_vesting_balance(string owner_account, + string amount, + string asset_symbol, + vesting_balance_type vesting_type, + bool broadcast = false); + /** * Get information about a vesting balance object. * @@ -1433,6 +1627,61 @@ class wallet_api bool approve, bool broadcast = false); + /** Vote for a given SON. + * + * An account can publish a list of all SONs they approve of. This + * command allows you to add or remove SONs from this list. + * Each account's vote is weighted according to the number of shares of the + * core asset owned by that account at the time the votes are tallied. + * + * @note you cannot vote against a SON, you can only vote for the SON + * or not vote for the SON. + * + * @param voting_account the name or id of the account who is voting with their shares + * @param son the name or id of the SONs' owner account + * @param approve true if you wish to vote in favor of that SON, false to + * remove your vote in favor of that SON + * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction changing your vote for the given SON + */ + signed_transaction vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast = false); + + /** Change your SON votes. + * + * An account can publish a list of all SONs they approve of. + * Each account's vote is weighted according to the number of shares of the + * core asset owned by that account at the time the votes are tallied. + * This command allows you to add or remove one or more SON from this list + * in one call. When you are changing your vote on several SONs, this + * may be easier than multiple `vote_for_sons` and + * `set_desired_witness_and_committee_member_count` calls. + * + * @note you cannot vote against a SON, you can only vote for the SON + * or not vote for the SON. + * + * @param voting_account the name or id of the account who is voting with their shares + * @param sons_to_approve the names or ids of the sons owner accounts you wish + * to approve (these will be added to the list of sons + * you currently approve). This list can be empty. + * @param sons_to_reject the names or ids of the SONs owner accounts you wish + * to reject (these will be removed from the list of SONs + * you currently approve). This list can be empty. + * @param desired_number_of_sons the number of SONs you believe the network + * should have. You must vote for at least this many + * SONs. You can set this to 0 to abstain from + * voting on the number of SONNs. + * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction changing your vote for the given witnesses + */ + signed_transaction update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_son, + bool broadcast = false); + /** Vote for a given witness. * * An account can publish a list of all witnesses they approve of. This @@ -1881,20 +2130,6 @@ class wallet_api rock_paper_scissors_gesture gesture, bool broadcast); - /** Create a vesting balance including gpos vesting balance after HARDFORK_GPOS_TIME - * @param owner vesting balance owner and creator - * @param amount amount to vest - * @param asset_symbol the symbol of the asset to vest - * @param is_gpos True if the balance is of gpos type - * @param broadcast true if you wish to broadcast the transaction - * @return the signed version of the transaction - */ - signed_transaction create_vesting_balance(string owner, - string amount, - string asset_symbol, - bool is_gpos, - bool broadcast); - signed_transaction create_custom_permission(string owner, string permission_name, authority auth, @@ -1950,6 +2185,7 @@ class wallet_api optional revenue_split, bool is_transferable, bool is_sellable, + optional role_id, bool broadcast); /** @@ -1975,6 +2211,7 @@ class wallet_api optional revenue_split, optional is_transferable, optional is_sellable, + optional role_id, bool broadcast); /** @@ -2113,6 +2350,28 @@ class wallet_api vector get_offer_history_by_item(const nft_id_type item, uint32_t limit, optional lower_id) const; vector get_offer_history_by_bidder(string bidder_account_id_or_name, uint32_t limit, optional lower_id) const; + signed_transaction create_account_role(string owner_account_id_or_name, + string name, + string metadata, + flat_set allowed_operations, + flat_set whitelisted_accounts, + time_point_sec valid_to, + bool broadcast); + signed_transaction update_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + optional name, + optional metadata, + flat_set operations_to_add, + flat_set operations_to_remove, + flat_set accounts_to_add, + flat_set accounts_to_remove, + optional valid_to, + bool broadcast); + signed_transaction delete_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + bool broadcast); + vector get_account_roles_by_owner(string owner_account_id_or_name) const; + void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); void dbg_push_blocks( std::string src_filename, uint32_t count ); @@ -2266,11 +2525,28 @@ FC_API( graphene::wallet::wallet_api, (settle_asset) (whitelist_account) (create_committee_member) + (get_son) (get_witness) (is_witness) (get_committee_member) (list_witnesses) (list_committee_members) + (create_son) + (try_create_son) + (update_son) + (list_sons) + (list_active_sons) + (request_son_maintenance) + (cancel_request_son_maintenance) + (get_active_son_wallet) + (get_son_wallet_by_time_point) + (get_son_wallets) + (add_sidechain_address) + (delete_sidechain_address) + (get_sidechain_addresses_by_account) + (get_sidechain_addresses_by_sidechain) + (get_sidechain_address_by_account_and_sidechain) + (get_sidechain_addresses_count) (create_witness) (update_witness) (create_worker) @@ -2279,6 +2555,8 @@ FC_API( graphene::wallet::wallet_api, (withdraw_vesting) (withdraw_GPOS_vesting_balance) (vote_for_committee_member) + (vote_for_son) + (update_son_votes) (vote_for_witness) (update_witness_votes) (set_voting_proxy) @@ -2387,6 +2665,10 @@ FC_API( graphene::wallet::wallet_api, (get_offer_history_by_issuer) (get_offer_history_by_item) (get_offer_history_by_bidder) + (create_account_role) + (update_account_role) + (delete_account_role) + (get_account_roles_by_owner) (get_upcoming_tournaments) (get_tournaments) (get_tournaments_by_state) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 97b31370..4b44ad6e 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -74,6 +74,7 @@ #include #include +#include #include #include @@ -683,6 +684,7 @@ public: result["participation"] = (100*dynamic_props.recent_slots_filled.popcount()) / 128.0; result["active_witnesses"] = fc::variant(global_props.active_witnesses, GRAPHENE_MAX_NESTED_OBJECTS); result["active_committee_members"] = fc::variant(global_props.active_committee_members, GRAPHENE_MAX_NESTED_OBJECTS); + result["active_sons"] = fc::variant(global_props.active_sons, GRAPHENE_MAX_NESTED_OBJECTS); result["entropy"] = fc::variant(dynamic_props.random, GRAPHENE_MAX_NESTED_OBJECTS); return result; } @@ -853,6 +855,7 @@ public: // account, false otherwise (but it is stored either way) bool import_key(string account_name_or_id, string wif_key) { + fc::scoped_lock lock(_resync_mutex); fc::optional optional_private_key = wif_to_key(wif_key); if (!optional_private_key) FC_THROW("Invalid private key"); @@ -1394,6 +1397,7 @@ public: bool broadcast = false, bool save_wallet = true) { try { + fc::scoped_lock lock(_resync_mutex); int active_key_index = find_first_unused_derived_key_index(owner_privkey); fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index); @@ -1824,6 +1828,41 @@ public: FC_CAPTURE_AND_RETHROW( (owner_account) ) } + son_object get_son(string owner_account) + { + try + { + fc::optional son_id = maybe_id(owner_account); + if (son_id) + { + std::vector ids_to_get; + ids_to_get.push_back(*son_id); + std::vector> son_objects = _remote_db->get_sons(ids_to_get); + if (son_objects.front()) + return *son_objects.front(); + FC_THROW("No SON is registered for id ${id}", ("id", owner_account)); + } + else + { + // then maybe it's the owner account + try + { + account_id_type owner_account_id = get_account_id(owner_account); + fc::optional son = _remote_db->get_son_by_account(owner_account_id); + if (son) + return *son; + else + FC_THROW("No SON is registered for account ${account}", ("account", owner_account)); + } + catch (const fc::exception&) + { + FC_THROW("No account or SON named ${account}", ("account", owner_account)); + } + } + } + FC_CAPTURE_AND_RETHROW( (owner_account) ) + } + bool is_witness(string owner_account) { try @@ -1894,10 +1933,204 @@ public: FC_CAPTURE_AND_RETHROW( (owner_account) ) } + signed_transaction create_son(string owner_account, + string url, + vesting_balance_id_type deposit_id, + vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, + bool broadcast /* = false */) + { try { + fc::scoped_lock lock(_resync_mutex); + account_object son_account = get_account(owner_account); + auto son_public_key = son_account.active.get_keys()[0]; + + son_create_operation son_create_op; + son_create_op.owner_account = son_account.id; + son_create_op.signing_key = son_public_key; + son_create_op.url = url; + son_create_op.deposit = deposit_id; + son_create_op.pay_vb = pay_vb_id; + son_create_op.sidechain_public_keys = sidechain_public_keys; + + if (_remote_db->get_son_by_account(son_create_op.owner_account)) + FC_THROW("Account ${owner_account} is already a SON", ("owner_account", owner_account)); + + signed_transaction tx; + tx.operations.push_back( son_create_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + + signed_transaction update_son(string owner_account, + string url, + string block_signing_key, + flat_map sidechain_public_keys, + bool broadcast /* = false */) + { try { + son_object son = get_son(owner_account); + + son_update_operation son_update_op; + son_update_op.son_id = son.id; + son_update_op.owner_account = son.son_account; + if( url != "" ) + son_update_op.new_url = url; + if( block_signing_key != "" ) { + son_update_op.new_signing_key = public_key_type( block_signing_key ); + } + if( !sidechain_public_keys.empty() ) { + son_update_op.new_sidechain_public_keys = sidechain_public_keys; + } + + signed_transaction tx; + tx.operations.push_back( son_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(url)(block_signing_key)(broadcast) ) } + + signed_transaction request_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_maintenance_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + op.request_type = son_maintenance_request_type::request_maintenance; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + + signed_transaction cancel_request_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_maintenance_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + op.request_type = son_maintenance_request_type::cancel_request_maintenance; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + + map list_active_sons() + { try { + global_property_object gpo = get_global_properties(); + vector son_ids; + son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(son_ids, son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + std::vector> son_objects = _remote_db->get_sons(son_ids); + vector owners; + for(auto obj: son_objects) + { + if (obj) + { + std::string acc_id = account_id_to_string(obj->son_account); + owners.push_back(acc_id); + } + } + vector< optional< account_object> > accs = _remote_db->get_accounts(owners); + std::remove_if(son_objects.begin(), son_objects.end(), + [](const fc::optional& obj) -> bool { return obj.valid(); }); + map result; + std::transform(accs.begin(), accs.end(), son_objects.begin(), + std::inserter(result, result.end()), + [](fc::optional& acct, fc::optional son) { + FC_ASSERT(acct, "Invalid active SONs list in global properties."); + if (son.valid() && son->status != son_status::deregistered) + return std::make_pair(string(acct->name), std::move(son->id)); + return std::make_pair(string(acct->name), std::move(son_id_type())); + }); + return result; + } FC_CAPTURE_AND_RETHROW() } + + optional get_active_son_wallet() + { try { + return _remote_db->get_active_son_wallet(); + } FC_CAPTURE_AND_RETHROW() } + + optional get_son_wallet_by_time_point(time_point_sec time_point) + { try { + return _remote_db->get_son_wallet_by_time_point(time_point); + } FC_CAPTURE_AND_RETHROW() } + + vector> get_son_wallets(uint32_t limit) + { try { + return _remote_db->get_son_wallets(limit); + } FC_CAPTURE_AND_RETHROW() } + + signed_transaction add_sidechain_address(string account, + sidechain_type sidechain, + string deposit_public_key, + string withdraw_public_key, + string withdraw_address, + bool broadcast /* = false */) + { try { + account_id_type sidechain_address_account_id = get_account_id(account); + + sidechain_address_add_operation op; + op.payer = sidechain_address_account_id; + op.sidechain_address_account = sidechain_address_account_id; + op.sidechain = sidechain; + op.deposit_public_key = deposit_public_key; + op.withdraw_public_key = withdraw_public_key; + op.withdraw_address = withdraw_address; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW() } + + signed_transaction delete_sidechain_address(string account, + sidechain_type sidechain, + bool broadcast /* = false */) + { try { + account_id_type sidechain_address_account_id = get_account_id(account); + fc::optional sao = _remote_db->get_sidechain_address_by_account_and_sidechain(sidechain_address_account_id, sidechain); + if (!sao) + FC_THROW("No sidechain address for account ${account} and sidechain ${sidechain}", ("account", sidechain_address_account_id)("sidechain", sidechain)); + + sidechain_address_delete_operation op; + op.payer = sidechain_address_account_id; + op.sidechain_address_id = sao->id; + op.sidechain_address_account = sidechain_address_account_id; + op.sidechain = sidechain; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + + } FC_CAPTURE_AND_RETHROW() } + signed_transaction create_witness(string owner_account, string url, bool broadcast /* = false */) { try { + fc::scoped_lock lock(_resync_mutex); account_object witness_account = get_account(owner_account); fc::ecc::private_key active_private_key = get_private_key_for_account(witness_account); int witness_key_index = find_first_unused_derived_key_index(active_private_key); @@ -2069,6 +2302,33 @@ public: return sign_transaction( tx, broadcast ); } + signed_transaction create_vesting_balance(string owner_account, + string amount, + string asset_symbol, + vesting_balance_type vesting_type, + bool broadcast /* = false */) + { try { + FC_ASSERT( !is_locked() ); + account_object user_account = get_account(owner_account); + fc::optional asset_obj = get_asset(asset_symbol); + FC_ASSERT(asset_obj, "Invalid asset symbol {asst}", ("asst", asset_symbol)); + + vesting_balance_create_operation op; + op.creator = user_account.get_id(); + op.owner = user_account.get_id(); + op.amount = asset_obj->amount_from_string(amount); + op.balance_type = vesting_type; + if (op.balance_type == vesting_balance_type::son) + op.policy = dormant_vesting_policy_initializer {}; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -2114,8 +2374,8 @@ public: vesting_balance_object vbo = get_object< vesting_balance_object >( *vbid ); - if(vbo.balance_type != vesting_balance_type::normal) - FC_THROW("Allowed to withdraw only Normal type vest balances with this method"); + if(vbo.balance_type == vesting_balance_type::gpos) + FC_THROW("Allowed to withdraw only Normal and Son type vest balances with this method"); vesting_balance_withdraw_operation vesting_balance_withdraw_op; @@ -2246,6 +2506,95 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (voting_account)(committee_member)(approve)(broadcast) ) } + signed_transaction vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast /* = false */) + { try { + + std::vector vbo_info = get_vesting_balances(voting_account); + std::vector::iterator vbo_iter; + vbo_iter = std::find_if(vbo_info.begin(), vbo_info.end(), [](vesting_balance_object_with_info const& obj){return obj.balance_type == vesting_balance_type::gpos;}); + if( vbo_info.size() == 0 || vbo_iter == vbo_info.end()) + FC_THROW("Account ${account} has no core Token ${TOKEN} vested and will not be allowed to vote for the SON account", ("account", voting_account)("TOKEN", GRAPHENE_SYMBOL)); + + account_object voting_account_object = get_account(voting_account); + account_id_type son_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a son", ("son", son)); + if (approve) + { + auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); + if (!insert_result.second) + FC_THROW("Account ${account} was already voting for son ${son}", ("account", voting_account)("son", son)); + } + else + { + unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + if (!votes_removed) + FC_THROW("Account ${account} is already not voting for son ${son}", ("account", voting_account)("son", son)); + } + account_update_operation account_update_op; + account_update_op.account = voting_account_object.id; + account_update_op.new_options = voting_account_object.options; + + signed_transaction tx; + tx.operations.push_back( account_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (voting_account)(son)(approve)(broadcast) ) } + + signed_transaction update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_sons, + bool broadcast /* = false */) + { try { + FC_ASSERT(sons_to_approve.size() || sons_to_reject.size(), "Both accepted and rejected lists can't be empty simultaneously"); + std::vector vbo_info = get_vesting_balances(voting_account); + std::vector::iterator vbo_iter; + vbo_iter = std::find_if(vbo_info.begin(), vbo_info.end(), [](vesting_balance_object_with_info const& obj){return obj.balance_type == vesting_balance_type::gpos;}); + if( vbo_info.size() == 0 || vbo_iter == vbo_info.end()) + FC_THROW("Account ${account} has no core Token ${TOKEN} vested and will not be allowed to vote for the SON account", ("account", voting_account)("TOKEN", GRAPHENE_SYMBOL)); + + account_object voting_account_object = get_account(voting_account); + for (const std::string& son : sons_to_approve) + { + account_id_type son_owner_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a SON", ("son", son)); + auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); + if (!insert_result.second) + FC_THROW("Account ${account} was already voting for SON ${son}", ("account", voting_account)("son", son)); + } + for (const std::string& son : sons_to_reject) + { + account_id_type son_owner_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a SON", ("son", son)); + unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + if (!votes_removed) + FC_THROW("Account ${account} is already not voting for SON ${son}", ("account", voting_account)("son", son)); + } + voting_account_object.options.num_son = desired_number_of_sons; + + account_update_operation account_update_op; + account_update_op.account = voting_account_object.id; + account_update_op.new_options = voting_account_object.options; + + signed_transaction tx; + tx.operations.push_back( account_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (voting_account)(sons_to_approve)(sons_to_reject)(desired_number_of_sons)(broadcast) ) } + signed_transaction vote_for_witness(string voting_account, string witness, bool approve, @@ -4439,6 +4788,11 @@ map wallet_api::list_committee_members(const st return my->_remote_db->lookup_committee_member_accounts(lowerbound, limit); } +son_object wallet_api::get_son(string owner_account) +{ + return my->get_son(owner_account); +} + witness_object wallet_api::get_witness(string owner_account) { return my->get_witness(owner_account); @@ -4454,6 +4808,139 @@ committee_member_object wallet_api::get_committee_member(string owner_account) return my->get_committee_member(owner_account); } +signed_transaction wallet_api::create_vesting_balance(string owner_account, + string amount, + string asset_symbol, + vesting_balance_type vesting_type, + bool broadcast /* = false */) +{ + return my->create_vesting_balance(owner_account, amount, asset_symbol, vesting_type, broadcast); +} + +signed_transaction wallet_api::create_son(string owner_account, + string url, + vesting_balance_id_type deposit_id, + vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, + bool broadcast /* = false */) +{ + return my->create_son(owner_account, url, deposit_id, pay_vb_id, sidechain_public_keys, broadcast); +} + +signed_transaction wallet_api::try_create_son(string owner_account, + string url, + flat_map sidechain_public_keys, + bool broadcast /* = false */) +{ + vesting_balance_id_type deposit_id; + bool deposit_found = false; + vesting_balance_id_type pay_vb_id; + bool pay_vb_found = false; + vector vbs = get_vesting_balances(owner_account); + for(const auto& vb: vbs) + { + if ((vb.balance_type == vesting_balance_type::son) && + (vb.get_asset_amount() >= my->get_global_properties().parameters.son_vesting_amount()) && + (vb.policy.which() == vesting_policy::tag::value)) + { + deposit_found = true; + deposit_id = vb.id; + } + if ((vb.balance_type == vesting_balance_type::normal) && + (vb.policy.which() == vesting_policy::tag::value)) + { + pay_vb_found = true; + pay_vb_id = vb.id; + } + } + if (!deposit_found || !pay_vb_found) + FC_THROW("Failed to find vesting balance objects"); + return my->create_son(owner_account, url, deposit_id, pay_vb_id, sidechain_public_keys, broadcast); +} + +signed_transaction wallet_api::update_son(string owner_account, + string url, + string block_signing_key, + flat_map sidechain_public_keys, + bool broadcast /* = false */) +{ + return my->update_son(owner_account, url, block_signing_key, sidechain_public_keys, broadcast); +} + +signed_transaction wallet_api::request_son_maintenance(string owner_account, bool broadcast) +{ + return my->request_son_maintenance(owner_account, broadcast); +} + +signed_transaction wallet_api::cancel_request_son_maintenance(string owner_account, bool broadcast) +{ + return my->cancel_request_son_maintenance(owner_account, broadcast); +} + +map wallet_api::list_sons(const string& lowerbound, uint32_t limit) +{ + return my->_remote_db->lookup_son_accounts(lowerbound, limit); +} + +map wallet_api::list_active_sons() +{ + return my->list_active_sons(); +} + +optional wallet_api::get_active_son_wallet() +{ + return my->get_active_son_wallet(); +} + +optional wallet_api::get_son_wallet_by_time_point(time_point_sec time_point) +{ + return my->get_son_wallet_by_time_point(time_point); +} + +vector> wallet_api::get_son_wallets(uint32_t limit) +{ + return my->get_son_wallets(limit); +} + +signed_transaction wallet_api::add_sidechain_address(string account, + sidechain_type sidechain, + string deposit_public_key, + string withdraw_public_key, + string withdraw_address, + bool broadcast /* = false */) +{ + return my->add_sidechain_address(account, sidechain, deposit_public_key, withdraw_public_key, withdraw_address, broadcast); +} + +signed_transaction wallet_api::delete_sidechain_address(string account, + sidechain_type sidechain, + bool broadcast /* = false */) +{ + return my->delete_sidechain_address(account, sidechain, broadcast); +} + +vector> wallet_api::get_sidechain_addresses_by_account(string account) +{ + account_id_type account_id = get_account_id(account); + return my->_remote_db->get_sidechain_addresses_by_account(account_id); +} + +vector> wallet_api::get_sidechain_addresses_by_sidechain(sidechain_type sidechain) +{ + return my->_remote_db->get_sidechain_addresses_by_sidechain(sidechain); +} + +fc::optional wallet_api::get_sidechain_address_by_account_and_sidechain(string account, sidechain_type sidechain) +{ + account_id_type account_id = get_account_id(account); + return my->_remote_db->get_sidechain_address_by_account_and_sidechain(account_id, sidechain); +} + +uint64_t wallet_api::get_sidechain_addresses_count() +{ + return my->_remote_db->get_sidechain_addresses_count(); +} + signed_transaction wallet_api::create_witness(string owner_account, string url, bool broadcast /* = false */) @@ -4523,6 +5010,23 @@ signed_transaction wallet_api::vote_for_committee_member(string voting_account, return my->vote_for_committee_member(voting_account, witness, approve, broadcast); } +signed_transaction wallet_api::vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast /* = false */) +{ + return my->vote_for_son(voting_account, son, approve, broadcast); +} + +signed_transaction wallet_api::update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_sons, + bool broadcast /* = false */) +{ + return my->update_son_votes(voting_account, sons_to_approve, sons_to_reject, desired_number_of_sons, broadcast); +} + signed_transaction wallet_api::vote_for_witness(string voting_account, string witness, bool approve, @@ -6332,41 +6836,6 @@ signed_transaction wallet_api::rps_throw(game_id_type game_id, return my->sign_transaction( tx, broadcast ); } -signed_transaction wallet_api::create_vesting_balance(string owner, - string amount, - string asset_symbol, - bool is_gpos, - bool broadcast) -{ - FC_ASSERT( !is_locked() ); - //Can be deleted after GPOS hardfork time - time_point_sec now = time_point::now(); - if(is_gpos && now < HARDFORK_GPOS_TIME) - FC_THROW("GPOS related functionality is not avaiable until next Spring"); - - account_object owner_account = get_account(owner); - account_id_type owner_id = owner_account.id; - - fc::optional asset_obj = get_asset(asset_symbol); - - auto type = vesting_balance_type::normal; - if(is_gpos) - type = vesting_balance_type::gpos; - - vesting_balance_create_operation op; - op.creator = owner_id; - op.owner = owner_id; - op.amount = asset_obj->amount_from_string(amount); - op.balance_type = type; - - signed_transaction trx; - trx.operations.push_back(op); - my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); - trx.validate(); - - return my->sign_transaction( trx, broadcast ); -} - signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_name, string name, string symbol, @@ -6375,6 +6844,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na optional revenue_split, bool is_transferable, bool is_sellable, + optional role_id, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6397,6 +6867,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na } op.is_transferable = is_transferable; op.is_sellable = is_sellable; + op.account_role = role_id; signed_transaction trx; trx.operations.push_back(op); @@ -6415,6 +6886,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na optional revenue_split, optional is_transferable, optional is_sellable, + optional role_id, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6438,6 +6910,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na } op.is_transferable = is_transferable; op.is_sellable = is_sellable; + op.account_role = role_id; signed_transaction trx; trx.operations.push_back(op); @@ -6719,6 +7192,88 @@ vector wallet_api::get_offer_history_by_bidder(string bidd account_object bidder_account = my->get_account(bidder_account_id_or_name); return my->_remote_db->get_offer_history_by_bidder(lb_id, bidder_account.id, limit); } + +signed_transaction wallet_api::create_account_role(string owner_account_id_or_name, + string name, + string metadata, + flat_set allowed_operations, + flat_set whitelisted_accounts, + time_point_sec valid_to, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_create_operation op; + op.owner = owner_account.id; + op.name = name; + op.metadata = metadata; + op.allowed_operations = allowed_operations; + op.whitelisted_accounts = whitelisted_accounts; + op.valid_to = valid_to; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +signed_transaction wallet_api::update_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + optional name, + optional metadata, + flat_set operations_to_add, + flat_set operations_to_remove, + flat_set accounts_to_add, + flat_set accounts_to_remove, + optional valid_to, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_update_operation op; + op.owner = owner_account.id; + op.account_role_id = role_id; + op.name = name; + op.metadata = metadata; + op.allowed_operations_to_add = operations_to_add; + op.allowed_operations_to_remove = operations_to_remove; + op.accounts_to_add = accounts_to_add; + op.accounts_to_remove = accounts_to_remove; + op.valid_to = valid_to; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +signed_transaction wallet_api::delete_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_delete_operation op; + op.owner = owner_account.id; + op.account_role_id = role_id; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +vector wallet_api::get_account_roles_by_owner(string owner_account_id_or_name) const +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + return my->_remote_db->get_account_roles_by_owner(owner_account.id); +} // default ctor necessary for FC_REFLECT signed_block_with_info::signed_block_with_info() { @@ -6774,7 +7329,8 @@ vesting_balance_object_with_info::vesting_balance_object_with_info( const vestin : vesting_balance_object( vbo ) { allowed_withdraw = get_allowed_withdraw( now ); - if(vbo.balance_type == vesting_balance_type::gpos) + if(vbo.balance_type == vesting_balance_type::gpos || + ((vbo.balance_type == vesting_balance_type::son) && (vbo.policy.which() == vesting_policy::tag::value))) allowed_withdraw_time = vbo.policy.get().begin_timestamp + vbo.policy.get().vesting_cliff_seconds; else allowed_withdraw_time = now; diff --git a/programs/cli_wallet/main.cpp b/programs/cli_wallet/main.cpp index b7abdabe..fda7f22d 100644 --- a/programs/cli_wallet/main.cpp +++ b/programs/cli_wallet/main.cpp @@ -176,7 +176,7 @@ int main( int argc, char** argv ) fc::http::websocket_client client; idump((wdata.ws_server)); auto con = client.connect( wdata.ws_server ); - auto apic = std::make_shared(*con, GRAPHENE_MAX_NESTED_OBJECTS); + auto apic = std::make_shared(con, GRAPHENE_MAX_NESTED_OBJECTS); auto remote_api = apic->get_remote_api< login_api >(1); edump((wdata.ws_user)(wdata.ws_password) ); @@ -209,13 +209,14 @@ int main( int argc, char** argv ) wallet_cli->set_prompt( locked ? "locked >>> " : "unlocked >>> " ); })); - auto _websocket_server = std::make_shared(); + std::shared_ptr _websocket_server; if( options.count("rpc-endpoint") ) { + _websocket_server = std::make_shared(); _websocket_server->on_connection([&wapi]( const fc::http::websocket_connection_ptr& c ){ std::cout << "here... \n"; wlog("." ); - auto wsc = std::make_shared(*c, GRAPHENE_MAX_NESTED_OBJECTS); + auto wsc = std::make_shared(c, GRAPHENE_MAX_NESTED_OBJECTS); wsc->register_api(wapi); c->set_session_data( wsc ); }); @@ -228,11 +229,12 @@ int main( int argc, char** argv ) if( options.count( "rpc-tls-certificate" ) ) cert_pem = options.at("rpc-tls-certificate").as(); - auto _websocket_tls_server = std::make_shared(cert_pem); + std::shared_ptr _websocket_tls_server; if( options.count("rpc-tls-endpoint") ) { + _websocket_tls_server = std::make_shared(cert_pem); _websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ - auto wsc = std::make_shared(*c, GRAPHENE_MAX_NESTED_OBJECTS); + auto wsc = std::make_shared(c, GRAPHENE_MAX_NESTED_OBJECTS); wsc->register_api(wapi); c->set_session_data( wsc ); }); diff --git a/programs/delayed_node/main.cpp b/programs/delayed_node/main.cpp index 112b7dee..3e058b64 100644 --- a/programs/delayed_node/main.cpp +++ b/programs/delayed_node/main.cpp @@ -70,10 +70,19 @@ int main(int argc, char** argv) { bpo::variables_map options; + bpo::options_description cli, cfg; + node.set_program_options(cli, cfg); + cfg_options.add(cfg); + + cfg_options.add_options() + ("plugins", bpo::value()->default_value("delayed_node account_history market_history"), + "Space-separated list of plugins to activate"); + auto delayed_plug = node.register_plugin(); auto history_plug = node.register_plugin(); auto market_history_plug = node.register_plugin(); + // add plugin options to config try { bpo::options_description cli, cfg; @@ -160,6 +169,10 @@ int main(int argc, char** argv) { elog("Error parsing configuration file: ${e}", ("e", e.what())); return 1; } + + if( !options.count("plugins") ) + options.insert( std::make_pair( "plugins", bpo::variable_value(std::string("delayed_node account_history market_history"), true) ) ); + node.initialize(data_dir, options); node.initialize_plugins( options ); diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 94a3296a..34b861b1 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -47,6 +47,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index c83fc363..d5578379 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246 target_link_libraries( witness_node - PRIVATE graphene_app graphene_account_history graphene_affiliate_stats graphene_elasticsearch graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_bookie graphene_egenesis_full graphene_snapshot graphene_es_objects fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE graphene_app graphene_account_history graphene_affiliate_stats graphene_elasticsearch graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_bookie graphene_egenesis_full graphene_snapshot graphene_es_objects fc peerplays_sidechain ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) # also add dependencies to graphene_generate_genesis graphene_generate_uia_sharedrop_genesis if you want those plugins install( TARGETS diff --git a/programs/witness_node/genesis.json b/programs/witness_node/genesis.json index e07cec92..192a3879 100644 --- a/programs/witness_node/genesis.json +++ b/programs/witness_node/genesis.json @@ -344,6 +344,7 @@ "maximum_asset_feed_publishers": 10, "maximum_witness_count": 1001, "maximum_committee_count": 1001, + "maximum_son_count": 15, "maximum_authority_membership": 10, "reserve_percent_of_fee": 2000, "network_percent_of_fee": 2000, @@ -384,7 +385,14 @@ "gpos_period": 15552000, "gpos_subperiod": 2592000, "gpos_period_start": 1601528400, - "gpos_vesting_lockin_period": 2592000 + "gpos_vesting_lockin_period": 2592000, + "son_vesting_amount": 5000000, + "son_vesting_period": 172800, + "son_pay_max": 20000000, + "son_pay_time": 86400, + "son_deregister_time": 43200, + "son_heartbeat_frequency": 180, + "son_down_time": 360 } }, "initial_bts_accounts": [], @@ -526,4 +534,4 @@ "num_special_accounts": 0, "num_special_assets": 0 } -} \ No newline at end of file +} diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index 4d49d96f..7823fed3 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -35,6 +35,7 @@ //#include #include #include +#include #include #include @@ -42,8 +43,9 @@ #include #include - #include +#include +#include #include #include @@ -51,7 +53,7 @@ #include #ifdef WIN32 -# include +# include #else # include #endif @@ -67,12 +69,23 @@ int main(int argc, char** argv) { bpo::options_description cfg_options("Graphene Witness Node"); app_options.add_options() ("help,h", "Print this help message and exit.") - ("version", "Display the version info and exit") - ("data-dir,d", bpo::value()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.") - ; + ("data-dir,d", bpo::value()->default_value("witness_node_data_dir"), + "Directory containing databases, configuration file, etc.") + ("version,v", "Display version information") + ("plugins", bpo::value() + ->default_value("witness account_history market_history accounts_list affiliate_stats bookie"), + "Space-separated list of plugins to activate"); bpo::variables_map options; + bpo::options_description cli, cfg; + node->set_program_options(cli, cfg); + cfg_options.add(cfg); + + cfg_options.add_options() + ("plugins", bpo::value()->default_value("witness account_history market_history accounts_list affiliate_stats bookie"), + "Space-separated list of plugins to activate"); + auto witness_plug = node->register_plugin(); auto debug_witness_plug = node->register_plugin(); auto history_plug = node->register_plugin(); @@ -84,8 +97,10 @@ int main(int argc, char** argv) { auto list_plug = node->register_plugin(); auto affiliate_stats_plug = node->register_plugin(); auto bookie_plug = node->register_plugin(); + auto peerplays_sidechain = node->register_plugin(); auto snapshot_plug = node->register_plugin(); + // add plugin options to config try { bpo::options_description cli, cfg; @@ -100,11 +115,6 @@ int main(int argc, char** argv) { return 1; } - if( options.count("help") ) - { - std::cout << app_options << "\n"; - return 0; - } if (options.count("version")) { std::string witness_version(graphene::utilities::git_revision_description); @@ -118,6 +128,11 @@ int main(int argc, char** argv) { std::cout << "Boost: " << boost::replace_all_copy(std::string(BOOST_LIB_VERSION), "_", ".") << "\n"; return 0; } + if( options.count("help") ) + { + std::cout << app_options << "\n"; + return 0; + } fc::path data_dir; if( options.count("data-dir") ) @@ -126,10 +141,24 @@ int main(int argc, char** argv) { if( data_dir.is_relative() ) data_dir = fc::current_path() / data_dir; } - app::load_configuration_options(data_dir, cfg_options, options); + std::set plugins; + boost::split(plugins, options.at("plugins").as(), [](char c){return c == ' ';}); + + if(plugins.count("account_history") && plugins.count("elasticsearch")) { + std::cerr << "Plugin conflict: Cannot load both account_history plugin and elasticsearch plugin\n"; + return 1; + } + + std::for_each(plugins.begin(), plugins.end(), [node](const std::string& plug) mutable { + if (!plug.empty()) { + node->enable_plugin(plug); + } + }); + bpo::notify(options); + node->initialize(data_dir, options); node->initialize_plugins( options ); @@ -156,7 +185,7 @@ int main(int argc, char** argv) { node->shutdown_plugins(); node->shutdown(); delete node; - return 0; + return EXIT_SUCCESS; } catch( const fc::exception& e ) { // deleting the node can yield, so do this outside the exception handler unhandled_exception = e; @@ -167,6 +196,6 @@ int main(int argc, char** argv) { elog("Exiting with error:\n${e}", ("e", unhandled_exception->to_detail_string())); node->shutdown(); delete node; - return 1; + return EXIT_FAILURE; } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e57e3374..01474582 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,6 +33,10 @@ file(GLOB BETTING_TESTS "betting/*.cpp") add_executable( betting_test ${BETTING_TESTS} ${COMMON_SOURCES} ) target_link_libraries( betting_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_bookie graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB PEERPLAYS_SIDECHAIN_TESTS "peerplays_sidechain/*.cpp") +add_executable( peerplays_sidechain_test ${PEERPLAYS_SIDECHAIN_TESTS} ${COMMON_SOURCES} ) +target_link_libraries( peerplays_sidechain_test graphene_chain graphene_app graphene_account_history graphene_bookie graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) + file(GLOB TOURNAMENT_TESTS "tournament/*.cpp") add_executable( tournament_test ${TOURNAMENT_TESTS} ${COMMON_SOURCES} ) target_link_libraries( tournament_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) @@ -46,7 +50,7 @@ add_executable( cli_test ${CLI_SOURCES} ) if(WIN32) list(APPEND PLATFORM_SPECIFIC_LIBS ws2_32) endif() -target_link_libraries( cli_test graphene_chain graphene_app graphene_witness graphene_wallet graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( cli_test graphene_chain graphene_app graphene_witness graphene_wallet graphene_egenesis_none fc graphene_elasticsearch graphene_es_objects ${PLATFORM_SPECIFIC_LIBS} ) if(MSVC) set_source_files_properties( cli/main.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) diff --git a/tests/app/main.cpp b/tests/app/main.cpp index 623f760e..28b8a1fc 100644 --- a/tests/app/main.cpp +++ b/tests/app/main.cpp @@ -69,6 +69,7 @@ BOOST_AUTO_TEST_CASE( two_node_network ) boost::program_options::variables_map cfg; cfg.emplace("p2p-endpoint", boost::program_options::variable_value(string("127.0.0.1:0"), false)); + cfg.emplace("plugins", boost::program_options::variable_value(string(" "), false)); app1.initialize(app_dir.path(), cfg); cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); diff --git a/tests/cli/cli_fixture.cpp b/tests/cli/cli_fixture.cpp new file mode 100644 index 00000000..5b5fd7ad --- /dev/null +++ b/tests/cli/cli_fixture.cpp @@ -0,0 +1,277 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#ifdef _WIN32 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #include + #include +#else +#include +#include +#include +#endif +#include + +#include +#include + +#include "cli_fixture.hpp" + +/***** + * Global Initialization for Windows + * ( sets up Winsock stuf ) + */ +#ifdef _WIN32 +int sockInit(void) +{ + WSADATA wsa_data; + return WSAStartup(MAKEWORD(1,1), &wsa_data); +} +int sockQuit(void) +{ + return WSACleanup(); +} +#endif + +/********************* + * Helper Methods + *********************/ + +#include "../common/genesis_file_util.hpp" + +////// +/// @brief attempt to find an available port on localhost +/// @returns an available port number, or -1 on error +///// +int get_available_port() +{ + struct sockaddr_in sin; + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) + return -1; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (::bind(socket_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1) + return -1; + socklen_t len = sizeof(sin); + if (getsockname(socket_fd, (struct sockaddr *)&sin, &len) == -1) + return -1; +#ifdef _WIN32 + closesocket(socket_fd); +#else + close(socket_fd); +#endif + return ntohs(sin.sin_port); +} + +/////////// +/// @brief Start the application +/// @param app_dir the temporary directory to use +/// @param server_port_number to be filled with the rpc endpoint port number +/// @returns the application object +////////// +std::shared_ptr start_application(fc::temp_directory& app_dir, int& server_port_number) { + std::shared_ptr app1(new graphene::app::application{}); + + app1->register_plugin(); + app1->register_plugin(); + app1->register_plugin(); + app1->register_plugin(); + app1->startup_plugins(); + boost::program_options::variables_map cfg; +#ifdef _WIN32 + sockInit(); +#endif + server_port_number = get_available_port(); + cfg.emplace( + "rpc-endpoint", + boost::program_options::variable_value(string("127.0.0.1:" + std::to_string(server_port_number)), false) + ); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); + cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); + cfg.emplace("plugins", boost::program_options::variable_value(string("bookie account_history market_history"), false)); + + app1->initialize(app_dir.path(), cfg); + + app1->initialize_plugins(cfg); + app1->startup_plugins(); + + app1->startup(); + fc::usleep(fc::milliseconds(500)); + return app1; +} + +client_connection::client_connection( + std::shared_ptr app, + const fc::temp_directory& data_dir, + const int server_port_number +) +{ + wallet_data.chain_id = app->chain_database()->get_chain_id(); + wallet_data.ws_server = "ws://127.0.0.1:" + std::to_string(server_port_number); + wallet_data.ws_user = ""; + wallet_data.ws_password = ""; + websocket_connection = websocket_client.connect( wallet_data.ws_server ); + + api_connection = std::make_shared(websocket_connection, GRAPHENE_MAX_NESTED_OBJECTS); + + remote_login_api = api_connection->get_remote_api< graphene::app::login_api >(1); + BOOST_CHECK(remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ) ); + + wallet_api_ptr = std::make_shared(wallet_data, remote_login_api); + wallet_filename = data_dir.path().generic_string() + "/wallet.json"; + wallet_api_ptr->set_wallet_filename(wallet_filename); + + wallet_api = fc::api(wallet_api_ptr); + + wallet_cli = std::make_shared(GRAPHENE_MAX_NESTED_OBJECTS); + for( auto& name_formatter : wallet_api_ptr->get_result_formatters() ) + wallet_cli->format_result( name_formatter.first, name_formatter.second ); + + boost::signals2::scoped_connection closed_connection(websocket_connection->closed.connect([=]{ + cerr << "Server has disconnected us.\n"; + wallet_cli->stop(); + })); + (void)(closed_connection); +} + +client_connection::~client_connection() +{ + // wait for everything to finish up + fc::usleep(fc::milliseconds(500)); +} + +/////////////////////////////// +// Cli Wallet Fixture +/////////////////////////////// + +cli_fixture::cli_fixture() : + server_port_number(0), + app_dir( graphene::utilities::temp_directory_path() ), + app1( start_application(app_dir, server_port_number) ), + con( app1, app_dir, server_port_number ), + nathan_keys( {"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"} ) +{ + BOOST_TEST_MESSAGE("Setup cli_wallet::boost_fixture_test_case"); + + using namespace graphene::chain; + using namespace graphene::app; + + try + { + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +cli_fixture::~cli_fixture() +{ + BOOST_TEST_MESSAGE("Cleanup cli_wallet::boost_fixture_test_case"); + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + + app1->shutdown(); +#ifdef _WIN32 + sockQuit(); +#endif +} + +bool cli_fixture::generate_maintenance_block() { + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + uint32_t skip = ~database::skip_fork_db; + auto db = app1->chain_database(); + auto maint_time = db->get_dynamic_global_properties().next_maintenance_time; + auto slots_to_miss = db->get_slot_at_time(maint_time); + db->generate_block(db->get_slot_time(slots_to_miss), + db->get_scheduled_witness(slots_to_miss), + committee_key, + skip); + return true; + } catch (exception& e) + { + return false; + } +} + +bool cli_fixture::generate_block() +{ + graphene::chain::signed_block returned_block; + return generate_block(returned_block); +} + +bool cli_fixture::generate_block(graphene::chain::signed_block& returned_block) +{ + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + auto db = app1->chain_database(); + returned_block = db->generate_block( db->get_slot_time(1), + db->get_scheduled_witness(1), + committee_key, + database::skip_nothing ); + return true; + } catch (exception &e) { + return false; + } +} + +void cli_fixture::init_nathan() +{ + try + { + BOOST_TEST_MESSAGE("Upgrade Nathan's account"); + + account_object nathan_acct_before_upgrade, nathan_acct_after_upgrade; + std::vector import_txs; + signed_transaction upgrade_tx; + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + BOOST_CHECK(generate_block()); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + + nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( + std::not_equal_to(), + (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) + ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} diff --git a/tests/cli/cli_fixture.hpp b/tests/cli/cli_fixture.hpp new file mode 100644 index 00000000..ad3e9e33 --- /dev/null +++ b/tests/cli/cli_fixture.hpp @@ -0,0 +1,81 @@ +#include +#include + +#include +#include +#include +#include + +#define INVOKE(test) ((struct test*)this)->test_method(); + +int get_available_port(); + +std::shared_ptr start_application(fc::temp_directory& app_dir, int& server_port_number); + +/////////// +/// @brief a class to make connecting to the application server easier +/////////// +class client_connection +{ +public: + client_connection( + std::shared_ptr app, + const fc::temp_directory& data_dir, + const int server_port_number + ); + ~client_connection(); +public: + fc::http::websocket_client websocket_client; + graphene::wallet::wallet_data wallet_data; + fc::http::websocket_connection_ptr websocket_connection; + std::shared_ptr api_connection; + fc::api remote_login_api; + std::shared_ptr wallet_api_ptr; + fc::api wallet_api; + std::shared_ptr wallet_cli; + std::string wallet_filename; +}; + +/////////////////////////////// +// Cli Wallet Fixture +/////////////////////////////// +struct cli_fixture +{ + class dummy + { + public: + ~dummy() + { + // wait for everything to finish up + fc::usleep(fc::milliseconds(500)); + } + }; + dummy dmy; + int server_port_number; + fc::temp_directory app_dir; + std::shared_ptr app1; + client_connection con; + std::vector nathan_keys; + + cli_fixture(); + ~cli_fixture(); + + /////////// + /// Send a block to the db + /// @param returned_block the signed block + /// @returns true on success + /////////// + bool generate_block(graphene::chain::signed_block& returned_block); + bool generate_block(); + /////////// + /// @brief Skip intermediate blocks, and generate a maintenance block + /// @returns true on success + /////////// + bool generate_maintenance_block(); + + /////////// + // @brief init "nathan" account and make it LTM to use in tests + ////////// + void init_nathan(); +}; + diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 9e7a4119..36fb626c 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -21,314 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include +#include "cli_fixture.hpp" #include #include -#ifdef _WIN32 -#ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x0501 - #endif - #include - #include -#else -#include -#include -#include -#endif -#include - -#include +#include #define BOOST_TEST_MODULE Test Application #include -/***** - * Global Initialization for Windows - * ( sets up Winsock stuf ) - */ -#ifdef _WIN32 -int sockInit(void) -{ - WSADATA wsa_data; - return WSAStartup(MAKEWORD(1,1), &wsa_data); -} -int sockQuit(void) -{ - return WSACleanup(); -} -#endif - -/********************* - * Helper Methods - *********************/ - -#include "../common/genesis_file_util.hpp" - -using std::exception; -using std::cerr; - -#define INVOKE(test) ((struct test*)this)->test_method(); - -////// -/// @brief attempt to find an available port on localhost -/// @returns an available port number, or -1 on error -///// -int get_available_port() -{ - struct sockaddr_in sin; - int socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) - return -1; - sin.sin_family = AF_INET; - sin.sin_port = 0; - sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - if (::bind(socket_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1) - return -1; - socklen_t len = sizeof(sin); - if (getsockname(socket_fd, (struct sockaddr *)&sin, &len) == -1) - return -1; -#ifdef _WIN32 - closesocket(socket_fd); -#else - close(socket_fd); -#endif - return ntohs(sin.sin_port); -} - -/////////// -/// @brief Start the application -/// @param app_dir the temporary directory to use -/// @param server_port_number to be filled with the rpc endpoint port number -/// @returns the application object -////////// -std::shared_ptr start_application(fc::temp_directory& app_dir, int& server_port_number) { - std::shared_ptr app1(new graphene::app::application{}); - - app1->register_plugin< graphene::bookie::bookie_plugin>(); - app1->register_plugin< graphene::account_history::account_history_plugin>(); - app1->register_plugin< graphene::market_history::market_history_plugin >(); - app1->register_plugin< graphene::witness_plugin::witness_plugin >(); - app1->register_plugin< graphene::accounts_list::accounts_list_plugin >(); - app1->register_plugin< graphene::affiliate_stats::affiliate_stats_plugin >(); - app1->startup_plugins(); - boost::program_options::variables_map cfg; -#ifdef _WIN32 - sockInit(); -#endif - server_port_number = get_available_port(); - cfg.emplace( - "rpc-endpoint", - boost::program_options::variable_value(string("127.0.0.1:" + std::to_string(server_port_number)), false) - ); - cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); - cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); - - app1->initialize(app_dir.path(), cfg); - - app1->initialize_plugins(cfg); - app1->startup_plugins(); - - app1->startup(); - fc::usleep(fc::milliseconds(500)); - return app1; -} - -/////////// -/// Send a block to the db -/// @param app the application -/// @param returned_block the signed block -/// @returns true on success -/////////// -bool generate_block(std::shared_ptr app, graphene::chain::signed_block& returned_block) -{ - try { - fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); - auto db = app->chain_database(); - returned_block = db->generate_block( db->get_slot_time(1), - db->get_scheduled_witness(1), - committee_key, - database::skip_nothing ); - return true; - } catch (exception &e) { - return false; - } -} - -bool generate_block(std::shared_ptr app) -{ - graphene::chain::signed_block returned_block; - return generate_block(app, returned_block); -} - -/////////// -/// @brief Skip intermediate blocks, and generate a maintenance block -/// @param app the application -/// @returns true on success -/////////// -bool generate_maintenance_block(std::shared_ptr app) { - try { - fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); - uint32_t skip = ~0; - auto db = app->chain_database(); - auto maint_time = db->get_dynamic_global_properties().next_maintenance_time; - auto slots_to_miss = db->get_slot_at_time(maint_time); - db->generate_block(db->get_slot_time(slots_to_miss), - db->get_scheduled_witness(slots_to_miss), - committee_key, - skip); - return true; - } catch (exception& e) - { - return false; - } -} - -/////////// -/// @brief a class to make connecting to the application server easier -/////////// -class client_connection -{ -public: - ///////// - // constructor - ///////// - client_connection( - std::shared_ptr app, - const fc::temp_directory& data_dir, - const int server_port_number - ) - { - wallet_data.chain_id = app->chain_database()->get_chain_id(); - wallet_data.ws_server = "ws://127.0.0.1:" + std::to_string(server_port_number); - wallet_data.ws_user = ""; - wallet_data.ws_password = ""; - websocket_connection = websocket_client.connect( wallet_data.ws_server ); - - api_connection = std::make_shared(*websocket_connection, GRAPHENE_MAX_NESTED_OBJECTS); - - remote_login_api = api_connection->get_remote_api< graphene::app::login_api >(1); - BOOST_CHECK(remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ) ); - - wallet_api_ptr = std::make_shared(wallet_data, remote_login_api); - wallet_filename = data_dir.path().generic_string() + "/wallet.json"; - wallet_api_ptr->set_wallet_filename(wallet_filename); - - wallet_api = fc::api(wallet_api_ptr); - - wallet_cli = std::make_shared(GRAPHENE_MAX_NESTED_OBJECTS); - for( auto& name_formatter : wallet_api_ptr->get_result_formatters() ) - wallet_cli->format_result( name_formatter.first, name_formatter.second ); - - boost::signals2::scoped_connection closed_connection(websocket_connection->closed.connect([=]{ - cerr << "Server has disconnected us.\n"; - wallet_cli->stop(); - })); - (void)(closed_connection); - } - ~client_connection() - { - // wait for everything to finish up - fc::usleep(fc::milliseconds(500)); - } -public: - fc::http::websocket_client websocket_client; - graphene::wallet::wallet_data wallet_data; - fc::http::websocket_connection_ptr websocket_connection; - std::shared_ptr api_connection; - fc::api remote_login_api; - std::shared_ptr wallet_api_ptr; - fc::api wallet_api; - std::shared_ptr wallet_cli; - std::string wallet_filename; -}; - - -/////////////////////////////// -// Cli Wallet Fixture -/////////////////////////////// - -struct cli_fixture -{ - class dummy - { - public: - ~dummy() - { - // wait for everything to finish up - fc::usleep(fc::milliseconds(500)); - } - }; - dummy dmy; - int server_port_number; - fc::temp_directory app_dir; - std::shared_ptr app1; - client_connection con; - std::vector nathan_keys; - - cli_fixture() : - server_port_number(0), - app_dir( graphene::utilities::temp_directory_path() ), - app1( start_application(app_dir, server_port_number) ), - con( app1, app_dir, server_port_number ), - nathan_keys( {"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"} ) - { - BOOST_TEST_MESSAGE("Setup cli_wallet::boost_fixture_test_case"); - - using namespace graphene::chain; - using namespace graphene::app; - - try - { - BOOST_TEST_MESSAGE("Setting wallet password"); - con.wallet_api_ptr->set_password("supersecret"); - con.wallet_api_ptr->unlock("supersecret"); - - // import Nathan account - BOOST_TEST_MESSAGE("Importing nathan key"); - BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); - BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); - } catch( fc::exception& e ) { - edump((e.to_detail_string())); - throw; - } - } - - ~cli_fixture() - { - BOOST_TEST_MESSAGE("Cleanup cli_wallet::boost_fixture_test_case"); - - // wait for everything to finish up - fc::usleep(fc::seconds(1)); - - app1->shutdown(); -#ifdef _WIN32 - sockQuit(); -#endif - } -}; - /////////////////////////////// // Tests /////////////////////////////// @@ -336,7 +38,9 @@ struct cli_fixture //////////////// // Start a server and connect using the same calls as the CLI //////////////// -BOOST_FIXTURE_TEST_CASE( cli_connect, cli_fixture ) +BOOST_FIXTURE_TEST_SUITE( cli_common, cli_fixture ) + +BOOST_AUTO_TEST_CASE( cli_connect ) { BOOST_TEST_MESSAGE("Testing wallet connection."); } @@ -353,44 +57,14 @@ BOOST_FIXTURE_TEST_CASE( cli_quit, cli_fixture ) BOOST_FIXTURE_TEST_CASE( upgrade_nathan_account, cli_fixture ) { - try - { - BOOST_TEST_MESSAGE("Upgrade Nathan's account"); - - account_object nathan_acct_before_upgrade, nathan_acct_after_upgrade; - std::vector import_txs; - signed_transaction upgrade_tx; - - BOOST_TEST_MESSAGE("Importing nathan's balance"); - import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); - nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); - - BOOST_CHECK(generate_block(app1)); - - // upgrade nathan - BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); - upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); - - nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); - - // verify that the upgrade was successful - BOOST_CHECK_PREDICATE( - std::not_equal_to(), - (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) - (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) - ); - BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); - } catch( fc::exception& e ) { - edump((e.to_detail_string())); - throw; - } + init_nathan(); } -BOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture ) +BOOST_AUTO_TEST_CASE( create_new_account ) { try { - INVOKE(upgrade_nathan_account); + init_nathan(); // create a new account graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); @@ -402,7 +76,7 @@ BOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture ) BOOST_CHECK(con.wallet_api_ptr->import_key("jmjatlanta", bki.wif_priv_key)); con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - BOOST_CHECK(generate_block(app1)); + BOOST_CHECK(generate_block()); fc::usleep( fc::seconds(1) ); // attempt to give jmjatlanta some peerplays @@ -439,9 +113,9 @@ BOOST_FIXTURE_TEST_CASE( cli_vote_for_2_witnesses, cli_fixture ) signed_transaction vote_witness1_tx = con.wallet_api_ptr->vote_for_witness("jmjatlanta", "init1", true, true); // generate a block to get things started - BOOST_CHECK(generate_block(app1)); + BOOST_CHECK(generate_block()); // wait for a maintenance interval - BOOST_CHECK(generate_maintenance_block(app1)); + BOOST_CHECK(generate_maintenance_block()); // Verify that the vote is there init1_obj = con.wallet_api_ptr->get_witness("init1"); @@ -454,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE( cli_vote_for_2_witnesses, cli_fixture ) signed_transaction vote_witness2_tx = con.wallet_api_ptr->vote_for_witness("jmjatlanta", "init2", true, true); // send another block to trigger maintenance interval - BOOST_CHECK(generate_maintenance_block(app1)); + BOOST_CHECK(generate_maintenance_block()); // Verify that both the first vote and the 2nd are there init2_obj = con.wallet_api_ptr->get_witness("init2"); @@ -510,7 +184,7 @@ BOOST_FIXTURE_TEST_CASE( cli_get_signed_transaction_signers, cli_fixture ) /////////////////////// // Check account history pagination /////////////////////// -BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) +BOOST_AUTO_TEST_CASE( account_history_pagination ) { try { @@ -524,7 +198,7 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) "1.3.0", "Here are some CORE token for your new account", true); } - BOOST_CHECK(generate_block(app1)); + BOOST_CHECK(generate_block()); // now get account history and make sure everything is there (and no duplicates) std::vector history = con.wallet_api_ptr->get_account_history("jmjatlanta", 300); @@ -798,18 +472,16 @@ graphene::wallet::plain_keys decrypt_keys( const std::string& password, const ve return fc::raw::unpack( decrypted ); } -BOOST_AUTO_TEST_CASE( saving_keys_wallet_test ) +BOOST_FIXTURE_TEST_CASE( saving_keys_wallet_test, cli_fixture ) { - cli_fixture cli; - - cli.con.wallet_api_ptr->import_balance( "nathan", cli.nathan_keys, true ); - cli.con.wallet_api_ptr->upgrade_account( "nathan", true ); + con.wallet_api_ptr->import_balance( "nathan", nathan_keys, true ); + con.wallet_api_ptr->upgrade_account( "nathan", true ); std::string brain_key( "FICTIVE WEARY MINIBUS LENS HAWKIE MAIDISH MINTY GLYPH GYTE KNOT COCKSHY LENTIGO PROPS BIFORM KHUTBAH BRAZIL" ); - cli.con.wallet_api_ptr->create_account_with_brain_key( brain_key, "account1", "nathan", "nathan", true ); + con.wallet_api_ptr->create_account_with_brain_key( brain_key, "account1", "nathan", "nathan", true ); - BOOST_CHECK_NO_THROW( cli.con.wallet_api_ptr->transfer( "nathan", "account1", "9000", "1.3.0", "", true ) ); + BOOST_CHECK_NO_THROW( con.wallet_api_ptr->transfer( "nathan", "account1", "9000", "1.3.0", "", true ) ); - std::string path( cli.app_dir.path().generic_string() + "/wallet.json" ); + std::string path( app_dir.path().generic_string() + "/wallet.json" ); graphene::wallet::wallet_data wallet = fc::json::from_file( path ).as( 2 * GRAPHENE_MAX_NESTED_OBJECTS ); BOOST_CHECK( wallet.extra_keys.size() == 1 ); // nathan BOOST_CHECK( wallet.pending_account_registrations.size() == 1 ); // account1 @@ -818,14 +490,16 @@ BOOST_AUTO_TEST_CASE( saving_keys_wallet_test ) graphene::wallet::plain_keys pk = decrypt_keys( "supersecret", wallet.cipher_keys ); BOOST_CHECK( pk.keys.size() == 1 ); // nathan key - BOOST_CHECK( generate_block( cli.app1 ) ); + BOOST_CHECK( generate_block() ); fc::usleep( fc::seconds(1) ); wallet = fc::json::from_file( path ).as( 2 * GRAPHENE_MAX_NESTED_OBJECTS ); BOOST_CHECK( wallet.extra_keys.size() == 2 ); // nathan + account1 BOOST_CHECK( wallet.pending_account_registrations.empty() ); - BOOST_CHECK_NO_THROW( cli.con.wallet_api_ptr->transfer( "account1", "nathan", "1000", "1.3.0", "", true ) ); + BOOST_CHECK_NO_THROW( con.wallet_api_ptr->transfer( "account1", "nathan", "1000", "1.3.0", "", true ) ); pk = decrypt_keys( "supersecret", wallet.cipher_keys ); BOOST_CHECK( pk.keys.size() == 3 ); // nathan key + account1 active key + account1 memo key } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/cli/son.cpp b/tests/cli/son.cpp new file mode 100644 index 00000000..87febbdd --- /dev/null +++ b/tests/cli/son.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2019 PBSA, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "cli_fixture.hpp" + +#include +#include + +#include + +class son_test_helper +{ + cli_fixture& fixture_; + +public: + son_test_helper(cli_fixture& fixture): + fixture_(fixture) + { + fixture_.init_nathan(); + } + + void create_son(const std::string& account_name, const std::string& son_url, + flat_map& sidechain_public_keys, + bool generate_maintenance = true) + { + graphene::wallet::brain_key_info bki; + signed_transaction create_tx; + signed_transaction transfer_tx; + signed_transaction upgrade_tx; + account_object son_account; + son_object son_obj; + + // create son account + bki = fixture_.con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + create_tx = fixture_.con.wallet_api_ptr->create_account_with_brain_key( + bki.brain_priv_key, account_name, "nathan", "nathan", true + ); + // save the private key for this new account in the wallet file + BOOST_CHECK(fixture_.con.wallet_api_ptr->import_key(account_name, bki.wif_priv_key)); + fixture_.con.wallet_api_ptr->save_wallet_file(fixture_.con.wallet_filename); + + // attempt to give son account some CORE tokens + BOOST_TEST_MESSAGE("Transferring CORE tokens from Nathan to son account"); + transfer_tx = fixture_.con.wallet_api_ptr->transfer( + "nathan", account_name, "65000", "1.3.0", "Here are some CORE token for your new account", true + ); + + BOOST_CHECK(fixture_.generate_block()); + + // upgrade son account + BOOST_TEST_MESSAGE("Upgrading son account to LTM"); + upgrade_tx = fixture_.con.wallet_api_ptr->upgrade_account(account_name, true); + son_account = fixture_.con.wallet_api_ptr->get_account(account_name); + + // verify that the upgrade was successful + BOOST_CHECK(son_account.is_lifetime_member()); + + BOOST_CHECK(fixture_.generate_block()); + + // create deposit vesting + fixture_.con.wallet_api_ptr->create_vesting_balance(account_name, + "50", "1.3.0", vesting_balance_type::son, true); + BOOST_CHECK(fixture_.generate_block()); + + // create pay_vb vesting + fixture_.con.wallet_api_ptr->create_vesting_balance(account_name, "1", "1.3.0", vesting_balance_type::normal, true); + BOOST_CHECK(fixture_.generate_block()); + + // check deposits are here + auto deposits = fixture_.con.wallet_api_ptr->get_vesting_balances(account_name); + BOOST_CHECK(deposits.size() >= 2); + + create_tx = fixture_.con.wallet_api_ptr->try_create_son(account_name, son_url, + sidechain_public_keys, + true); + + if (generate_maintenance) + BOOST_CHECK(fixture_.generate_maintenance_block()); + } + +}; + +/////////////////////// +// SON CLI +/////////////////////// +BOOST_FIXTURE_TEST_SUITE(son_cli, cli_fixture) + +BOOST_AUTO_TEST_CASE( create_sons ) +{ + BOOST_TEST_MESSAGE("SON cli wallet tests begin"); + try + { + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); + + auto son1_obj = con.wallet_api_ptr->get_son("son1account"); + BOOST_CHECK(son1_obj.son_account == con.wallet_api_ptr->get_account_id("son1account")); + BOOST_CHECK_EQUAL(son1_obj.url, "http://son1"); + + auto son2_obj = con.wallet_api_ptr->get_son("son2account"); + BOOST_CHECK(son2_obj.son_account == con.wallet_api_ptr->get_account_id("son2account")); + BOOST_CHECK_EQUAL(son2_obj.url, "http://son2"); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON cli wallet tests end"); +} + +BOOST_AUTO_TEST_CASE( cli_update_son ) +{ + try + { + BOOST_TEST_MESSAGE("Cli get_son and update_son Test"); + + flat_map sidechain_public_keys; + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + + son_test_helper sth(*this); + sth.create_son("sonmember", "http://sonmember", sidechain_public_keys); + + auto sonmember_acct = con.wallet_api_ptr->get_account("sonmember"); + + // get_son + auto son_data = con.wallet_api_ptr->get_son("sonmember"); + BOOST_CHECK(son_data.url == "http://sonmember"); + BOOST_CHECK(son_data.son_account == sonmember_acct.get_id()); + + // update SON + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + + con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated", "", sidechain_public_keys, true); + son_data = con.wallet_api_ptr->get_son("sonmember"); + BOOST_CHECK(son_data.url == "http://sonmember_updated"); + + // update SON signing key + sidechain_public_keys.clear(); + con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated2", "TEST6Yaq5ZNTTkMM2kBBzV5jktr8ETsniCC3bnVD7eFmegRrLXfGGG", sidechain_public_keys, true); + son_data = con.wallet_api_ptr->get_son("sonmember"); + BOOST_CHECK(son_data.url == "http://sonmember_updated2"); + BOOST_CHECK(std::string(son_data.signing_key) == "TEST6Yaq5ZNTTkMM2kBBzV5jktr8ETsniCC3bnVD7eFmegRrLXfGGG"); + + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( son_voting ) +{ + BOOST_TEST_MESSAGE("SON Vote cli wallet tests begin"); + try + { + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); + + BOOST_TEST_MESSAGE("Voting for SONs"); + + son_object son1_obj; + son_object son2_obj; + signed_transaction vote_son1_tx; + signed_transaction vote_son2_tx; + uint64_t son1_start_votes, son1_end_votes; + uint64_t son2_start_votes, son2_end_votes; + + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_start_votes = son1_obj.total_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_start_votes = son2_obj.total_votes; + + con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); + // Vote for a son1account + BOOST_TEST_MESSAGE("Voting for son1account"); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", true, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify that the vote is there + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes > son1_start_votes); + + // Vote for a son2account + BOOST_TEST_MESSAGE("Voting for son2account"); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", true, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify that the vote is there + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes > son2_start_votes); + + // Withdraw vote for a son1account + BOOST_TEST_MESSAGE("Withdraw vote for a son1account"); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", false, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify that the vote is removed + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + + // Withdraw vote for a son2account + BOOST_TEST_MESSAGE("Withdraw vote for a son2account"); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", false, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify that the vote is removed + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes == son2_start_votes); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON Vote cli wallet tests end"); +} + +BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON cli wallet tests begin"); + try + { + son_test_helper sth(*this); + + graphene::wallet::brain_key_info bki; + signed_transaction create_tx; + signed_transaction transfer_tx; + signed_transaction upgrade_tx; + signed_transaction vote_tx; + account_object acc_before_upgrade, acc_after_upgrade; + son_object son_obj; + global_property_object gpo; + + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 0; i < son_number + 1; i++) + { + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + + std::string name = "sonaccount" + fc::to_pretty_string(i); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + for(unsigned int i = 0; i < son_number; i++) + { + std::string name1 = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + } + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + + for(unsigned int i = 0; i < son_number - 1; i++) + { + std::string name1 = "sonaccount" + fc::to_pretty_string(i + 2); + std::string name2 = "sonaccount" + fc::to_pretty_string(i); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + } + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + + for(unsigned int i = 0; i < son_number - 2; i++) + { + std::string name1 = "sonaccount" + fc::to_pretty_string(i + 3); + std::string name2 = "sonaccount" + fc::to_pretty_string(i); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + } + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_CHECK(generate_maintenance_block()); + + BOOST_CHECK(gpo.active_sons.size() == 15); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON cli wallet tests end"); +} + +BOOST_AUTO_TEST_CASE( list_son ) +{ + BOOST_TEST_MESSAGE("List SONs cli wallet tests begin"); + try + { + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); + + auto res = con.wallet_api_ptr->list_sons("", 100); + BOOST_REQUIRE(res.size() == 2); + BOOST_CHECK(res.find("son1account") != res.end()); + BOOST_CHECK(res.find("son2account") != res.end()); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("List SONs cli wallet tests end"); +} + +BOOST_AUTO_TEST_CASE( update_son_votes_test ) +{ + BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests begin"); + try + { + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); + + BOOST_TEST_MESSAGE("Vote for 2 accounts with update_son_votes"); + + son_object son1_obj; + son_object son2_obj; + uint64_t son1_start_votes, son1_end_votes; + uint64_t son2_start_votes, son2_end_votes; + + // Get votes at start + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_start_votes = son1_obj.total_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_start_votes = son2_obj.total_votes; + + std::vector accepted; + std::vector rejected; + signed_transaction update_votes_tx; + + // Vote for both SONs + accepted.clear(); + rejected.clear(); + accepted.push_back("son1account"); + accepted.push_back("son2account"); + con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 2, true); + BOOST_CHECK(generate_block()); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes > son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes > son2_start_votes); + son2_start_votes = son2_end_votes; + + + // Withdraw vote for SON 1 + accepted.clear(); + rejected.clear(); + rejected.push_back("son1account"); + con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 1, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes < son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + // voice distribution changed, SON2 now has all voices + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK((son2_end_votes > son2_start_votes)); // nathan spent funds for vb, it has different voting power + son2_start_votes = son2_end_votes; + + // Try to reject incorrect SON + accepted.clear(); + rejected.clear(); + rejected.push_back("son1accnt"); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 1, true), fc::exception); + BOOST_CHECK(generate_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes == son2_start_votes); + son2_start_votes = son2_end_votes; + + // Reject SON2 + accepted.clear(); + rejected.clear(); + rejected.push_back("son2account"); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 0, true); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes < son2_start_votes); + son2_start_votes = son2_end_votes; + + // Try to accept and reject the same SON + accepted.clear(); + rejected.clear(); + rejected.push_back("son1accnt"); + accepted.push_back("son1accnt"); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 1, true), fc::exception); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes == son2_start_votes); + son2_start_votes = son2_end_votes; + + // Try to accept and reject empty lists + accepted.clear(); + rejected.clear(); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 1, true), fc::exception); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes == son2_start_votes); + son2_start_votes = son2_end_votes; + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests end"); +} + +BOOST_AUTO_TEST_CASE( related_functions ) +{ + BOOST_TEST_MESSAGE("SON-related functions cli wallet tests begin"); + try + { + global_property_object gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons.size() == 0); + + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); + + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons.size() == 2); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON-related functions cli wallet tests end"); +} + +BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons begin"); + try + { + son_test_helper sth(*this); + + signed_transaction vote_tx; + global_property_object gpo; + + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + for(unsigned int i = 1; i < son_number; i++) + { + std::string name1 = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + + BOOST_CHECK(gpo.active_sons.size() == son_number); + + map active_sons = con.wallet_api_ptr->list_active_sons(); + BOOST_CHECK(active_sons.size() == son_number); + for(unsigned int i = 1; i < son_number + 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + BOOST_CHECK(active_sons.find(name) != active_sons.end()); + } + + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->list_active_sons()); + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons end"); +} + +BOOST_AUTO_TEST_CASE( maintenance_test ) +{ + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests begin"); + try + { + son_test_helper sth(*this); + + std::string name("sonaccount1"); + + global_property_object gpo; + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + son_object son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + // put SON in maintenance mode + con.wallet_api_ptr->request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is in request_maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::request_maintenance); + + // restore SON activity + con.wallet_api_ptr->cancel_request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is active + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + // put SON in maintenance mode + con.wallet_api_ptr->request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is in request_maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::request_maintenance); + + // process maintenance + BOOST_CHECK(generate_maintenance_block()); + + // check SON is in maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::in_maintenance); + + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests end"); +} + +BOOST_AUTO_TEST_SUITE_END() + + + diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 3a381585..b5ce1f55 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -50,6 +50,8 @@ #include #include +#include +#include #include #include "database_fixture.hpp" @@ -326,9 +328,10 @@ void database_fixture::verify_asset_supplies( const database& db ) uint64_t sweeps_vestings = 0; for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() ) sweeps_vestings += svbo.balance; - + total_balances[db.get_global_properties().parameters.sweeps_distribution_asset()] += sweeps_vestings / SWEEPS_VESTING_BALANCE_MULTIPLIER; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; + total_balances[asset_id_type()] += db.get_dynamic_global_properties().son_budget; for( const auto& item : total_debts ) { @@ -469,6 +472,23 @@ void database_fixture::generate_blocks(fc::time_point_sec timestamp, bool miss_i generate_block(skip); } +bool database_fixture::generate_maintenance_block() { + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + uint32_t skip = ~database::skip_fork_db; + auto maint_time = db.get_dynamic_global_properties().next_maintenance_time; + auto slots_to_miss = db.get_slot_at_time(maint_time); + db.generate_block(db.get_slot_time(slots_to_miss), + db.get_scheduled_witness(slots_to_miss), + committee_key, + skip); + return true; + } catch (std::exception& e) + { + return false; + } +} + account_create_operation database_fixture::make_account( const std::string& name /* = "nathan" */, public_key_type key /* = key_id_type() */ @@ -787,7 +807,7 @@ const witness_object& database_fixture::create_witness( const account_object& ow witness_create_operation op; op.witness_account = owner.id; op.block_signing_key = signing_private_key.get_public_key(); - + secret_hash_type::encoder enc; fc::raw::pack(enc, signing_private_key); fc::raw::pack(enc, secret_hash_type()); @@ -1172,12 +1192,12 @@ int64_t database_fixture::get_balance( const account_object& account, const asse } int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type, - account_id_type dividend_holder_account_id, - asset_id_type dividend_payout_asset_type) const + account_id_type dividend_holder_account_id, + asset_id_type dividend_payout_asset_type) const { - const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = + const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type(); - auto pending_payout_iter = + auto pending_payout_iter = pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) return 0; @@ -1398,7 +1418,7 @@ void database_fixture::delete_sport(sport_id_type sport_id) sport_delete_op.sport_id = sport_id; process_operation_by_witnesses(sport_delete_op); } FC_CAPTURE_AND_RETHROW( (sport_id) ) } - + const event_group_object& database_fixture::create_event_group(internationalized_string_type name, sport_id_type sport_id) { try { event_group_create_operation event_group_create_op; @@ -1428,7 +1448,7 @@ void database_fixture::delete_event_group(event_group_id_type event_group_id) process_operation_by_witnesses(event_group_delete_op); } FC_CAPTURE_AND_RETHROW( (event_group_id) ) } - + void database_fixture::try_update_event_group(event_group_id_type event_group_id, fc::optional sport_id, fc::optional name, @@ -1468,7 +1488,7 @@ void database_fixture::update_event_impl(event_id_type event_id, fc::optional event_group_id, fc::optional name, fc::optional season, - fc::optional status, + fc::optional status, bool force) { try { event_update_operation event_update_op; @@ -1505,9 +1525,9 @@ void database_fixture::update_betting_market_rules(betting_market_rules_id_type process_operation_by_witnesses(betting_market_rules_update_op); } FC_CAPTURE_AND_RETHROW( (name)(description) ) } -const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description, - event_id_type event_id, - betting_market_rules_id_type rules_id, +const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description, + event_id_type event_id, + betting_market_rules_id_type rules_id, asset_id_type asset_id, bool never_in_play, uint32_t delay_before_settling) @@ -1577,7 +1597,7 @@ void database_fixture::update_betting_market(betting_market_id_type betting_mark bet_place_op.amount_to_bet = amount_to_bet; bet_place_op.backer_multiplier = backer_multiplier; bet_place_op.back_or_lay = back_or_lay; - + trx.operations.push_back(bet_place_op); trx.validate(); processed_transaction ptx = db.push_transaction(trx, ~0); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 200d1897..a190b9c6 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -199,6 +199,12 @@ struct database_fixture { */ void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0); + /////////// + /// @brief Skip intermediate blocks, and generate a maintenance block + /// @returns true on success + /////////// + bool generate_maintenance_block(); + account_create_operation make_account( const std::string& name = "nathan", public_key_type = public_key_type() @@ -295,7 +301,7 @@ struct database_fixture { int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; int64_t get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type, - account_id_type dividend_holder_account_id, + account_id_type dividend_holder_account_id, asset_id_type dividend_payout_asset_type) const; vector< operation_history_object > get_operation_history( account_id_type account_id )const; void process_operation_by_witnesses(operation op); @@ -321,7 +327,7 @@ struct database_fixture { fc::optional season, fc::optional status, bool force); - BOOST_PARAMETER_MEMBER_FUNCTION((void), update_event, keywords::tag, + BOOST_PARAMETER_MEMBER_FUNCTION((void), update_event, keywords::tag, (required (event_id, (event_id_type))) (optional (event_group_id, (fc::optional), fc::optional()) (name, (fc::optional), fc::optional()) @@ -336,9 +342,9 @@ struct database_fixture { void update_betting_market_rules(betting_market_rules_id_type rules_id, fc::optional name, fc::optional description); - const betting_market_group_object& create_betting_market_group(internationalized_string_type description, - event_id_type event_id, - betting_market_rules_id_type rules_id, + const betting_market_group_object& create_betting_market_group(internationalized_string_type description, + event_id_type event_id, + betting_market_rules_id_type rules_id, asset_id_type asset_id, bool never_in_play, uint32_t delay_before_settling); @@ -347,7 +353,7 @@ struct database_fixture { fc::optional rules_id, fc::optional status, bool force); - BOOST_PARAMETER_MEMBER_FUNCTION((void), update_betting_market_group, keywords::tag, + BOOST_PARAMETER_MEMBER_FUNCTION((void), update_betting_market_group, keywords::tag, (required (betting_market_group_id, (betting_market_group_id_type))) (optional (description, (fc::optional), fc::optional()) (rules_id, (fc::optional), fc::optional()) diff --git a/tests/elasticsearch/main.cpp b/tests/elasticsearch/main.cpp index 28d3522c..c948616f 100644 --- a/tests/elasticsearch/main.cpp +++ b/tests/elasticsearch/main.cpp @@ -231,7 +231,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { create_bitasset("USD", account_id_type()); // create op 0 const account_object& dan = create_account("dan"); // create op 1 create_bitasset("CNY", dan.id); // create op 2 - create_bitasset("BTC", account_id_type()); // create op 3 + create_bitasset("BTCTEST", account_id_type()); // create op 3 create_bitasset("XMR", dan.id); // create op 4 create_bitasset("EUR", account_id_type()); // create op 5 create_bitasset("OIL", dan.id); // create op 6 diff --git a/tests/peerplays_sidechain/bitcoin_address_tests.cpp b/tests/peerplays_sidechain/bitcoin_address_tests.cpp new file mode 100644 index 00000000..a6909e24 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_address_tests.cpp @@ -0,0 +1,316 @@ +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; + +BOOST_AUTO_TEST_SUITE(bitcoin_address_tests) + +fc::ecc::public_key_data create_public_key_data(const std::vector &public_key) { + FC_ASSERT(public_key.size() == 33); + fc::ecc::public_key_data key; + for (size_t i = 0; i < 33; i++) { + key.at(i) = public_key[i]; + } + return key; +} + +BOOST_AUTO_TEST_CASE(addresses_type_test) { + // public_key + std::string compressed("03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22"); + BOOST_CHECK(bitcoin_address(compressed).get_type() == payment_type::P2PK); + + std::string uncompressed("04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904"); + BOOST_CHECK(bitcoin_address(uncompressed).get_type() == payment_type::NULLDATA); + + // segwit_address + std::string p2wpkh_mainnet("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); + BOOST_CHECK(bitcoin_address(p2wpkh_mainnet).get_type() == payment_type::P2WPKH); + + std::string p2wpkh_testnet("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"); + BOOST_CHECK(bitcoin_address(p2wpkh_testnet).get_type() == payment_type::P2WPKH); + + std::string p2wsh("bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9"); + BOOST_CHECK(bitcoin_address(p2wsh).get_type() == payment_type::P2WSH); + + // base58 + std::string p2pkh_mainnet("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"); + BOOST_CHECK(bitcoin_address(p2pkh_mainnet).get_type() == payment_type::P2PKH); + + std::string p2pkh_testnet("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"); + BOOST_CHECK(bitcoin_address(p2pkh_testnet).get_type() == payment_type::P2PKH); + + std::string p2sh_mainnet("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"); + BOOST_CHECK(bitcoin_address(p2sh_mainnet).get_type() == payment_type::P2SH); + + std::string p2sh_testnet("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc"); + BOOST_CHECK(bitcoin_address(p2sh_testnet).get_type() == payment_type::P2SH); + + std::string p2sh_regtest1("2NAL3YhMF4VcbRQdectN8XPMJipvATGefTZ"); + BOOST_CHECK(bitcoin_address(p2sh_regtest1).get_type() == payment_type::P2SH); +} + +BOOST_AUTO_TEST_CASE(addresses_raw_test) { + // public_key + std::string compressed("03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22"); + bytes standard_compressed(parse_hex(compressed)); + BOOST_CHECK(bitcoin_address(compressed).get_raw_address() == standard_compressed); + + std::string uncompressed("04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904"); + BOOST_CHECK(bitcoin_address(uncompressed).get_raw_address() == bytes()); + + // segwit_address + std::string p2wpkh_mainnet("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); + bytes standard_p2wpkh_mainnet(parse_hex("751e76e8199196d454941c45d1b3a323f1433bd6")); + BOOST_CHECK(bitcoin_address(p2wpkh_mainnet).get_raw_address() == standard_p2wpkh_mainnet); + + std::string p2wpkh_testnet("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"); + bytes standard_p2wpkh_testnet(parse_hex("751e76e8199196d454941c45d1b3a323f1433bd6")); + BOOST_CHECK(bitcoin_address(p2wpkh_testnet).get_raw_address() == standard_p2wpkh_testnet); + + std::string p2wsh("bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9"); + bytes standard_p2wsh(parse_hex("c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d")); + BOOST_CHECK(bitcoin_address(p2wsh).get_raw_address() == standard_p2wsh); + + // base58 + std::string p2pkh_mainnet("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"); + bytes standard_p2pkh_mainnet(parse_hex("47376c6f537d62177a2c41c4ca9b45829ab99083")); + BOOST_CHECK(bitcoin_address(p2pkh_mainnet).get_raw_address() == standard_p2pkh_mainnet); + + std::string p2pkh_testnet("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"); + bytes standard_p2pkh_testnet(parse_hex("243f1394f44554f4ce3fd68649c19adc483ce924")); + BOOST_CHECK(bitcoin_address(p2pkh_testnet).get_raw_address() == standard_p2pkh_testnet); + + std::string p2sh_mainnet("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"); + bytes standard_p2sh_mainnet(parse_hex("8f55563b9a19f321c211e9b9f38cdf686ea07845")); + BOOST_CHECK(bitcoin_address(p2sh_mainnet).get_raw_address() == standard_p2sh_mainnet); + + std::string p2sh_testnet("2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc"); + bytes standard_p2sh_testnet(parse_hex("4e9f39ca4688ff102128ea4ccda34105324305b0")); + BOOST_CHECK(bitcoin_address(p2sh_testnet).get_raw_address() == standard_p2sh_testnet); +} + +BOOST_AUTO_TEST_CASE(create_multisig_address_test) { + + std::vector public_key1 = parse_hex("03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010"); + std::vector public_key2 = parse_hex("0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983"); + std::vector public_key3 = parse_hex("033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f"); + + std::vector address = parse_hex("a91460cb986f0926e7c4ca1984ca9f56767da2af031e87"); + std::vector redeem_script = parse_hex("522103db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010210320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b441798321033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f53ae"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + + btc_multisig_address cma(2, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}}); + + BOOST_CHECK(address == cma.raw_address); + BOOST_CHECK(redeem_script == cma.redeem_script); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_test) { + // https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa + std::vector public_key1 = parse_hex("03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb"); + std::vector public_key2 = parse_hex("03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6"); + std::vector public_key3 = parse_hex("033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1"); + + std::vector witness_script = parse_hex("0020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + + btc_multisig_segwit_address address(2, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}}); + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk"); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_10_of_14_test) { + std::vector public_key1 = parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"); + std::vector public_key2 = parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"); + std::vector public_key3 = parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"); + std::vector public_key4 = parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"); + std::vector public_key5 = parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"); + std::vector public_key6 = parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"); + std::vector public_key7 = parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"); + std::vector public_key8 = parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"); + std::vector public_key9 = parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"); + std::vector public_key10 = parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"); + std::vector public_key11 = parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"); + std::vector public_key12 = parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"); + std::vector public_key13 = parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"); + std::vector public_key14 = parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"); + + std::vector witness_script = parse_hex("0020b70a52b55974d3c8c1a2055bf23e2d6421942135c7be1f786ad8cbce2f532cef"); + std::vector redeem_script = parse_hex("5a2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae85eae"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + fc::ecc::public_key_data key4 = create_public_key_data(public_key4); + fc::ecc::public_key_data key5 = create_public_key_data(public_key5); + fc::ecc::public_key_data key6 = create_public_key_data(public_key6); + fc::ecc::public_key_data key7 = create_public_key_data(public_key7); + fc::ecc::public_key_data key8 = create_public_key_data(public_key8); + fc::ecc::public_key_data key9 = create_public_key_data(public_key9); + fc::ecc::public_key_data key10 = create_public_key_data(public_key10); + fc::ecc::public_key_data key11 = create_public_key_data(public_key11); + fc::ecc::public_key_data key12 = create_public_key_data(public_key12); + fc::ecc::public_key_data key13 = create_public_key_data(public_key13); + fc::ecc::public_key_data key14 = create_public_key_data(public_key14); + + btc_multisig_segwit_address address(10, { + {son_id_type(1), public_key_type(key1)}, + {son_id_type(2), public_key_type(key2)}, + {son_id_type(3), public_key_type(key3)}, + {son_id_type(4), public_key_type(key4)}, + {son_id_type(5), public_key_type(key5)}, + {son_id_type(6), public_key_type(key6)}, + {son_id_type(7), public_key_type(key7)}, + {son_id_type(8), public_key_type(key8)}, + {son_id_type(9), public_key_type(key9)}, + {son_id_type(10), public_key_type(key10)}, + {son_id_type(11), public_key_type(key11)}, + {son_id_type(12), public_key_type(key12)}, + {son_id_type(13), public_key_type(key13)}, + {son_id_type(14), public_key_type(key14)}, + }); + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_redeem_script() == redeem_script); + BOOST_CHECK(address.get_address() == "2MwhYhBrZeb6mTf1kaWcYfLBCCQoMqQybFZ"); +} + +BOOST_AUTO_TEST_CASE(create_segwit_address_11_of_15_test) { + std::vector public_key1 = parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"); + std::vector public_key2 = parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"); + std::vector public_key3 = parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"); + std::vector public_key4 = parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"); + std::vector public_key5 = parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"); + std::vector public_key6 = parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"); + std::vector public_key7 = parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"); + std::vector public_key8 = parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"); + std::vector public_key9 = parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"); + std::vector public_key10 = parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"); + std::vector public_key11 = parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"); + std::vector public_key12 = parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"); + std::vector public_key13 = parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"); + std::vector public_key14 = parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"); + std::vector public_key15 = parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"); + + fc::ecc::public_key_data key1 = create_public_key_data(public_key1); + fc::ecc::public_key_data key2 = create_public_key_data(public_key2); + fc::ecc::public_key_data key3 = create_public_key_data(public_key3); + fc::ecc::public_key_data key4 = create_public_key_data(public_key4); + fc::ecc::public_key_data key5 = create_public_key_data(public_key5); + fc::ecc::public_key_data key6 = create_public_key_data(public_key6); + fc::ecc::public_key_data key7 = create_public_key_data(public_key7); + fc::ecc::public_key_data key8 = create_public_key_data(public_key8); + fc::ecc::public_key_data key9 = create_public_key_data(public_key9); + fc::ecc::public_key_data key10 = create_public_key_data(public_key10); + fc::ecc::public_key_data key11 = create_public_key_data(public_key11); + fc::ecc::public_key_data key12 = create_public_key_data(public_key12); + fc::ecc::public_key_data key13 = create_public_key_data(public_key13); + fc::ecc::public_key_data key14 = create_public_key_data(public_key14); + fc::ecc::public_key_data key15 = create_public_key_data(public_key15); + + std::vector witness_script = parse_hex("00205fcdce3c62b477553b8dc957a935adda8305cbd47a408f07d2cb867d588279eb"); + std::vector redeem_script = parse_hex("5b2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae"); + + btc_multisig_segwit_address address(11, {{son_id_type(1), public_key_type(key1)}, {son_id_type(2), public_key_type(key2)}, {son_id_type(3), public_key_type(key3)}, {son_id_type(4), public_key_type(key4)}, {son_id_type(5), public_key_type(key5)}, {son_id_type(6), public_key_type(key6)}, {son_id_type(7), public_key_type(key7)}, {son_id_type(8), public_key_type(key8)}, {son_id_type(9), public_key_type(key9)}, {son_id_type(10), public_key_type(key10)}, {son_id_type(11), public_key_type(key11)}, {son_id_type(12), public_key_type(key12)}, {son_id_type(13), public_key_type(key13)}, {son_id_type(14), public_key_type(key14)}, {son_id_type(15), public_key_type(key15)}}); + + BOOST_CHECK(address.get_witness_script() == witness_script); + BOOST_CHECK(address.get_redeem_script() == redeem_script); + BOOST_CHECK(address.get_address() == "2NAL3YhMF4VcbRQdectN8XPMJipvATGefTZ"); +} + +BOOST_AUTO_TEST_CASE(btc_weighted_multisig_address_test) { + std::vector priv_old; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_old; + for (auto &key : priv_old) + pub_old.push_back(key.get_public_key()); + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_old[i], i + 1)); + + btc_weighted_multisig_address addr(weights, btc_weighted_multisig_address::network::testnet); + BOOST_CHECK(addr.get_address() == "tb1qj4dwcpsqxhszc6ee5wwndqxe04j4vzs0qksrzgscfarzduu6th8qlyqaqp"); +} + +BOOST_AUTO_TEST_CASE(create_1_or_2_of_15_segwit_address_test) { + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + btc_one_or_m_of_n_multisig_address address(user_key, 2, {public_key1, public_key2, public_key3, public_key4, public_key5, public_key6, public_key7, public_key8, public_key9, public_key10, public_key11, public_key12, public_key13, public_key14, public_key15}); + + std::vector redeem_script = parse_hex("63210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae68"); + + BOOST_CHECK(address.get_address() == "bcrt1qp8vplzrn7alzpjq8cw4ynd6xqzassmefrh48dzsj0krvkq85dywq9txkqr"); + BOOST_CHECK(address.get_redeem_script() == redeem_script); +} + +BOOST_AUTO_TEST_CASE(create_1_or_multiweighted_seggwit_address_test) { + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_pub_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + // key weights + std::vector> weights; + weights.push_back(std::make_pair(public_key1, 1)); + weights.push_back(std::make_pair(public_key2, 1)); + weights.push_back(std::make_pair(public_key3, 1)); + weights.push_back(std::make_pair(public_key4, 1)); + weights.push_back(std::make_pair(public_key5, 1)); + weights.push_back(std::make_pair(public_key6, 1)); + weights.push_back(std::make_pair(public_key7, 1)); + weights.push_back(std::make_pair(public_key8, 1)); + weights.push_back(std::make_pair(public_key9, 1)); + weights.push_back(std::make_pair(public_key10, 1)); + weights.push_back(std::make_pair(public_key11, 1)); + weights.push_back(std::make_pair(public_key12, 1)); + weights.push_back(std::make_pair(public_key13, 1)); + weights.push_back(std::make_pair(public_key14, 1)); + weights.push_back(std::make_pair(public_key15, 1)); + + btc_one_or_weighted_multisig_address addr(user_pub_key, weights); + auto redeem_script = parse_hex("210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac635167007c2103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275ac635193687c2102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4ac635193687c21025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61ac635193687c210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866ac635193687c21037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666ac635193687c2102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccfac635193687c210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7fac635193687c210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84ac635193687c21023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccfac635193687c210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5ac635193687c21024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8daac635193687c2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1ac635193687c2102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8ac635193687c210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8ac635193687c2102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73ac635193685ba268"); + + BOOST_CHECK(addr.get_address() == "bcrt1qz2cykw980zns8uxpt7dsn4ku7ce24cq3z8mna0tttzhw2qcr4y0qxmgej2"); + BOOST_CHECK(addr.get_redeem_script() == redeem_script); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp new file mode 100644 index 00000000..82a121c9 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp @@ -0,0 +1,449 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; +using namespace fc::ecc; + +BOOST_AUTO_TEST_SUITE(bitcoin_sign_tests) + +const secp256k1_context_t *btc_context() { + static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +inline bytes get_privkey_bytes(const std::string &privkey_base58) { + const auto privkey = fc::from_base58(privkey_base58); + // Remove version and hash + return bytes(privkey.cbegin() + 1, privkey.cbegin() + 1 + 32); +} + +BOOST_AUTO_TEST_CASE(btc_tx_witness_signature_test) { + bitcoin_transaction tx; + tx.nVersion = 1; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("0a510f49749aaaa2638048132eafea959dd8e47e79332dbcb2a14189870e3145"); + tx.vin[0].prevout.n = 1; + tx.vin[0].scriptSig = parse_hex("220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1"); + tx.vin[0].nSequence = 4294967295; + + tx.vout[0].value = 20000000; + tx.vout[0].scriptPubKey = parse_hex("76a9143ebc40e411ed3c76f86711507ab952300890397288ac"); + + const auto privkey_1 = get_privkey_bytes("cQPUeypiYqp8J8Y8dGXUhvWGPHXTYYs3haryjdquwvMLAabXAnzF"); + const auto privkey_2 = get_privkey_bytes("cTG9AXoZjPbUmU2rx4ojeUBm3P88q3ZvRR6YWTUeVJHyke5KbVPM"); + const auto redeemScript = parse_hex("522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae"); + uint64_t amount = 20021300; + int32_t hash_type = 1; + + tx.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx, {redeemScript}, {amount}, privkey_1, btc_context(), hash_type)[0]); + tx.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx, {redeemScript}, {amount}, privkey_2, btc_context(), hash_type)[0]); + sign_witness_transaction_finalize(tx, {redeemScript}); + + BOOST_CHECK(fc::to_hex(pack(tx)) == "0100000000010145310e878941a1b2bc2d33797ee4d89d95eaaf2e13488063a2aa9a74490f510a0100000023220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1ffffffff01002d3101000000001976a9143ebc40e411ed3c76f86711507ab952300890397288ac0400473044022001dd489a5d4e2fbd8a3ade27177f6b49296ba7695c40dbbe650ea83f106415fd02200b23a0602d8ff1bdf79dee118205fc7e9b40672bf31563e5741feb53fb86388501483045022100f88f040e90cc5dc6c6189d04718376ac19ed996bf9e4a3c29c3718d90ffd27180220761711f16c9e3a44f71aab55cbc0634907a1fa8bb635d971a9a01d368727bea10169522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae00000000"); +} + +BOOST_AUTO_TEST_CASE(verify_sig_test) { + std::string hash("df074d23cbedea48308aa2161c5b0da3893cc898e143c285ae4d5d787b366f10"); + bytes vec_hash(parse_hex(hash)); + + std::string key1("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"); + bytes vec_key1(parse_hex(key1)); + + std::string sig("3044022051641c36fc6bc1e7ddd0022259c3f3a8dce0ac7fa4538c8b303c49e14b216b5302204e64c88a7f0279d902ccb338ffd42941ccdbbd7ddf8ba17d1ebf693f1f0b3ed901"); + bytes vec_sig(parse_hex(sig)); + + BOOST_CHECK(verify_sig(vec_sig, vec_key1, vec_hash, btc_context())); + + std::string key2("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435ca"); + bytes vec_key2(parse_hex(key2)); + + BOOST_CHECK(!verify_sig(vec_sig, vec_key2, vec_hash, btc_context())); +} + +BOOST_AUTO_TEST_CASE(get_pubkey_from_redeemScript_test) { + std::string script("5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae"); + + std::vector keys = {parse_hex("025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf"), + parse_hex("02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf")}; + + std::vector keys_from_script = get_pubkey_from_redeemScript(parse_hex(script)); + + BOOST_CHECK(keys.size() == keys_from_script.size()); + for (size_t i = 0; i < keys.size(); i++) { + BOOST_CHECK(keys[i] == keys_from_script[i]); + } + + std::string script2("5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e95bae"); + + std::vector keys_from_script2 = get_pubkey_from_redeemScript(parse_hex(script2)); + BOOST_CHECK(keys_from_script2.size() == 1); + BOOST_CHECK(keys_from_script2[0] == parse_hex("025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9")); +} + +BOOST_AUTO_TEST_CASE(sort_sig_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\",\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\",\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\",\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\",\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[5993981520,1200000000,1200000000,1200000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(already_sorted_sigs_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[5993981520,1200000000,1200000000,1200000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(all_signatures_are_same_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e35635fce1cbe1e347a5f8a3c5dccd79db9d43a6fb4b27991894a8bce1a70e03\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"]},{\"prevout\":{\"hash\":\"1826f1ba0ed5034806cf1cb3eaa5dc9abf04a319048ae1e73e3df75dcc9f0bca\",\"n\":1},\"scriptSig\":\"2200203b9e077c0043e8f394a273baffc0aed01d10d8c894ad39810257d63be9a315e0\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]}],\"vout\":[{\"value\":1997997990,\"scriptPubKey\":\"0020a40e801531fdca0fb550013a9140aaaf8d9eb106c427258fbc86d0e0c52c432d\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[998998995,1000000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"],[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(same_signatures_test) { + bitcoin_transaction trx(fc::json::from_string("{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"f8d70d29817a78e160c53dcffcf6a783008f1cc43c64f7efb49e4de3a23ef016\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"]},{\"prevout\":{\"hash\":\"dd54a8c470be54b02cd937a1758761863c2faf920b2645d2dd35bd0308ef0dfb\",\"n\":1},\"scriptSig\":\"220020f38dc1aecea9e28bea4410e6aa807be49cf6472b9a718750080b9703e80d9fe9\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\"]}],\"vout\":[{\"value\":2996996985,\"scriptPubKey\":\"0020a4d938999fff18a140d830009f8c9a2c5ab00d61cc3ffea10ee703b7d9b24b9a\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":66667,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":66667,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":66667,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":66667,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}").as(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector scripts(fc::json::from_string("[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector amounts(fc::json::from_string("[1997997990,1000000000]").as>(GRAPHENE_MAX_NESTED_OBJECTS)); + std::vector> results(fc::json::from_string("[[\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\"],[\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\"]]").as>>(GRAPHENE_MAX_NESTED_OBJECTS)); + + auto new_stacks = sort_sigs(trx, scripts, amounts, btc_context()); + for (size_t i = 0; i < trx.vin.size(); i++) { + BOOST_CHECK(new_stacks[i] == results[i]); + } +} + +BOOST_AUTO_TEST_CASE(weighted_multisig_spend_test) { + // create weighted multisig addess in regtest + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + btc_weighted_multisig_address addr(weights); + BOOST_CHECK(addr.get_address() == "bcrt1qj4dwcpsqxhszc6ee5wwndqxe04j4vzs0qksrzgscfarzduu6th8qja2m4m"); + + bytes redeem_script = addr.get_redeem_script(); + + // this address was filled with regtest transaction + // id 577c1abcbf87db230c499c3585a5f1e20a835fe2d39cade39c232554bd21d0b5 + // output 0, 10000 satoshis + + // now send it back to bcrt1qavkxpjkc30x0euepxup8qe2yfzpjyepzq0qctu + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("577c1abcbf87db230c499c3585a5f1e20a835fe2d39cade39c232554bd21d0b5"); + tx.vin[0].prevout.n = 0; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + bitcoin_address to_address("bcrt1qavkxpjkc30x0euepxup8qe2yfzpjyepzq0qctu"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + for (auto &key : priv_keys) { + bytes key_data(key.get_secret().data(), key.get_secret().data() + key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + // insert signatures in reverse order + tx.vin[0].scriptWitness.insert(tx.vin[0].scriptWitness.begin(), sigs[0]); + } + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000000101b5d021bd5425239ce3ad9cd3e25f830ae2f1a585359c490c23db87bfbc1a7c570000000000ffffffff012823000000000000160014eb2c60cad88bccfcf321370270654448832264221047304402204da1308f748ad89a268cf1c33fce2f88a39a7bcd8a60c5d2c95d7e2f2087d587022011ce8024743620c0388d4b998c8db28ff464928decbdb6114fab185033abbea00147304402206344d532aebe1ca501f6ae7191c5b7497d52fe582358c65dce9a20bd52b4b53402205dfc03292fdc5670dba1e73f9d418e7cbe1b18a8511eb9d7a99c7140f41b9bbc01483045022100a73580772db51b534673eebfc5c0d165a117fdafded7e4e5970a7452ff73cd2702200e616c6660011855c370b37b227c67ddcfda1dab08ff8c2027f313b82a00cbd501483045022100bfcf88627d42ac61ec8688292874d8326ad9015141c1ff203a07148f110f9dee022057d2c111ffc53b02bd462f88363563f4bbd10a9d317fe9ae02e129e4a8de69d901483045022100940eed87a3f4c359ad350fd8bd725fbb43db42c4f265a5e6e7c345b4b3d71f5c022046675535c070d3987af143a0daa5bd72b2ebe5e0057679681c04408aa7f3c382014830450221008d7d05e56253db6f99ca927583dfe3e4c1f9e94b8cc78498ccabf6afdd4c0bb702200717ef5e641cc02cfb048d4afca47e4a345756bbcd4dfcd6487e63c301ccb5930147304402204a7e44a6ec8ea1d0f20fa863e281ccf07be788d885c5149075f6009716e5d1d702201db6b8962fd2eed845e3df7979e3decc5a4acbc283b24df657d2ca3800ffb45501483045022100daf90eedd898e0b4f0270a1f5da300b0342f9ff45ea0b0882372afd1bff95b1c02207153a03fb5d4bf6a13c09eebebf9638d44968b04890c92d552511c96f62e55b70147304402205ebb11f079571c992d4b86a376369df6cea08a3d288264f8f0b942a22b28a900022060c7f9727c160c552fb6111a14253e27e8952e66cb491ee72c6b4eab46c1620501483045022100976cef7c51c5ad7a7ad55e748a5b499e2a9b8df613be8bd8f1c69b1f2a6facb902207b2505ebe9a4f853ce87c2a28945463a89fc2e6ce7104991f335cc80a7b2438c01483045022100e8b95ad09ae443303b3d0f84f5db1bb16ddca64f77d98d8c06347baa15831e8e02203a73c5f8b21d8aa339f1f652708db025c857b1086fadf9483ff34e41b9b59a8b0147304402203c949c4ecea198aaaa426185d904e7fdc0a8dd00caadf25d5ad30974a408513202205d75f601e6bd8289b899c60737947152f4a821e26ebb5b145303f3b494a5e310014730440220726943bffb5b8ca2bf487842de3918e0a5268ec0d6e99045853cacdbcbdc6b5902204347a8da6e05a353ec324cb37aa028cc3fcd4dc5939e6eea334ea7dc60c2144601473044022061d39cb49dc2cc27a48cb02d93332de59cd3134df4b7c23e375a1f0d90ffa5fc0220419cb88f1c2e961c33382278a85d5bf669ecb049ffce0b4e7116513a6621ff7e01473044022062e3daf133888df12f71ffb1ff62a37171dd6d9e7a5582a4d7928a281a3d666002201d83128b63483a901aebaeba80296cf8b99cc96de8be74ddfbc0f597d7cdc17401fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680151a200000000"); + // this transaction was published in regtest and was accepted, + // its id is 98666cd0f7e5e2040e4ad08477fc6f95df21724f2b0331a685ddbadad631b21b +} + +BOOST_AUTO_TEST_CASE(user_sig_one_or_2_of_15_multisig_spend_test) { + const auto user_pkey = get_privkey_bytes("cPzX5utDDBt2kfK4uz43e78zMxZSdfiGrV9wnkmqrqPnxfQCB2Rc"); + const auto son1_pkey = get_privkey_bytes("cSKyTeXidmj93dgbMFqgzD7yvxzA7QAYr5j9qDnY9seyhyv7gH2m"); + const auto son2_pkey = get_privkey_bytes("cQBBNyEw6P3pgc2NjPpKR2YoCpio9s3qEMkFkY7v9hByLAxeLQ3s"); + const auto son3_pkey = get_privkey_bytes("cQLKc4UgKyCdXY3PosndszEZTsB6mTrg4avZF6kDphrULKd2W6L4"); + const auto son4_pkey = get_privkey_bytes("cN43k9sqQimgzChZm9Qz1V1bdkjVwB3mcSHsEuj6bfUa4SP2AsTk"); + + auto public_key1 = fc::ecc::public_key(create_public_key_data(parse_hex("03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"))); + auto public_key2 = fc::ecc::public_key(create_public_key_data(parse_hex("02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4"))); + auto public_key3 = fc::ecc::public_key(create_public_key_data(parse_hex("025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61"))); + auto public_key4 = fc::ecc::public_key(create_public_key_data(parse_hex("0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866"))); + auto public_key5 = fc::ecc::public_key(create_public_key_data(parse_hex("037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666"))); + auto public_key6 = fc::ecc::public_key(create_public_key_data(parse_hex("02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf"))); + auto public_key7 = fc::ecc::public_key(create_public_key_data(parse_hex("0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f"))); + auto public_key8 = fc::ecc::public_key(create_public_key_data(parse_hex("0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84"))); + auto public_key9 = fc::ecc::public_key(create_public_key_data(parse_hex("023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf"))); + auto public_key10 = fc::ecc::public_key(create_public_key_data(parse_hex("0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5"))); + auto public_key11 = fc::ecc::public_key(create_public_key_data(parse_hex("024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da"))); + auto public_key12 = fc::ecc::public_key(create_public_key_data(parse_hex("03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1"))); + auto public_key13 = fc::ecc::public_key(create_public_key_data(parse_hex("02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8"))); + auto public_key14 = fc::ecc::public_key(create_public_key_data(parse_hex("0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8"))); + auto public_key15 = fc::ecc::public_key(create_public_key_data(parse_hex("02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73"))); + + auto user_key = fc::ecc::public_key(create_public_key_data(parse_hex("0368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cd"))); + + btc_one_or_m_of_n_multisig_address addr(user_key, 2, {public_key1, public_key2, public_key3, public_key4, public_key5, public_key6, public_key7, public_key8, public_key9, public_key10, public_key11, public_key12, public_key13, public_key14, public_key15}); + + BOOST_CHECK(addr.get_address() == "bcrt1qp8vplzrn7alzpjq8cw4ynd6xqzassmefrh48dzsj0krvkq85dywq9txkqr"); + BOOST_CHECK(addr.get_redeem_script() == parse_hex("63210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae68")); + + bytes redeem_script = addr.get_redeem_script(); + //User spend + bitcoin_transaction tx1; + tx1.nVersion = 2; + tx1.vin.resize(1); + tx1.vout.resize(1); + tx1.nLockTime = 0; + + tx1.vin[0].prevout.hash = fc::sha256("3cdb7e835006695b4f99fbad3a43ac9ce2ea63b67af0c3bb4c2af6cb54554f90"); + tx1.vin[0].prevout.n = 0; + tx1.vin[0].nSequence = 0xffffffff; + + tx1.vout[0].value = 9000; + bitcoin_address to_address("bcrt1qettl5u3jlh6e8s6hxpqsdpevxqmq947f0jnnfraeg4jsvhsme2uqplzjgt"); + tx1.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + tx1.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx1, {redeem_script}, {amount}, user_pkey, btc_context(), hash_type)[0]); + bytes one = {0x01}; + tx1.vin[0].scriptWitness.push_back(one); + + sign_witness_transaction_finalize(tx1, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx1)) == "02000000000101904f5554cbf62a4cbbc3f07ab663eae29cac433aadfb994f5b690650837edb3c0000000000ffffffff012823000000000000220020cad7fa7232fdf593c357304106872c303602d7c97ca7348fb94565065e1bcab80347304402206f7d3a4689fa1a8107bf5b4ab3f363db147e6eaee49c455cacaee434ac9e4786022054fdea815431dae2b82ce3f9a2151fd127e7dd2685ae5655938f22b5c38046d1010101fd270263210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae6800000000"); + + //SON Spend + bitcoin_transaction tx2; + tx2.nVersion = 2; + tx2.vin.resize(1); + tx2.vout.resize(1); + tx2.nLockTime = 0; + + tx2.vin[0].prevout.hash = fc::sha256("00afbe7f3f31178c91c837ef160bef3d8108b80e368cbeda785e7a2f6e76f0a5"); + tx2.vin[0].prevout.n = 0; + tx2.vin[0].nSequence = 0xffffffff; + + tx2.vout[0].value = 9000; + tx2.vout[0].scriptPubKey = to_address.get_script(); + + tx2.vin[0].scriptWitness.push_back(bytes()); + tx2.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx2, {redeem_script}, {amount}, son2_pkey, btc_context(), hash_type)[0]); + tx2.vin[0].scriptWitness.push_back(sign_witness_transaction_part(tx2, {redeem_script}, {amount}, son4_pkey, btc_context(), hash_type)[0]); + tx2.vin[0].scriptWitness.push_back(bytes()); + + sign_witness_transaction_finalize(tx2, {redeem_script}, false); + + BOOST_CHECK(fc::to_hex(pack(tx2)) == "02000000000101a5f0766e2f7a5e78dabe8c360eb808813def0b16ef37c8918c17313f7fbeaf000000000000ffffffff012823000000000000220020cad7fa7232fdf593c357304106872c303602d7c97ca7348fb94565065e1bcab8050047304402204d6ed521e2dbe040f4139891b2dce21711757bcf5f4acf6dec9186157fe5bf3902206f0d1fa8e5c51c3f1c02568328ca92995136aee1a95fa1bc7739404b63f29c520147304402200999a6d7c657d729a6dceb1ed0e2cd441435e0d125dea99fbc117f14f267bf1e02202003c54c1cdf9d263c0a01a27fc53923c048a6230221765c3dc0e7fba4341d980100fd270263210368dc31b2b547c74f52abfc67c7fc768c68115d8ab96430d9bb4996fa660121cdac67522103456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef2752102d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f421025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61210228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe986621037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f6662102ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf210317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f210266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf8421023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf210229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e521024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da2103df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c12102bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8210287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae82102053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd735fae6800000000"); +} + +BOOST_AUTO_TEST_CASE(user_sig_one_or_weighted_multisig_spend_test) { + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + fc::sha256 h = fc::sha256::hash("user", 4); + fc::ecc::private_key user_key = fc::ecc::private_key::generate_from_seed(h); + fc::ecc::public_key user_pub_key = user_key.get_public_key(); + + btc_one_or_weighted_multisig_address addr(user_pub_key, weights); + BOOST_CHECK(addr.get_address() == "bcrt1q6e323wwh3nw5875kztgna6z7ygvec2mvt48e630gmlsdh3t8ed8s63qmm5"); + + bytes redeem_script = addr.get_redeem_script(); + + { + // this address was filled with regtest transaction + // id 11f5a6d6917c49eb46dd1e19bf1d76c09b58acc35cd92f6911695df44bddf4a0 + // output 0, 10000 satoshis + + // now send it to 2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1 + // with single user signature + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("11f5a6d6917c49eb46dd1e19bf1d76c09b58acc35cd92f6911695df44bddf4a0"); + tx.vin[0].prevout.n = 1; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + bitcoin_address to_address("2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + bytes key_data(user_key.get_secret().data(), user_key.get_secret().data() + user_key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + tx.vin[0].scriptWitness.push_back(sigs[0]); + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted, + // its id is 9969642bf24f3d25f025f91dbf87dd5e5cf8508597aedd56af6ef1f97658f9d9 + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000000101a0f4dd4bf45d6911692fd95cc3ac589bc0761dbf191edd46eb497c91d6a6f5110100000000ffffffff01282300000000000017a9140b552f4a72cb614717878b20743d9e38e618130a87024730440220083ae11a94bc0c0a5783fa1d81e470cf906fbd5a8b3b8cadf89d78ba4b721e7a022045b5469576dc075f27f3ea510e07cf5037af3b462a2111d0f1d6e6da7d06728501fd83022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac635167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680151a26800000000"); + } + + { + // this address was filled again with regtest transaction + // id 57df37df31895807d6a5255e24691c58c028d6f60b11b3f5504fbdd22df90872 + // output 0, 10000 satoshis + + // now send it to the primary wallet with sons signatures + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("57df37df31895807d6a5255e24691c58c028d6f60b11b3f5504fbdd22df90872"); + tx.vin[0].prevout.n = 0; + tx.vin[0].nSequence = 0xffffffff; + + tx.vout[0].value = 9000; + btc_weighted_multisig_address to_address(weights); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + for (auto &key : priv_keys) { + bytes key_data(key.get_secret().data(), key.get_secret().data() + key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + // insert signatures in reverse order + tx.vin[0].scriptWitness.insert(tx.vin[0].scriptWitness.begin(), sigs[0]); + } + // add empty sig for user signature + tx.vin[0].scriptWitness.push_back(bytes()); + + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted, + // its id is d1c1f75a276fed52a230f57120fafb5af10fb5d9b666d37e78722e1b811fd6dd + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000001017208f92dd2bd4f50f5b3110bf6d628c0581c69245e25a5d607588931df37df570000000000ffffffff012823000000000000220020fd2b172e2f825a9cba582cdabf5ba178c5ac3b49cd63970192cb2511fc59e17211483045022100b2401c5081bea13c87678ee1a11b09a937be7cab4ac0f295eded00f3d9920fbc022010d84be64051ec09f5d0682f59539e23df77dc5ada73a0ba0ac4e1ae32d7d0620147304402203196944571e652275eb5b055ea48de8d62a8321d6ba5c0c52f01bfd3d187dc7802207062762016d8b26946823b30470f720919f8312024bda8799984d55a784ef20301483045022100c2a183ca75915b4d2e8dc1bb94a2be536f1c8b26effed9360ca5913128b7dee102203fa40e103cb6e711acd84b1b94eb46279ad52a6affc95bc78359bb5ceb706126014830450221009e08aa12474c67c07dc90d94ce1181b1b9f347a13142c7bb9eba30b45b89b52c022031a4313250b01e4fb3edc63cb7cb3a5b0fd3362f8624ec9e7c63722fc6245b8c0147304402207e044ffed8c5d9a2458c728a0b0381571db4cadf83c0cc7d07930836ed712d5e0220786c41038b605f0fb7926395f869f756ff0018c73f934e73f9a3162903c0f2c201483045022100cd48b237d227af046b721e323a21cefbb47cbe6956893b0b3294e8d57cfc49c502202197209d0ed63998ad9fe5e4b448f472e7a0009c0d764b6855fd3bfcb65d258d0147304402205319cae42865486e4e4cfe040129f5057cc52768a8c11310e226171b7655b8d402203f521314eeb716aa5a1911e3663a27844ecf747fddbcfb201217abbeb236693a01483045022100f35e680293c1e3c97cd7a8b24f05c4f45862b8d93485100a7b934668467cc2bb02203827853d1efb1ae98266a622216fccd72acd5bbc1681ccd9e5c4e29c7daf3bc601483045022100bae57bb8eee66938c20b231bd71b84f5e09b582c005015f8ccd163c9afb6b08d02204deaf19b266ae3548b75235ab5c36c3334beb6fd93b936ef3eca7b0d39f9e03a01473044022044848ec4188a00695a45c4ae3e977654f8a819e9931501ae36b9c500a7e72a5e02207e0b27b855fe72a46a6d75c6286d9b47068bfc29fe54f86f5bf2761db7ecaf7701483045022100edd042c70e727d3956d4e08a1f9ae789f43ce44f5d90b5d19f32a78c20db57140220337aafb7441bcc29ad20a9d813db0cf3d9528102afa5191654feedd1d3b3fd2d0147304402205fa41757a0d1b25c8acf413700e90268d7f17d4ae296388d619bfbdc863154b70220529d33e4b6a34e8de2b821257097bbe16b5d0040c8ae4d443c61daa7d80f2c86014730440220623f7039e9e9bd652e3370a125b82919092d9eb5144b2835a208d59de3887c3d022041d274f9dabe3fb00db6deef0edf2e0671dc49f1fe7b4bcc6c589f697c02a44a01483045022100847dc557a81e06efe330c7a50d40e3aa7a4c97ea64dc93d809571aab5258ab6002203da85fc76c01d4a50a44e9de4c21e53a88444df573f2f000e9b3dddc7063bbf501473044022052bebca16b1629c121c23ce14e6d8f0f12603f96d54f07c33f4ccdbad57887a502206df9cb44417f513c97cf03bd8cd7a05d3b19294880158ef695c1ff5269f59bb30100fd83022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac635167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680151a26800000000"); + } +} + +BOOST_AUTO_TEST_CASE(user_sig_timelocked_one_or_weighted_multisig_spend_test) { + std::vector priv_keys; + for (uint32_t i = 0; i < 15; ++i) { + const char *seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_keys.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_keys; + for (auto &key : priv_keys) { + pub_keys.push_back(key.get_public_key()); + } + // key weights + std::vector> weights; + for (uint16_t i = 0; i < 15; ++i) + weights.push_back(std::make_pair(pub_keys[i], i + 1)); + + fc::sha256 h = fc::sha256::hash("user", 4); + fc::ecc::private_key user_key = fc::ecc::private_key::generate_from_seed(h); + fc::ecc::public_key user_pub_key = user_key.get_public_key(); + + uint32_t latency = 1; + btc_timelocked_one_or_weighted_multisig_address addr(user_pub_key, latency, weights); + BOOST_CHECK(addr.get_address() == "bcrt1qk9ef5eh4lsrc6uenchd4fpfvgd8avulnf7daqxhrt7d8arxyp8kqsnzyc4"); + + bytes redeem_script = addr.get_redeem_script(); + + { + // this address was filled with regtest transaction + // id e6f6bc21f4034ad222820162717574e7d2488b34412dccecec19e2bbd3889dee + // output 1, 10000 satoshis + + // now send it to 2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1 + // with single user signature + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin.resize(1); + tx.vout.resize(1); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256("e6f6bc21f4034ad222820162717574e7d2488b34412dccecec19e2bbd3889dee"); + tx.vin[0].prevout.n = 1; + tx.vin[0].nSequence = 0x1; + + tx.vout[0].value = 9000; + bitcoin_address to_address("2MtH9U8fEZbRmco3GYVMjSg9NfUyPn5RDN1"); + tx.vout[0].scriptPubKey = to_address.get_script(); + + uint64_t amount = 10000; + int32_t hash_type = 1; // implement SIGHASH_ALL scheme + + bytes key_data(user_key.get_secret().data(), user_key.get_secret().data() + user_key.get_secret().data_size()); + std::vector sigs = sign_witness_transaction_part(tx, {redeem_script}, {amount}, key_data, btc_context(), hash_type); + tx.vin[0].scriptWitness.push_back(sigs[0]); + sign_witness_transaction_finalize(tx, {redeem_script}, false); + + // this transaction was published in regtest and was accepted only 1 block later then source tx + // its id is 32831d5c6c979166c4fdc7871e28d67858c9f66fbb0fdb8284eb16e42554d6c9 + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000000101ee9d88d3bbe219ececcc2d41348b48d2e774757162018222d24a03f421bcf6e601000000000100000001282300000000000017a9140b552f4a72cb614717878b20743d9e38e618130a8702483045022100d3a5aa67227cd67f2a2f851b3762343ae407cba2e0e2fa8e6abdf3a6143681fd022033ba7e4cdcc2185c4b80021d7c0f90aa839c08c1477f54d4cfd5208ff4ea222801fd86022102d2c1cb1575d323b6120b6e5bcc9ce5ad373e88e73e675030f1c2c5261b4dbc86ac6351b2755167007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680151a26800000000"); + } + +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp new file mode 100644 index 00000000..20fe878c --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_transaction_tests.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain::bitcoin; + +BOOST_AUTO_TEST_SUITE(bitcoin_transaction_tests) + +BOOST_AUTO_TEST_CASE(serialize_bitcoin_transaction_test) { + out_point prevout; + prevout.hash = fc::sha256("89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a"); + prevout.n = 0; + + tx_in in; + in.prevout = prevout; + in.scriptSig = parse_hex("473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101"); + in.nSequence = 4294967294; + + tx_out out1; + out1.value = 3500000000; + out1.scriptPubKey = parse_hex("76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac"); + + tx_out out2; + out2.value = 1499996160; + out2.scriptPubKey = parse_hex("76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac"); + + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin = {in}; + tx.vout = {out1, out2}; + tx.nLockTime = 101; + + const auto serialized = pack(tx); + + const auto expected = parse_hex("02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000"); + + BOOST_CHECK_EQUAL_COLLECTIONS(serialized.cbegin(), serialized.cend(), expected.cbegin(), expected.cend()); +} + +BOOST_AUTO_TEST_CASE(deserialize_bitcoin_transaction_test) { + bitcoin_transaction tx = unpack(parse_hex("02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000")); + + BOOST_CHECK_EQUAL(tx.nVersion, 2); + BOOST_CHECK_EQUAL(tx.nLockTime, 101); + + BOOST_REQUIRE_EQUAL(tx.vin.size(), 1); + BOOST_CHECK_EQUAL(tx.vin[0].prevout.hash.str(), "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a"); + BOOST_CHECK_EQUAL(tx.vin[0].prevout.n, 0); + + BOOST_CHECK(fc::to_hex(tx.vin[0].scriptSig) == "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101"); + BOOST_CHECK_EQUAL(tx.vin[0].nSequence, 4294967294); + + BOOST_REQUIRE_EQUAL(tx.vout.size(), 2); + BOOST_CHECK_EQUAL(tx.vout[0].value, 3500000000); + BOOST_CHECK(fc::to_hex(tx.vout[0].scriptPubKey) == "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac"); + BOOST_CHECK_EQUAL(tx.vout[1].value, 1499996160); + BOOST_CHECK(fc::to_hex(tx.vout[1].scriptPubKey) == "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac"); +} + +BOOST_AUTO_TEST_CASE(btc_tx_methods_test) { + const auto tx = unpack(parse_hex("0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f856040000002322002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1ffffffff0188b3f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02473044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc012321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac00000000")); + + BOOST_CHECK(tx.get_hash().str() == "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1"); + BOOST_CHECK(tx.get_txid().str() == "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88"); + BOOST_CHECK(tx.get_vsize() == 148); +} + +BOOST_AUTO_TEST_CASE(bitcoin_transaction_builder_test) { + // All tests are only to verefy the compilation of transactions, the transactions are not real(not valid) + { // P2PKH to P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PKH, fc::sha256("5d42b45d5a3ddcf2421b208885871121551acf6ea5cc1c1b4e666537ab6fcbef"), 0, bytes()); + tb.add_out(payment_type::P2PKH, 4999990000, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000001efcb6fab3765664e1b1ccca56ecf1a552111878588201b42f2dc3d5a5db4425d0000000000ffffffff01f0ca052a010000001976a9143307bf6f98832e53a48b144d65c6a95700a93ffb88ac00000000"); + } /* + { // coinbase to P2PK + const auto pubkey = fc::raw::unpack( parse_hex( "02028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00" ) ); + out_point prevout; + prevout.hash = fc::sha256( "0000000000000000000000000000000000000000000000000000000000000000" ); + prevout.n = 0xffffffff; + + tx_in txin; + txin.prevout = prevout; + + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, txin, parse_hex( "022a020101" ) ); + tb.add_out( payment_type::P2PK, 625000000, pubkey); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "21030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022a020101ffffffff0240be402500000000232102028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00ac0000000000000000256a2321030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac00000000" ); + }*/ + { // P2SH to P2SH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2SH, fc::sha256("40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8"), 0, bytes()); + tb.add_out(payment_type::P2SH, 0xffffffff, "3P14159f73E4gFr7JterCCQh9QjiTjiZrG"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000001c8cc2b56525e734ff63a13bc6ad06a9e5664df8c67632253a8e36017aee3ee400000000000ffffffff01ffffffff0000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a8700000000"); + } + { // P2PK to NULLDATA + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PK, fc::sha256("fa897a4a2b8bc507db6cf4425e81ca7ebde89a369e07d608ac7f7c311cb13b4f"), 0, bytes()); + tb.add_out(payment_type::NULLDATA, 0, parse_hex("ffffffff")); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000014f3bb11c317c7fac08d6079e369ae8bd7eca815e42f46cdb07c58b2b4a7a89fa0000000000ffffffff010000000000000000066a04ffffffff00000000"); + } + { // P2PK+P2PKH to P2PKH,P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2PK, fc::sha256("13d29149e08b6ca63263f3dddd303b32f5aab646ebc6b7db84756d80a227f6d9"), 0, bytes()); + tb.add_in(payment_type::P2PKH, fc::sha256("a8e7f661925cdd2c0e37fc93c03540c113aa6bcea02b35de09377127f76d0da3"), 0, bytes()); + tb.add_out(payment_type::P2PKH, 4999990000, "mzg9RZ1p29uNXu4uTWoMdMERdVXZpunJhW"); + tb.add_out(payment_type::P2PKH, 4999990000, "n2SPW6abRxUnnTSSHp73VGahbPW4WT9GaK"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "0200000002d9f627a2806d7584dbb7c6eb46b6aaf5323b30ddddf36332a66c8be04991d2130000000000ffffffffa30d6df727713709de352ba0ce6baa13c14035c093fc370e2cdd5c9261f6e7a80000000000ffffffff02f0ca052a010000001976a914d2276c7ed7af07f697175cc2cbcbbf32da81caba88acf0ca052a010000001976a914e57d9a9af070998bedce991c4d8e39f9c51eb93a88ac00000000"); + } + { // P2WPKH to P2WPKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2WPKH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 1, bytes()); + tb.add_out(payment_type::P2WPKH, 99988480, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001600143307bf6f98832e53a48b144d65c6a95700a93ffb00000000"); + } + { // P2WSH to P2WSH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2WSH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 2, bytes()); + tb.add_out(payment_type::P2WSH, 99988360, "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560200000000ffffffff0188b3f505000000001600140000010966776006953d5567439e5e39f86a0d2700000000"); + } + { // P2SH(WPKH) to P2SH(WPKH) + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in(payment_type::P2SH_WPKH, fc::sha256("56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 3, parse_hex("ab68025513c3dbd2f7b92a94e0581f5d50f654e7")); + tb.add_out(payment_type::P2SH_WPKH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560300000014ab68025513c3dbd2f7b92a94e0581f5d50f654e7ffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000"); + } + { // P2SH(WSH) to P2SH(WSH) + bitcoin_transaction_builder tb; + tb.set_version(2); + const auto redeem_script = parse_hex("21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac"); + tb.add_in(payment_type::P2SH_WSH, fc::sha256("fca01bd539623013f6f945dc6173c395394621ffaa53a9eb6da6e9a2e7c9400e"), 0, redeem_script); + tb.add_out(payment_type::P2SH_WSH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd"); + + const auto tx = tb.get_transaction(); + BOOST_CHECK(fc::to_hex(pack(tx)) == "02000000010e40c9e7a2e9a66deba953aaff21463995c37361dc45f9f613306239d51ba0fc000000002321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acacffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp b/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp new file mode 100644 index 00000000..84577384 --- /dev/null +++ b/tests/peerplays_sidechain/peerplays_sidechain_tests.cpp @@ -0,0 +1,17 @@ +#include + +#define BOOST_TEST_MODULE Peerplays SON Tests + +BOOST_AUTO_TEST_CASE(peerplays_sidechain) { +} + +#include +#include +#include + +boost::unit_test::test_suite *init_unit_test_suite(int argc, char *argv[]) { + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; + + return nullptr; +} diff --git a/tests/tests/account_role_tests.cpp b/tests/tests/account_role_tests.cpp new file mode 100644 index 00000000..73824678 --- /dev/null +++ b/tests/tests/account_role_tests.cpp @@ -0,0 +1,379 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(account_role_tests, database_fixture) + +BOOST_AUTO_TEST_CASE(account_role_create_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_create_test"); + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + generate_block(); + set_expiration(db, trx); + ACTORS((resourceowner)(alice)(bob)(charlie)); + upgrade_to_lifetime_member(resourceowner); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + upgrade_to_lifetime_member(charlie); + transfer(committee_account, resourceowner_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, alice_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, charlie_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + { + BOOST_TEST_MESSAGE("Send account_role_create_operation"); + + account_role_create_operation op; + op.owner = resourceowner_id; + op.name = "Test Account Role"; + op.metadata = "{\"country\": \"earth\", \"race\": \"human\" }"; + + int ops[] = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + op.allowed_operations.insert(ops, ops + 6); + op.whitelisted_accounts.emplace(alice_id); + op.whitelisted_accounts.emplace(bob_id); + op.valid_to = db.head_block_time() + 1000; + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.begin(); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->owner == resourceowner_id); + BOOST_CHECK(obj->name == "Test Account Role"); + BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\" }"); + flat_set expected_allowed_operations = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + BOOST_CHECK(obj->allowed_operations == expected_allowed_operations); + flat_set expected_whitelisted_accounts = {alice_id, bob_id}; + BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts); + BOOST_CHECK(obj->valid_to == db.head_block_time() + 1000); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_role_update_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_create_test"); + INVOKE(account_role_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(resourceowner); + set_expiration(db, trx); + { + BOOST_TEST_MESSAGE("Send account_role_update_operation"); + + account_role_update_operation op; + op.owner = resourceowner_id; + op.account_role_id = account_role_id_type(0); + op.name = "Test Account Role Update"; + op.metadata = "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }"; + + int ops_add[] = {operation::tag::value}; + int ops_delete[] = {operation::tag::value, operation::tag::value}; + op.allowed_operations_to_add.insert(ops_add, ops_add + 1); + op.allowed_operations_to_remove.insert(ops_delete, ops_delete + 2); + op.valid_to = db.head_block_time() + 10000; + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.begin(); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->owner == resourceowner_id); + BOOST_CHECK(obj->name == "Test Account Role Update"); + BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }"); + flat_set expected_allowed_operations = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + BOOST_CHECK(obj->allowed_operations == expected_allowed_operations); + flat_set expected_whitelisted_accounts = {alice_id, bob_id}; + BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts); + BOOST_CHECK(obj->valid_to == db.head_block_time() + 10000); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_role_delete_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_delete_test"); + INVOKE(account_role_create_test); + GET_ACTOR(resourceowner); + set_expiration(db, trx); + { + BOOST_TEST_MESSAGE("Send account_role_delete_operation"); + + account_role_delete_operation op; + op.owner = resourceowner_id; + op.account_role_id = account_role_id_type(0); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(nft_metadata_create_mint_with_account_role_test) +{ + try + { + BOOST_TEST_MESSAGE("nft_metadata_create_mint_with_account_role_test"); + INVOKE(account_role_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(resourceowner); + + { + BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); + + nft_metadata_create_operation op; + op.owner = resourceowner_id; + op.name = "NFT Test"; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.revenue_partner = resourceowner_id; + op.revenue_split = 1000; + op.is_transferable = true; + op.is_sellable = true; + op.account_role = account_role_id_type(0); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + + const auto &nftmd_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nftmd_idx.size() == 1); + auto nftmd_obj = nftmd_idx.begin(); + BOOST_REQUIRE(nftmd_obj != nftmd_idx.end()); + BOOST_CHECK(nftmd_obj->owner == resourceowner_id); + BOOST_CHECK(nftmd_obj->name == "NFT Test"); + BOOST_CHECK(nftmd_obj->symbol == "NFT"); + BOOST_CHECK(nftmd_obj->base_uri == "http://nft.example.com"); + BOOST_CHECK(nftmd_obj->account_role == account_role_id_type(0)); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation"); + + nft_mint_operation op; + op.payer = resourceowner_id; + op.nft_metadata_id = nftmd_obj->id; + op.owner = alice_id; + op.approved = alice_id; + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + + BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + + const auto &nft_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nft_idx.size() == 1); + auto nft_obj = nft_idx.begin(); + BOOST_REQUIRE(nft_obj != nft_idx.end()); + BOOST_CHECK(nft_obj->owner == alice_id); + BOOST_CHECK(nft_obj->approved == alice_id); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(nft_safe_transfer_account_role_test) +{ + try + { + BOOST_TEST_MESSAGE("nft_safe_transfer_account_role_test"); + INVOKE(nft_metadata_create_mint_with_account_role_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + + { + BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation"); + + nft_safe_transfer_from_operation op; + op.operator_ = alice_id; + op.from = alice_id; + op.to = bob_id; + op.token_id = nft_id_type(0); + op.data = "data"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_safe_transfer_from_operation results"); + + const auto &nft_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nft_idx.size() == 1); + auto nft_obj = nft_idx.begin(); + BOOST_REQUIRE(nft_obj != nft_idx.end()); + BOOST_CHECK(nft_obj->owner == bob_id); + + { + BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation"); + + nft_safe_transfer_from_operation op; + op.operator_ = bob_id; + op.from = bob_id; + op.to = charlie_id; + op.token_id = nft_id_type(0); + op.data = "data"; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + // Charlie is not whitelisted by resource creator + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(nft_offer_bid_account_role_test) +{ + try + { + BOOST_TEST_MESSAGE("nft_offer_bid_account_role_test"); + INVOKE(nft_metadata_create_mint_with_account_role_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + + { + BOOST_TEST_MESSAGE("Send create_offer"); + + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.issuer = alice_id; + offer_op.buying_item = false; + offer_op.maximum_price = asset(10000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + + trx.operations.push_back(offer_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + // Charlie tries to bid but fails. + { + BOOST_TEST_MESSAGE("Send create_bid by charlie"); + bid_operation bid_op; + bid_op.offer_id = offer_id_type(0); + // Buy it now price + bid_op.bid_price = asset(10000); + bid_op.bidder = charlie_id; + trx.operations.push_back(bid_op); + sign(trx, charlie_private_key); + // Charlie is not whitelisting to perform bid_operation by resource/metadata owner + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + } + // Bob succeeds in bidding. + { + BOOST_TEST_MESSAGE("Send create_bid by bob"); + bid_operation bid_op; + bid_op.offer_id = offer_id_type(0); + // Buy it now price + bid_op.bid_price = asset(10000); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + // Bob is whitelisted in account role created by resource/metadata owner + PUSH_TX(db, trx); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check offer results"); + + const auto &nft_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nft_idx.size() == 1); + auto nft_obj = nft_idx.begin(); + BOOST_REQUIRE(nft_obj != nft_idx.end()); + BOOST_CHECK(nft_obj->owner == bob_id); + // Charlie tries to bid (buy offer) but fails + { + BOOST_TEST_MESSAGE("Send create_offer"); + + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.issuer = charlie_id; + offer_op.buying_item = true; + offer_op.maximum_price = asset(10000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + + trx.operations.push_back(offer_op); + sign(trx, charlie_private_key); + // Charlie is not whitelisting to perform offer_operation by resource/metadata owner + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 72482a0a..804d9e8a 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -289,7 +289,7 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) { ACTORS( (irene) ); - const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; + const asset_id_type btc_id = create_user_issued_asset( "BTCTEST", irene, 0 ).id; issue_uia( irene, asset( 100000, btc_id ) ); affiliate_test_helper ath( *this ); @@ -517,7 +517,7 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) { try { ACTORS( (irene) ); - const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; + const asset_id_type btc_id = create_user_issued_asset( "BTCTEST", irene, 0 ).id; affiliate_test_helper ath( *this ); @@ -616,7 +616,7 @@ BOOST_AUTO_TEST_CASE( statistics_test ) INVOKE(bookie_payout_test); - const asset_id_type btc_id = get_asset( "BTC" ).id; + const asset_id_type btc_id = get_asset( "BTCTEST" ).id; transfer( ath.alice_id, ath.ann_id, asset( 100, btc_id ), asset(0) ); transfer( ath.alice_id, ath.audrey_id, asset( 100, btc_id ), asset(0) ); diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index a6169489..387036df 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -320,7 +320,7 @@ BOOST_AUTO_TEST_CASE( proposed_single_account ) { vector other; flat_set active_set, owner_set; - operation_get_required_authorities(op,active_set,owner_set,other); + operation_get_required_authorities(op, active_set, owner_set, other, false); BOOST_CHECK_EQUAL(active_set.size(), 1lu); BOOST_CHECK_EQUAL(owner_set.size(), 0lu); BOOST_CHECK_EQUAL(other.size(), 0lu); @@ -328,7 +328,7 @@ BOOST_AUTO_TEST_CASE( proposed_single_account ) active_set.clear(); other.clear(); - operation_get_required_authorities(op.proposed_ops.front().op,active_set,owner_set,other); + operation_get_required_authorities(op.proposed_ops.front().op, active_set, owner_set, other, false); BOOST_CHECK_EQUAL(active_set.size(), 1lu); BOOST_CHECK_EQUAL(owner_set.size(), 0lu); BOOST_CHECK_EQUAL(other.size(), 0lu); @@ -1055,7 +1055,7 @@ BOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture ) flat_set active_set, owner_set; vector others; - trx.get_required_authorities( active_set, owner_set, others ); + trx.get_required_authorities(active_set, owner_set, others, false); PUSH_TX( db, trx, skip ); @@ -1204,9 +1204,12 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, false); + set result_set2 = tx.get_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, true); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( well_id, authority( 60, alice_id, 50, bob_id, 50 ) ); @@ -1326,9 +1329,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, false); + set result_set2 = tx.get_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, true); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; auto chk_min = [&]( @@ -1338,9 +1344,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.minimize_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, false); + set result_set2 = tx.minimize_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, get_custom, true); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( roco_id, authority( 2, styx_id, 1, thud_id, 2 ) ); @@ -1357,9 +1366,13 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom, false ), + fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom, true ), + fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom, true ); } catch(fc::exception& e) { diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index b7ed69fe..9885b548 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -733,7 +733,7 @@ BOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture ) fc::time_point_sec maintenence_time = db.get_dynamic_global_properties().next_maintenance_time; BOOST_CHECK_GT(maintenence_time.sec_since_epoch(), db.head_block_time().sec_since_epoch()); auto initial_properties = db.get_global_properties(); - const account_object& nathan = create_account("nathan"); + auto nathan = create_account("nathan"); upgrade_to_lifetime_member(nathan); const committee_member_object nathans_committee_member = create_committee_member(nathan); { @@ -747,6 +747,7 @@ BOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture ) } generate_block(); + nathan = get_account("nathan"); transfer(account_id_type()(db), nathan, asset(5000)); generate_blocks(maintenence_time - initial_properties.parameters.block_interval); diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp index 943b8265..8b8a1a51 100644 --- a/tests/tests/history_api_tests.cpp +++ b/tests/tests/history_api_tests.cpp @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(get_account_history_additional) { const account_object& dan = create_account("dan"); // create op 1 create_bitasset("CNY", dan.id); // create op 2 - create_bitasset("BTC", account_id_type()); // create op 3 + create_bitasset("BTCTEST", account_id_type()); // create op 3 create_bitasset("XMR", dan.id); // create op 4 create_bitasset("EUR", account_id_type()); // create op 5 create_bitasset("OIL", dan.id); // create op 6 @@ -454,7 +454,7 @@ BOOST_AUTO_TEST_CASE(track_account) { BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); // create more ops, starting with an untracked account - create_bitasset( "BTC", account_id_type() ); + create_bitasset( "BTCTEST", account_id_type() ); create_bitasset( "GBP", dan_id ); generate_block( ~database::skip_fork_db ); @@ -468,7 +468,7 @@ BOOST_AUTO_TEST_CASE(track_account) { db.pop_block(); // Try again, should result in same object IDs - create_bitasset( "BTC", account_id_type() ); + create_bitasset( "BTCTEST", account_id_type() ); create_bitasset( "GBP", dan_id ); generate_block(); diff --git a/tests/tests/sidechain_addresses_test.cpp b/tests/tests/sidechain_addresses_test.cpp new file mode 100644 index 00000000..755ad37f --- /dev/null +++ b/tests/tests/sidechain_addresses_test.cpp @@ -0,0 +1,170 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( sidechain_addresses_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( sidechain_address_add_test ) { + + BOOST_TEST_MESSAGE("sidechain_address_add_test"); + + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send sidechain_address_add_operation"); + + sidechain_address_add_operation op; + op.payer = alice_id; + op.sidechain_address_account = alice_id; + op.sidechain = sidechain_type::bitcoin; + op.deposit_public_key = "deposit_public_key"; + op.withdraw_public_key = "withdraw_public_key"; + op.withdraw_address = "withdraw_address"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check sidechain_address_add_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->sidechain_address_account == alice_id ); + BOOST_CHECK( obj->sidechain == sidechain_type::bitcoin ); + BOOST_CHECK( obj->deposit_public_key == "deposit_public_key" ); + BOOST_CHECK( obj->deposit_address == "" ); + BOOST_CHECK( obj->deposit_address_data == "" ); + BOOST_CHECK( obj->withdraw_public_key == "withdraw_public_key" ); + BOOST_CHECK( obj->withdraw_address == "withdraw_address" ); +} + +BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { + BOOST_TEST_MESSAGE("sidechain_address_update_test"); + generate_blocks(HARDFORK_SON_TIME); + generate_block(); + + INVOKE(sidechain_address_add_test); + + GET_ACTOR(alice); + ACTORS((bob)); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); + BOOST_REQUIRE( obj != idx.end() ); + std::string new_deposit_public_key = "deposit_public_key"; + std::string new_deposit_address = "new_deposit_address"; + std::string new_deposit_address_data = "new_deposit_address_data"; + std::string new_withdraw_public_key = "withdraw_public_key"; + std::string new_withdraw_address = "withdraw_address"; + + generate_block(); + auto& son = db.create( [&]( son_object& sobj ) + { + sobj.son_account = bob_id; + sobj.statistics = db.create([&](son_statistics_object& s){s.owner = sobj.id;}).id; + }); + generate_block(); + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + son_info sinfo; + sinfo.son_id = son.id; + _gpo.active_sons.push_back(sinfo); + }); + generate_block(); + { + BOOST_TEST_MESSAGE("Send sidechain_address_update_operation"); + trx.clear(); + sidechain_address_update_operation op; + op.payer = bob_id; + op.sidechain_address_id = sidechain_address_id_type(0); + op.sidechain_address_account = obj->sidechain_address_account; + op.sidechain = obj->sidechain; + op.deposit_public_key = new_deposit_public_key; + op.deposit_address = new_deposit_address; + op.deposit_address_data = new_deposit_address_data; + op.withdraw_public_key = new_withdraw_public_key; + op.withdraw_address = new_withdraw_address; + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + { + BOOST_TEST_MESSAGE("Check sidechain_address_update_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->sidechain_address_account == obj->sidechain_address_account ); + BOOST_CHECK( obj->sidechain == obj->sidechain ); + BOOST_CHECK( obj->deposit_public_key == new_deposit_public_key ); + BOOST_CHECK( obj->deposit_address == new_deposit_address ); + BOOST_CHECK( obj->deposit_address_data == new_deposit_address_data ); + BOOST_CHECK( obj->withdraw_public_key == new_withdraw_public_key ); + BOOST_CHECK( obj->withdraw_address == new_withdraw_address ); + } +} + +BOOST_AUTO_TEST_CASE( sidechain_address_delete_test ) { + + BOOST_TEST_MESSAGE("sidechain_address_delete_test"); + + INVOKE(sidechain_address_add_test); + + GET_ACTOR(alice); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); + BOOST_REQUIRE( obj != idx.end() ); + + { + BOOST_TEST_MESSAGE("Send sidechain_address_delete_operation"); + + sidechain_address_delete_operation op; + op.payer = alice_id; + op.sidechain_address_id = sidechain_address_id_type(0); + op.sidechain_address_account = alice_id; + op.sidechain = obj->sidechain; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + time_point_sec now = db.head_block_time(); + generate_block(); + + { + BOOST_TEST_MESSAGE("Check sidechain_address_delete_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); + BOOST_REQUIRE( obj == idx.end() ); + auto expired_obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, now ) ); + BOOST_REQUIRE( expired_obj != idx.end() ); + } +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp new file mode 100644 index 00000000..8029c58a --- /dev/null +++ b/tests/tests/son_operations_tests.cpp @@ -0,0 +1,788 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( create_son_test ) { + generate_blocks(HARDFORK_SON_TIME); + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)(bob)); + + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + + transfer( committee_account, alice_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + transfer( committee_account, bob_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + + set_expiration(db, trx); + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(10*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.operations.push_back(op); + + // amount in the son balance need to be at least 50 + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::exception ); + + op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); + trx.clear(); + + trx.operations.push_back(op); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + deposit = ptx.operation_results[0].get(); + + auto deposit_vesting = db.get(ptx.operation_results[0].get()); + + BOOST_CHECK_EQUAL(deposit(db).balance.amount.value, 50*GRAPHENE_BLOCKCHAIN_PRECISION); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit(db).is_withdraw_allowed(now, asset(50*GRAPHENE_BLOCKCHAIN_PRECISION)), false); // cant withdraw + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment ; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + + op.validate(); + + trx.operations.push_back(op); + trx.validate(); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment = ptx.operation_results[0].get(); + } + + generate_block(); + set_expiration(db, trx); + + // alice became son + { + flat_map sidechain_public_keys; + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url; + op.deposit = deposit; + op.pay_vb = payment; + op.signing_key = alice_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->url == test_url ); + BOOST_CHECK( obj->signing_key == alice_public_key ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "bitcoin address" ); + BOOST_CHECK( obj->deposit.instance == deposit.instance.value ); + BOOST_CHECK( obj->pay_vb.instance == payment.instance.value ); +} + +BOOST_AUTO_TEST_CASE( update_son_test ) { + + INVOKE(create_son_test); + GET_ACTOR(alice); + + std::string new_url = "https://anewurl.com"; + + { + flat_map sidechain_public_keys; + sidechain_public_keys[sidechain_type::bitcoin] = "new bitcoin address"; + + son_update_operation op; + op.son_id = son_id_type(0); + op.owner_account = alice_id; + op.new_url = new_url; + op.new_sidechain_public_keys = sidechain_public_keys; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->url == new_url ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "new bitcoin address" ); +} + +BOOST_AUTO_TEST_CASE( deregister_son_test ) { +try { + INVOKE(create_son_test); + GET_ACTOR(alice); + + auto deposit_vesting = db.get(vesting_balance_id_type(0)); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // cant withdraw + + { + son_deregister_operation op; + op.son_id = son_id_type(0); + op.payer = alice_id; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1); +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + +BOOST_AUTO_TEST_CASE( deregister_son_test_with_consensus_account ) { +try { + INVOKE(create_son_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + // Modify SON's status to active + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + db.modify( *son_stats_obj, [&]( son_statistics_object& _s) + { + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + }); + + auto deposit_vesting = db.get(vesting_balance_id_type(0)); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // cant withdraw + + { + trx.clear(); + son_deregister_operation op; + op.son_id = son_id_type(0); + op.payer = db.get_global_properties().parameters.son_account(); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_REQUIRE( idx.size() == 1 ); + BOOST_REQUIRE( obj->status == son_status::deregistered ); + BOOST_REQUIRE( son_stats_obj->deregistered_timestamp == now ); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + BOOST_CHECK_EQUAL(deposit_vesting.policy.get().vesting_cliff_seconds, + db.get_global_properties().parameters.son_vesting_period()); // in linear policy + + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // but still cant withdraw + + generate_blocks(now + fc::seconds(db.get_global_properties().parameters.son_vesting_period())); + generate_block(); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), true); // after 2 days withdraw is allowed +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + +BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner +try { + + INVOKE(create_son_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + // bob tries to update a son object he dont own + { + son_update_operation op; + op.owner_account = bob_id; + op.new_url = "whatever"; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception); + } + generate_block(); + + set_expiration(db, trx); + trx.clear(); + + const auto& idx = db.get_index_type().indices().get(); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + // not changing + BOOST_CHECK( obj->url == "https://create_son_test" ); + + // bob tries to deregister a son object he dont own + { + son_deregister_operation op; + op.son_id = son_id_type(0); + op.payer = bob_id; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception); + + } + generate_block(); + + obj = idx.find( alice_id ); + // not deleting + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->son_account.instance == alice_id.instance); +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} +} + +BOOST_AUTO_TEST_CASE( son_pay_test ) +{ + try + { + const dynamic_global_property_object& dpo = db.get_dynamic_global_properties(); + const auto block_interval = db.get_global_properties().parameters.block_interval; + BOOST_CHECK( dpo.son_budget.value == 0); + generate_blocks(HARDFORK_SON_TIME); + while (db.head_block_time() <= HARDFORK_SON_TIME) { + generate_block(); + } + generate_block(); + set_expiration(db, trx); + + { + ACTORS((alice)(bob)); + // Send some core to the actors + transfer( committee_account, alice_id, asset( 20000 * 100000) ); + transfer( committee_account, bob_id, asset( 20000 * 100000) ); + } + + generate_block(); + // Enable default fee schedule to collect fees + enable_fees(); + // Make SON Budget small for testing purposes + // Make witness budget zero so that amount can be allocated to SON + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + _gpo.parameters.extensions.value.son_pay_max = 200; + _gpo.parameters.witness_pay_per_block = 0; + } ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Upgrades pay fee and this goes to reserve + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + // Note payment time just to generate enough blocks to make budget + auto pay_fee_time = db.head_block_time().sec_since_epoch(); + generate_block(); + // Do maintenance from the upcoming block + auto schedule_maint = [&]() + { + db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo ) + { + _dpo.next_maintenance_time = db.head_block_time() + 1; + } ); + }; + + // Generate enough blocks to make budget + while( db.head_block_time().sec_since_epoch() - pay_fee_time < 100 * block_interval ) + { + generate_block(); + } + + // Enough blocks generated schedule maintenance now + schedule_maint(); + // This block triggers maintenance + generate_block(); + + // Check that the SON Budget is allocated and Witness budget is zero + BOOST_CHECK( dpo.son_budget.value == 200); + BOOST_CHECK( dpo.witness_budget.value == 0); + + // Now create SONs + std::string test_url1 = "https://create_son_test1"; + std::string test_url2 = "https://create_son_test2"; + + // create deposit vesting + vesting_balance_id_type deposit1; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit1 = ptx.operation_results[0].get(); + } + + // create payment vesting + vesting_balance_id_type payment1; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment1 = ptx.operation_results[0].get(); + } + + // create deposit vesting + vesting_balance_id_type deposit2; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit2 = ptx.operation_results[0].get(); + } + + // create payment vesting + vesting_balance_id_type payment2; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment2 = ptx.operation_results[0].get(); + } + + // alice becomes son + { + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url1; + op.deposit = deposit1; + op.pay_vb = payment1; + op.fee = asset(0); + op.signing_key = alice_private_key.get_public_key(); + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + // bob becomes son + { + son_create_operation op; + op.owner_account = bob_id; + op.url = test_url2; + op.deposit = deposit2; + op.pay_vb = payment2; + op.fee = asset(0); + op.signing_key = bob_private_key.get_public_key(); + trx.operations.push_back(op); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + // Check if SONs are created properly + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 2 ); + // Alice's SON + auto obj1 = idx.find( alice_id ); + BOOST_REQUIRE( obj1 != idx.end() ); + BOOST_CHECK( obj1->url == test_url1 ); + BOOST_CHECK( obj1->signing_key == alice_private_key.get_public_key() ); + BOOST_CHECK( obj1->deposit.instance == deposit1.instance.value ); + BOOST_CHECK( obj1->pay_vb.instance == payment1.instance.value ); + // Bob's SON + auto obj2 = idx.find( bob_id ); + BOOST_REQUIRE( obj2 != idx.end() ); + BOOST_CHECK( obj2->url == test_url2 ); + BOOST_CHECK( obj2->signing_key == bob_private_key.get_public_key() ); + BOOST_CHECK( obj2->deposit.instance == deposit2.instance.value ); + BOOST_CHECK( obj2->pay_vb.instance == payment2.instance.value ); + // Get the statistics object for the SONs + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 2 ); + auto son_stats_obj1 = sidx.find( obj1->statistics ); + auto son_stats_obj2 = sidx.find( obj2->statistics ); + BOOST_REQUIRE( son_stats_obj1 != sidx.end() ); + BOOST_REQUIRE( son_stats_obj2 != sidx.end() ); + // Modify the transaction signed statistics of Alice's SON + db.modify( *son_stats_obj1, [&]( son_statistics_object& _s) + { + _s.txs_signed = 2; + }); + // Modify the transaction signed statistics of Bob's SON + db.modify( *son_stats_obj2, [&]( son_statistics_object& _s) + { + _s.txs_signed = 3; + }); + + // Note the balances before the maintenance + int64_t obj1_balance = db.get_balance(obj1->son_account, asset_id_type()).amount.value; + int64_t obj2_balance = db.get_balance(obj2->son_account, asset_id_type()).amount.value; + // Next maintenance triggerred + generate_blocks(dpo.next_maintenance_time); + generate_block(); + // Check if the signed transaction statistics are reset for both SONs + BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed, 0); + BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed, 0); + + BOOST_REQUIRE_EQUAL(son_stats_obj1->total_txs_signed, 2); + BOOST_REQUIRE_EQUAL(son_stats_obj2->total_txs_signed, 3); + // Check that Alice and Bob are paid for signing the transactions in the previous day/cycle + BOOST_REQUIRE_EQUAL(db.get_balance(obj1->son_account, asset_id_type()).amount.value, 80+obj1_balance); + BOOST_REQUIRE_EQUAL(db.get_balance(obj2->son_account, asset_id_type()).amount.value, 120+obj2_balance); + // Check the SON Budget is again allocated after maintenance + BOOST_CHECK( dpo.son_budget.value == 200); + BOOST_CHECK( dpo.witness_budget.value == 0); + }FC_LOG_AND_RETHROW() + + } + +BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { + + try + { + INVOKE(create_son_test); + GET_ACTOR(alice); + + { + // Send Heartbeat for an inactive SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = fc::time_point::now(); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Try to go in maintenance for an inactive SON + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::request_maintenance; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + // Modify SON's status to active + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::active; + }); + + db.modify( *son_stats_obj, [&]( son_statistics_object& _s) + { + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time()); + }); + + { + generate_block(); + // Request SON Maintenance + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::request_maintenance; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_CHECK( obj->status == son_status::request_maintenance); + } + + { + generate_block(); + // Cancel SON Maintenance request + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::cancel_request_maintenance; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_CHECK( obj->status == son_status::active); + } + + // Modify SON's status to in_maintenance + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + uint64_t downtime = 0; + + { + generate_block(); + // Send Heartbeat for an in_maintenance SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + BOOST_CHECK( obj->status == son_status::inactive); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + + // Modify SON's status to in_maintenance + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + // SON is selected as one of the active SONs + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + son_info son_inf; + son_inf.son_id = son_id_type(0); + _gpo.active_sons.push_back(son_inf); + }); + + { + generate_block(); + // Send Heartbeat for an in_maintenance SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime + op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + BOOST_CHECK( obj->status == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + + { + generate_block(); + // Send Heartbeat for an active SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime); + BOOST_CHECK( obj->status == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( son_report_down_test ) { + + try + { + INVOKE(son_heartbeat_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + BOOST_CHECK( obj->status == son_status::active); + + { + // Check that transaction fails if down_ts < last_active_timestamp + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = fc::time_point_sec(son_stats_obj->last_active_timestamp - fc::seconds(1)); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction fails if payer is not db.get_global_properties().parameters.son_account(). + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = alice_id; + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction succeeds after getting enough approvals on db.get_global_properties().parameters.son_account(). + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + + BOOST_CHECK( obj->status == son_status::in_maintenance); + BOOST_CHECK( son_stats_obj->last_down_timestamp == op.down_ts); + } + + { + // Check that transaction fails if report down sent for an in_maintenance SON. + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + } FC_LOG_AND_RETHROW() +} BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/son_wallet_tests.cpp b/tests/tests/son_wallet_tests.cpp new file mode 100644 index 00000000..cef29b54 --- /dev/null +++ b/tests/tests/son_wallet_tests.cpp @@ -0,0 +1,225 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene; +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( son_wallet_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( son_wallet_recreate_test ) { + + BOOST_TEST_MESSAGE("son_wallet_recreate_test"); + + generate_blocks(HARDFORK_SON_TIME); + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)(bob)); + + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + + transfer( committee_account, alice_id, asset( 500000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + transfer( committee_account, bob_id, asset( 500000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + + generate_block(); + set_expiration(db, trx); + + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_alice = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_alice = ptx.operation_results[0].get(); + } + + generate_block(); + set_expiration(db, trx); + + // alice becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url; + op.deposit = deposit_alice; + op.pay_vb = payment_alice; + op.signing_key = alice_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + set_expiration(db, trx); + + // create deposit vesting + vesting_balance_id_type deposit_bob; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_bob ; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // bob becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = bob_id; + op.url = test_url; + op.deposit = deposit_bob; + op.pay_vb = payment_bob; + op.signing_key = bob_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + set_expiration(db, trx); + + generate_blocks(60); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send son_wallet_recreate_operation"); + + son_wallet_recreate_operation op; + + op.payer = db.get_global_properties().parameters.son_account(); + + { + son_info si; + si.son_id = son_id_type(0); + si.weight = 1000; + si.signing_key = alice_public_key; + si.sidechain_public_keys[sidechain_type::bitcoin] = ""; + op.sons.push_back(si); + } + + { + son_info si; + si.son_id = son_id_type(1); + si.weight = 1000; + si.signing_key = bob_public_key; + si.sidechain_public_keys[sidechain_type::bitcoin] = ""; + op.sons.push_back(si); + } + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check son_wallet_recreate_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find(son_wallet_id_type(0)); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_REQUIRE( obj->expires == time_point_sec::maximum() ); +} + +BOOST_AUTO_TEST_CASE( son_wallet_update_test ) { + + BOOST_TEST_MESSAGE("son_wallet_update_test"); + + INVOKE(son_wallet_recreate_test); + GET_ACTOR(alice); + + { + BOOST_TEST_MESSAGE("Send son_wallet_update_operation"); + + son_wallet_update_operation op; + + op.payer = db.get_global_properties().parameters.son_account(); + op.son_wallet_id = son_wallet_id_type(0); + op.sidechain = sidechain_type::bitcoin; + op.address = "bitcoin address"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check son_wallet_update_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find(son_wallet_id_type(0)); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_REQUIRE( obj->addresses.at(sidechain_type::bitcoin) == "bitcoin address" ); + } + +} + +BOOST_AUTO_TEST_SUITE_END()