Merge remote-tracking branch 'origin/dev' into stable

This commit is contained in:
Jason Rhinelander 2022-08-30 23:45:42 -03:00
commit 1ba2086090
No known key found for this signature in database
GPG Key ID: C4992CE7A88D4262
38 changed files with 600 additions and 2574 deletions

View File

@ -51,9 +51,13 @@ message(STATUS "CMake version ${CMAKE_VERSION}")
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)")
project(oxen
VERSION 10.1.3
VERSION 10.2.0
LANGUAGES CXX C)
set(OXEN_RELEASE_CODENAME "Wistful Wagyu")
# Version update notes:
# - bump the version above.
# - for a new major release, make a new codename
# - (if update needed) required SS/lokinet versions are in cryptonote_core/service_node_rules.h
# String value to append to the full version string; this is intended to easily identify whether a
# binary was build from the release or development branches. This should be permanently set to an

View File

@ -143,7 +143,7 @@ namespace cryptonote {
CREATE INDEX batched_payments_accrued_payout_offset_idx ON batched_payments_accrued(payout_offset);
DROP TRIGGER rollback_payment;
DROP TRIGGER IF EXISTS rollback_payment;
CREATE TRIGGER rollback_payment INSTEAD OF DELETE ON batched_payments_paid
FOR EACH ROW BEGIN
DELETE FROM batched_payments_raw WHERE address = OLD.address AND height_paid = OLD.height_paid;

View File

@ -167,11 +167,11 @@ namespace cryptonote
return result;
}
//---------------------------------------------------------------------------
bool checkpoints::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, checkpoint_t const *checkpoint)
void checkpoints::block_add(const block_add_info& info)
{
uint64_t const height = get_block_height(block);
if (height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || block.major_version < hf::hf12_checkpointing)
return true;
uint64_t const height = get_block_height(info.block);
if (height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || info.block.major_version < hf::hf12_checkpointing)
return;
uint64_t end_cull_height = 0;
{
@ -203,13 +203,11 @@ namespace cryptonote
}
}
if (checkpoint)
update_checkpoint(*checkpoint);
return true;
if (info.checkpoint)
update_checkpoint(*info.checkpoint);
}
//---------------------------------------------------------------------------
void checkpoints::blockchain_detached(uint64_t height, bool /*by_pop_blocks*/)
void checkpoints::blockchain_detached(uint64_t height)
{
m_last_cull_height = std::min(m_last_cull_height, height);

View File

@ -111,12 +111,10 @@ namespace cryptonote
* either from a json file or via DNS from a checkpoint-hosting server.
*/
class checkpoints
: public cryptonote::BlockAddedHook,
public cryptonote::BlockchainDetachedHook
{
public:
bool block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, checkpoint_t const *checkpoint) override;
void blockchain_detached(uint64_t height, bool by_pop_blocks) override;
void block_add(const block_add_info& info);
void blockchain_detached(uint64_t height);
bool get_checkpoint(uint64_t height, checkpoint_t &checkpoint) const;
/**

View File

@ -26,8 +26,7 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/algorithm/string.hpp>
#include <stdarg.h>
#include "string_util.h"
#include "epee/misc_log_ex.h"
#include "spawn.h"
#include "notify.h"
@ -43,40 +42,36 @@ namespace tools
- Improve tokenization to handle paths containing whitespaces, quotes, etc.
- Windows unicode support (implies implementing unicode command line parsing code)
*/
Notify::Notify(const char *spec)
Notify::Notify(std::string_view spec)
{
CHECK_AND_ASSERT_THROW_MES(spec, "Null spec");
CHECK_AND_ASSERT_THROW_MES(!spec.empty(), "Empty spec");
boost::split(args, spec, boost::is_any_of(" \t"), boost::token_compress_on);
CHECK_AND_ASSERT_THROW_MES(args.size() > 0, "Failed to parse spec");
if (strchr(spec, '\'') || strchr(spec, '\"') || strchr(spec, '\\'))
MWARNING("A notification spec contains a quote or backslash: note that these are handled verbatim, which may not be the intent");
filename = fs::u8path(args[0]);
auto pieces = tools::split_any(spec, " \t", true);
CHECK_AND_ASSERT_THROW_MES(pieces.size() > 0, "Failed to parse spec");
filename = fs::u8path(pieces[0]);
CHECK_AND_ASSERT_THROW_MES(fs::exists(filename), "File not found: " << filename);
args.reserve(pieces.size());
for (const auto& piece : pieces)
args.emplace_back(piece);
}
static void replace(std::vector<std::string> &v, const char *tag, const char *s)
void Notify::replace_tag(std::vector<std::string>& margs, std::string_view tag, std::string_view value)
{
for (std::string &str: v)
boost::replace_all(str, tag, s);
}
int Notify::notify(const char *tag, const char *s, ...)
{
std::vector<std::string> margs = args;
replace(margs, tag, s);
va_list ap;
va_start(ap, s);
while ((tag = va_arg(ap, const char*)))
{
s = va_arg(ap, const char*);
replace(margs, tag, s);
if (tag.empty())
return;
// Skip margs[0], it's the binary name
for (size_t i = 1; i < margs.size(); i++) {
size_t pos = 0;
while ((pos = margs[i].find(tag, pos)) != std::string::npos) {
margs[i].replace(pos, tag.size(), value);
pos += value.size();
}
}
va_end(ap);
}
return tools::spawn(filename, margs, false);
int Notify::spawn(const std::vector<std::string>& margs) const {
return tools::spawn(filename, margs, false);
}
}

View File

@ -29,7 +29,9 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <fmt/core.h>
#include "fs.h"
namespace tools
@ -38,13 +40,29 @@ namespace tools
class Notify
{
public:
Notify(const char *spec);
explicit Notify(std::string_view spec);
int notify(const char *tag, const char *s, ...);
template <typename T, typename... MoreTags>
int notify(std::string_view tag, const T& value, MoreTags&&... more) const {
std::vector<std::string> margs{args};
replace_tags(margs, tag, value, std::forward<MoreTags>(more)...);
return spawn(margs);
}
private:
fs::path filename;
std::vector<std::string> args;
int spawn(const std::vector<std::string>& margs) const;
template <typename T, typename... MoreTags>
static void replace_tags(std::vector<std::string>& margs, std::string_view tag, const T& value, MoreTags&&... more) {
replace_tag(margs, tag, fmt::format("{}", value));
if constexpr (sizeof...(MoreTags) > 0)
replace_tags(margs, std::forward<MoreTags>(more)...);
}
static void replace_tag(std::vector<std::string>& margs, std::string_view tag, std::string_view value);
};
}

View File

@ -43,7 +43,7 @@ add_library(cncrypto
hmac-keccak.c
jh.c
keccak.c
oaes_lib.c
oaes_lib_expand.c
random.c
skein.c
cn_heavy_hash_hard_arm.cpp

View File

@ -31,19 +31,21 @@
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "epee/int-util.h"
#include "hash-ops.h"
#include "oaes_lib.h"
#include "oaes_lib_expand.h"
#include "variant2_int_sqrt.h"
#define MEMORY (1 << 21) // 2MB scratchpad
#define ITER (1 << 20)
#define AES_BLOCK_SIZE 16
#define AES_KEY_SIZE 32
#define AES_EXPANDED_KEY_SIZE 240
#define INIT_SIZE_BLK 8
#define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE)
@ -695,7 +697,7 @@ void monero_hash_free_state(void)
*/
void cn_monero_hash(const void *data, size_t length, char *hash, int variant, int prehashed)
{
RDATA_ALIGN16 uint8_t expandedKey[240]; /* These buffers are aligned to use later with SSE functions */
RDATA_ALIGN16 uint8_t expandedKey[AES_EXPANDED_KEY_SIZE]; /* These buffers are aligned to use later with SSE functions */
uint8_t text[INIT_SIZE_BYTE];
RDATA_ALIGN16 uint64_t a[2];
@ -707,7 +709,6 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
size_t i, j;
uint64_t *p = NULL;
oaes_ctx *aes_ctx = NULL;
int useAes = !force_software_aes() && check_aes_hw();
static void (*const extra_hashes[4])(const void *, size_t, char *) =
@ -745,12 +746,11 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
else
{
aes_ctx = (oaes_ctx *) oaes_alloc();
oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE);
oaes_expand_key_256(state.hs.b, expandedKey);
for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
memcpy(&hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
}
@ -805,16 +805,15 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
else
{
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
{
xor_blocks(&text[j * AES_BLOCK_SIZE], &hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
}
oaes_free((OAES_CTX **) &aes_ctx);
}
/* CryptoNight Step 5: Apply Keccak to the state again, and then
@ -1286,13 +1285,12 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
uint8_t c1[AES_BLOCK_SIZE];
uint8_t d[AES_BLOCK_SIZE];
uint8_t aes_key[AES_KEY_SIZE];
RDATA_ALIGN16 uint8_t expandedKey[256];
RDATA_ALIGN16 uint8_t expandedKey[AES_EXPANDED_KEY_SIZE];
union cn_monero_hash_state state;
size_t i, j;
uint8_t *p = NULL;
oaes_ctx *aes_ctx;
static void (*const extra_hashes[4])(const void *, size_t, char *) =
{
hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein
@ -1311,14 +1309,11 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
memcpy(text, state.init, INIT_SIZE_BYTE);
aes_ctx = (oaes_ctx *) oaes_alloc();
oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE);
VARIANT1_INIT64();
VARIANT2_INIT64();
// use aligned data
memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len);
oaes_expand_key_256(state.hs.b, expandedKey);
for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
@ -1368,8 +1363,7 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
memcpy(text, state.init, INIT_SIZE_BYTE);
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
@ -1379,7 +1373,6 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
}
oaes_free((OAES_CTX **) &aes_ctx);
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
@ -1491,7 +1484,7 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
uint8_t d[AES_BLOCK_SIZE];
size_t i, j;
uint8_t aes_key[AES_KEY_SIZE];
oaes_ctx *aes_ctx;
uint8_t expandedKey[AES_EXPANDED_KEY_SIZE];
if (prehashed) {
memcpy(&state.hs, data, length);
@ -1500,15 +1493,14 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
memcpy(text, state.init, INIT_SIZE_BYTE);
memcpy(aes_key, state.hs.b, AES_KEY_SIZE);
aes_ctx = (oaes_ctx *) oaes_alloc();
VARIANT1_PORTABLE_INIT();
VARIANT2_PORTABLE_INIT();
oaes_key_import_data(aes_ctx, aes_key, AES_KEY_SIZE);
oaes_expand_key_256(aes_key, expandedKey);
for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) {
for (j = 0; j < INIT_SIZE_BLK; j++) {
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
}
@ -1554,18 +1546,17 @@ void cn_monero_hash(const void *data, size_t length, char *hash, int variant, in
}
memcpy(text, state.init, INIT_SIZE_BYTE);
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) {
for (j = 0; j < INIT_SIZE_BLK; j++) {
xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
}
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
/*memcpy(hash, &state, 32);*/
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
oaes_free((OAES_CTX **) &aes_ctx);
#ifdef FORCE_USE_HEAP
free(long_state);

View File

@ -415,7 +415,7 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
uint32_t aes_rounds = (iterations / 2);
size_t lightFlag = (light ? 2: 1);
RDATA_ALIGN16 uint8_t expandedKey[240]; /* These buffers are aligned to use later with SSE functions */
RDATA_ALIGN16 uint8_t expandedKey[AES_EXPANDED_KEY_SIZE]; /* These buffers are aligned to use later with SSE functions */
uint8_t text[INIT_SIZE_BYTE];
RDATA_ALIGN16 uint64_t a[2];
@ -427,7 +427,6 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
size_t i, j;
uint64_t *p = NULL;
oaes_ctx *aes_ctx = NULL;
static void (*const extra_hashes[4])(const void *, size_t, char *) =
{
@ -462,12 +461,11 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
else
{
aes_ctx = (oaes_ctx *) oaes_alloc();
oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE);
oaes_expand_key_256(state.hs.b, expandedKey);
for(i = 0; i < init_rounds; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
memcpy(&hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
}
@ -522,16 +520,15 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
else
{
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for(i = 0; i < init_rounds; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
{
xor_blocks(&text[j * AES_BLOCK_SIZE], &hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
}
oaes_free((OAES_CTX **) &aes_ctx);
}
/* CryptoNight Step 5: Apply Keccak to the state again, and then

View File

@ -224,7 +224,7 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
uint32_t aes_rounds = (iterations / 2);
size_t lightFlag = (light ? 2: 1);
RDATA_ALIGN16 uint8_t expandedKey[240];
RDATA_ALIGN16 uint8_t expandedKey[AES_EXPANDED_KEY_SIZE];
#ifndef FORCE_USE_HEAP
RDATA_ALIGN16 uint8_t hp_state[CN_TURTLE_PAGE_SIZE];
@ -458,7 +458,6 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
size_t i, j;
uint8_t *p = NULL;
oaes_ctx *aes_ctx;
static void (*const extra_hashes[4])(const void *, size_t, char *) =
{
hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein
@ -478,14 +477,10 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
memcpy(text, state.init, INIT_SIZE_BYTE);
aes_ctx = (oaes_ctx *) oaes_alloc();
oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE);
VARIANT1_INIT64();
VARIANT2_INIT64();
// use aligned data
memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len);
oaes_expand_key_256(state.hs.b, expandedKey);
for(i = 0; i < init_rounds; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
@ -535,8 +530,7 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
memcpy(text, state.init, INIT_SIZE_BYTE);
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for(i = 0; i < init_rounds; i++)
{
for(j = 0; j < INIT_SIZE_BLK; j++)
@ -546,7 +540,6 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
}
oaes_free((OAES_CTX **) &aes_ctx);
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);

View File

@ -95,7 +95,7 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
uint8_t d[AES_BLOCK_SIZE];
size_t i, j;
uint8_t aes_key[AES_KEY_SIZE];
oaes_ctx *aes_ctx;
uint8_t expandedKey[AES_EXPANDED_KEY_SIZE];
if (prehashed) {
memcpy(&state.hs, data, length);
@ -104,15 +104,14 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
memcpy(text, state.init, INIT_SIZE_BYTE);
memcpy(aes_key, state.hs.b, AES_KEY_SIZE);
aes_ctx = (oaes_ctx *) oaes_alloc();
VARIANT1_PORTABLE_INIT();
VARIANT2_PORTABLE_INIT();
oaes_key_import_data(aes_ctx, aes_key, AES_KEY_SIZE);
oaes_expand_key_256(aes_key, expandedKey);
for (i = 0; i < init_rounds; i++) {
for (j = 0; j < INIT_SIZE_BLK; j++) {
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
}
@ -160,18 +159,17 @@ void cn_turtle_hash(const void *data, size_t length, char *hash, int light, int
}
memcpy(text, state.init, INIT_SIZE_BYTE);
oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
oaes_expand_key_256(&state.hs.b[32], expandedKey);
for (i = 0; i < init_rounds; i++) {
for (j = 0; j < INIT_SIZE_BLK; j++) {
xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey);
}
}
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
/*memcpy(hash, &state, 32);*/
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
oaes_free((OAES_CTX **) &aes_ctx);
#ifdef FORCE_USE_HEAP
free(long_state);

View File

@ -8,17 +8,19 @@
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "epee/int-util.h"
#include "hash-ops.h"
#include "oaes_lib.h"
#include "oaes_lib_expand.h"
#include "variant2_int_sqrt.h"
// Standard Crypto Definitions
#define AES_BLOCK_SIZE 16
#define AES_KEY_SIZE 32
#define AES_EXPANDED_KEY_SIZE 240
#define INIT_SIZE_BLK 8
#define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE)

File diff suppressed because it is too large Load Diff

View File

@ -1,215 +0,0 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_LIB_H
#define _OAES_LIB_H
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef OAES_SHARED
# ifdef oaes_lib_EXPORTS
# define OAES_API __declspec(dllexport)
# else
# define OAES_API __declspec(dllimport)
# endif
# else
# define OAES_API
# endif
#else
# define OAES_API
#endif // WIN32
#define OAES_VERSION "0.8.1"
#define OAES_BLOCK_SIZE 16
typedef void OAES_CTX;
typedef enum
{
OAES_RET_FIRST = 0,
OAES_RET_SUCCESS = 0,
OAES_RET_UNKNOWN,
OAES_RET_ARG1,
OAES_RET_ARG2,
OAES_RET_ARG3,
OAES_RET_ARG4,
OAES_RET_ARG5,
OAES_RET_NOKEY,
OAES_RET_MEM,
OAES_RET_BUF,
OAES_RET_HEADER,
OAES_RET_COUNT
} OAES_RET;
/*
* oaes_set_option() takes one of these values for its [option] parameter
* some options accept either an optional or a required [value] parameter
*/
// no option
#define OAES_OPTION_NONE 0
// enable ECB mode, disable CBC mode
#define OAES_OPTION_ECB 1
// enable CBC mode, disable ECB mode
// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify
// the value of the initialization vector, iv
#define OAES_OPTION_CBC 2
#ifdef OAES_DEBUG
typedef int ( * oaes_step_cb ) (
const uint8_t state[OAES_BLOCK_SIZE],
const char * step_name,
int step_count,
void * user_data );
// enable state stepping mode
// value is required, must pass oaes_step_cb to receive the state at each step
#define OAES_OPTION_STEP_ON 4
// disable state stepping mode
#define OAES_OPTION_STEP_OFF 8
#endif // OAES_DEBUG
typedef uint16_t OAES_OPTION;
typedef struct _oaes_key
{
size_t data_len;
uint8_t *data;
size_t exp_data_len;
uint8_t *exp_data;
size_t num_keys;
size_t key_base;
} oaes_key;
typedef struct _oaes_ctx
{
#ifdef OAES_HAVE_ISAAC
randctx * rctx;
#endif // OAES_HAVE_ISAAC
#ifdef OAES_DEBUG
oaes_step_cb step_cb;
#endif // OAES_DEBUG
oaes_key * key;
OAES_OPTION options;
uint8_t iv[OAES_BLOCK_SIZE];
} oaes_ctx;
/*
* // usage:
*
* OAES_CTX * ctx = oaes_alloc();
* .
* .
* .
* {
* oaes_gen_key_xxx( ctx );
* {
* oaes_key_export( ctx, _buf, &_buf_len );
* // or
* oaes_key_export_data( ctx, _buf, &_buf_len );\
* }
* }
* // or
* {
* oaes_key_import( ctx, _buf, _buf_len );
* // or
* oaes_key_import_data( ctx, _buf, _buf_len );
* }
* .
* .
* .
* oaes_encrypt( ctx, m, m_len, c, &c_len );
* .
* .
* .
* oaes_decrypt( ctx, c, c_len, m, &m_len );
* .
* .
* .
* oaes_free( &ctx );
*/
OAES_API OAES_CTX * oaes_alloc(void);
OAES_API OAES_RET oaes_free( OAES_CTX ** ctx );
OAES_API OAES_RET oaes_set_option( OAES_CTX * ctx,
OAES_OPTION option, const void * value );
OAES_API OAES_RET oaes_key_gen_128( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_192( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_256( OAES_CTX * ctx );
// export key with header information
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// directly export the data from key
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export_data( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// import key with header information
OAES_API OAES_RET oaes_key_import( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// directly import data into key
OAES_API OAES_RET oaes_key_import_data( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// set c == NULL to get the required c_len
OAES_API OAES_RET oaes_encrypt( OAES_CTX * ctx,
const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len );
// set m == NULL to get the required m_len
OAES_API OAES_RET oaes_decrypt( OAES_CTX * ctx,
const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len );
// set buf == NULL to get the required buf_len
OAES_API OAES_RET oaes_sprintf(
char * buf, size_t * buf_len, const uint8_t * data, size_t data_len );
OAES_API OAES_RET oaes_encryption_round( const uint8_t * key, uint8_t * c );
OAES_API OAES_RET oaes_pseudo_encrypt_ecb( OAES_CTX * ctx, uint8_t * c );
#ifdef __cplusplus
}
#endif
#endif // _OAES_LIB_H

View File

@ -0,0 +1,130 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include "oaes_lib_expand.h"
#define OAES_RKEY_LEN 4
#define OAES_COL_LEN 4
#define OAES_ROUND_BASE 7
static uint8_t oaes_gf_8[] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
static uint8_t oaes_sub_byte_value[16][16] = {
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f,
/*0*/ { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76 },
/*1*/ { 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0 },
/*2*/ { 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15 },
/*3*/ { 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75 },
/*4*/ { 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84 },
/*5*/ { 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf },
/*6*/ { 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8 },
/*7*/ { 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2 },
/*8*/ { 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73 },
/*9*/ { 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb },
/*a*/ { 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79 },
/*b*/ { 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08 },
/*c*/ { 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a },
/*d*/ { 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e },
/*e*/ { 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf },
/*f*/ { 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 },
};
static void oaes_sub_byte( uint8_t * byte )
{
size_t _x, _y;
if( NULL == byte )
return;
_x = _y = *byte;
_x &= 0x0f;
_y &= 0xf0;
_y >>= 4;
*byte = oaes_sub_byte_value[_y][_x];
}
static void oaes_word_rot_left( uint8_t word[OAES_COL_LEN] )
{
uint8_t _temp[OAES_COL_LEN];
if( NULL == word )
return;
memcpy( _temp, word + 1, OAES_COL_LEN - 1 );
_temp[OAES_COL_LEN - 1] = word[0];
memcpy( word, _temp, OAES_COL_LEN );
}
void oaes_expand_key_256(const uint8_t *data, uint8_t *exp_data)
{
size_t _i, _j;
const size_t key_base = 32 / OAES_RKEY_LEN;
const size_t num_keys = key_base + OAES_ROUND_BASE;
assert(num_keys * OAES_RKEY_LEN * OAES_COL_LEN == 240);
// the first key->data_len are a direct copy
memcpy(exp_data, data, 32 );
// apply ExpandKey algorithm for remainder
for( _i = key_base; _i < num_keys * OAES_RKEY_LEN; _i++ )
{
uint8_t _temp[OAES_COL_LEN];
memcpy( _temp, exp_data + ( _i - 1 ) * OAES_RKEY_LEN, OAES_COL_LEN );
// transform key column
if( 0 == _i % key_base )
{
oaes_word_rot_left( _temp );
for( _j = 0; _j < OAES_COL_LEN; _j++ )
oaes_sub_byte( _temp + _j );
_temp[0] = _temp[0] ^ oaes_gf_8[ _i / key_base - 1 ];
}
else if( key_base > 6 && 4 == _i % key_base )
{
for( _j = 0; _j < OAES_COL_LEN; _j++ )
oaes_sub_byte( _temp + _j );
}
for( _j = 0; _j < OAES_COL_LEN; _j++ )
{
exp_data[ _i * OAES_RKEY_LEN + _j ] =
exp_data[ ( _i - key_base ) *
OAES_RKEY_LEN + _j ] ^ _temp[_j];
}
}
}

View File

@ -28,23 +28,20 @@
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_CONFIG_H
#define _OAES_CONFIG_H
#ifndef _OAES_LIB_H
#define _OAES_LIB_H
#ifdef __cplusplus
extern "C" {
#endif
//#ifndef OAES_HAVE_ISAAC
//#define OAES_HAVE_ISAAC 1
//#endif // OAES_HAVE_ISAAC
#include <stdint.h>
//#ifndef OAES_DEBUG
//#define OAES_DEBUG 0
//#endif // OAES_DEBUG
// directly import data (32 bytes), writes expanded key data of 240 bytes into exp_data
void oaes_expand_key_256(const uint8_t *data, uint8_t *exp_data);
#ifdef __cplusplus
}
#endif
#endif // _OAES_CONFIG_H
#endif // _OAES_LIB_H

View File

@ -36,23 +36,33 @@
namespace cryptonote {
class BlockAddedHook
{
public:
virtual bool block_added(const block& block, const std::vector<transaction>& txs, struct checkpoint_t const *checkpoint) = 0;
struct checkpoint_t;
struct block_add_info {
const cryptonote::block& block;
const std::vector<transaction>& txs;
const checkpoint_t* const checkpoint;
};
class BlockchainDetachedHook
{
public:
virtual void blockchain_detached(uint64_t height, bool by_pop_blocks) = 0;
using BlockAddHook = std::function<void(const block_add_info& info)>;
struct block_post_add_info {
const cryptonote::block& block;
bool reorg;
uint64_t split_height; // Only set when reorg is true
};
class InitHook
{
public:
virtual void init() = 0;
using BlockPostAddHook = std::function<void(const block_post_add_info& info)>;
struct detached_info {
uint64_t height;
bool by_pop_blocks;
};
using BlockchainDetachedHook = std::function<void(const detached_info& info)>;
using InitHook = std::function<void()>;
struct batch_sn_payment;
struct block_reward_parts;
struct miner_tx_info {
const cryptonote::block& block;
const block_reward_parts& reward_parts;
const std::vector<cryptonote::batch_sn_payment>& batched_sn_payments;
};
using ValidateMinerTxHook = std::function<void(const miner_tx_info& info)>;
struct address_parse_info
{
@ -76,18 +86,6 @@ namespace cryptonote {
: address_info{addr, 0}, amount{amt} {}
};
class ValidateMinerTxHook
{
public:
virtual bool validate_miner_tx(cryptonote::block const &block, struct block_reward_parts const &reward_parts, std::optional<std::vector<cryptonote::batch_sn_payment>> const &batched_sn_payments) const = 0;
};
class AltBlockAddedHook
{
public:
virtual bool alt_block_added(const block &block, const std::vector<transaction>& txs, struct checkpoint_t const *checkpoint) = 0;
};
#pragma pack(push, 1)
struct public_address_outer_blob
{

View File

@ -50,6 +50,7 @@ static constexpr std::array mainnet_hard_forks =
hard_fork{hf::hf18, 1, 839009, 1626217200 /*Tuesday, July 13, 2021 23:00 UTC */ }, // Oxen 9.2: mandatory SS 2.2.0 & lokinet 0.9.5 updates
hard_fork{hf::hf19_reward_batching, 0, 1080149, 1655154000 /*Monday, June 13, 2022 21:00 UTC */}, // Oxen 10.1: Service Node Reward Batching
hard_fork{hf::hf19_reward_batching, 1, 1090229, 1656363600 /*Monday, June 27, 2022 21:00 UTC */}, // Minor hardfork, upgrades to session.
hard_fork{hf::hf19_reward_batching, 2, 1146479, 1662066000 /*Wednesday, September 14, 2022 0:00 UTC */}, // Oxen 10.2: Unlock fixes, mandatory SS 2.4.0 update
};
static constexpr std::array testnet_hard_forks =
@ -65,6 +66,7 @@ static constexpr std::array testnet_hard_forks =
hard_fork{hf::hf18, 0, 252, 1653632397},
hard_fork{hf::hf19_reward_batching, 0, 253, 1653632397},
hard_fork{hf::hf19_reward_batching, 1, 254, 1653632397}, // 2022-05-27T06:19:57.000Z UTC
hard_fork{hf::hf19_reward_batching, 2, 62885, 1661205699}, // 2022-08-22T22:01:39.000Z UTC
};
static constexpr std::array devnet_hard_forks =

View File

@ -201,6 +201,7 @@ enum class hf : uint8_t
hf17,
hf18,
hf19_reward_batching,
hf20,
_next,
none = 0
@ -214,8 +215,8 @@ constexpr auto hf_prev(hf x) {
}
// This is here to make sure the numeric value of the top hf enum value is correct (i.e.
// hf19_reward_batching == 19 numerically); bump this when adding a new hf.
static_assert(static_cast<uint8_t>(hf_max) == 19);
// hf20 == 20 numerically); bump this when adding a new hf.
static_assert(static_cast<uint8_t>(hf_max) == 20);
// Constants for which hardfork activates various features:
namespace feature {

View File

@ -59,7 +59,6 @@
#include "cryptonote_core.h"
#include "ringct/rctSigs.h"
#include "common/perf_timer.h"
#include "common/notify.h"
#include "service_node_voting.h"
#include "service_node_list.h"
#include "common/varint.h"
@ -318,7 +317,7 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
// If the batching database falls behind it NEEDS the service node list information at that point in time
if (sqlite_height < snl_height)
{
m_service_node_list.blockchain_detached(sqlite_height, true);
m_service_node_list.blockchain_detached(sqlite_height);
snl_height = std::min(sqlite_height, m_service_node_list.height()) + 1;
}
start_height_options.push_back(snl_height);
@ -401,9 +400,10 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
if (blk.major_version >= hf::hf13_enforce_checkpoints && get_checkpoint(block_height, checkpoint))
checkpoint_ptr = &checkpoint;
if (!m_service_node_list.block_added(blk, txs, checkpoint_ptr))
{
MFATAL("Unable to process block for updating service node list: " << cryptonote::get_block_hash(blk));
try {
m_service_node_list.block_add(blk, txs, checkpoint_ptr);
} catch (const std::exception& e) {
MFATAL("Unable to process block {} for updating service node list: " << e.what());
return false;
}
snl_iteration_duration += clock::now() - snl_start;
@ -604,10 +604,10 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, std::shared_ptr<crypton
}
hook_block_added(m_checkpoints);
hook_blockchain_detached(m_checkpoints);
for (InitHook* hook : m_init_hooks)
hook->init();
hook_block_add([this] (const auto& info) { m_checkpoints.block_add(info); });
hook_blockchain_detached([this] (const auto& info) { m_checkpoints.blockchain_detached(info.height); });
for (const auto& hook : m_init_hooks)
hook();
if (!m_db->is_read_only() && !load_missing_blocks_into_oxen_subsystems())
{
@ -723,9 +723,9 @@ void Blockchain::pop_blocks(uint64_t nblocks)
return;
}
auto split_height = m_db->height();
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
hook->blockchain_detached(split_height, true /*by_pop_blocks*/);
detached_info hook_data{m_db->height(), /*by_pop_blocks=*/true};
for (const auto& hook : m_blockchain_detached_hooks)
hook(hook_data);
if (!pop_batching_rewards)
m_service_node_list.reset_batching_to_latest_height();
load_missing_blocks_into_oxen_subsystems();
@ -824,8 +824,8 @@ bool Blockchain::reset_and_set_genesis_block(const block& b)
m_db->reset();
m_db->drop_alt_blocks();
for (InitHook* hook : m_init_hooks)
hook->init();
for (const auto& hook : m_init_hooks)
hook();
db_wtxn_guard wtxn_guard(m_db);
block_verification_context bvc{};
@ -1051,8 +1051,9 @@ bool Blockchain::rollback_blockchain_switching(const std::list<block_and_checkpo
}
// Revert all changes from switching to the alt chain before adding the original chain back in
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
hook->blockchain_detached(rollback_height, false /*by_pop_blocks*/);
detached_info rollback_hook_data{rollback_height, /*by_pop_blocks=*/false};
for (const auto& hook : m_blockchain_detached_hooks)
hook(rollback_hook_data);
load_missing_blocks_into_oxen_subsystems();
//return back original chain
@ -1113,8 +1114,9 @@ bool Blockchain::switch_to_alternative_blockchain(const std::list<block_extended
}
auto split_height = m_db->height();
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
hook->blockchain_detached(split_height, false /*by_pop_blocks*/);
detached_info split_hook_data{split_height, /*by_pop_blocks=*/false};
for (const auto& hook : m_blockchain_detached_hooks)
hook(split_hook_data);
load_missing_blocks_into_oxen_subsystems();
//connecting new alternative chain
@ -1175,15 +1177,12 @@ bool Blockchain::switch_to_alternative_blockchain(const std::list<block_extended
get_block_longhash_reorg(split_height);
std::shared_ptr<tools::Notify> reorg_notify = m_reorg_notify;
if (reorg_notify)
reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(),
"%n", std::to_string(m_db->height() - split_height).c_str(), NULL);
std::shared_ptr<tools::Notify> block_notify = m_block_notify;
if (block_notify)
for (const auto &bei: alt_chain)
block_notify->notify("%s", tools::type_to_hex(get_block_hash(bei.bl)).c_str(), NULL);
for (auto it = alt_chain.begin(); it != alt_chain.end(); ++it) {
// Only the first hook gets `reorg=true`, the rest don't count as reorgs
block_post_add_info hook_data{it->bl, it == alt_chain.begin(), split_height};
for (const auto& hook: m_block_post_add_hooks)
hook(hook_data);
}
MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height());
return true;
@ -1379,11 +1378,15 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
if (m_nettype != network_type::FAKECHAIN)
throw std::logic_error("Blockchain missing SQLite Database");
}
cryptonote::block bl = b;
for (ValidateMinerTxHook* hook : m_validate_miner_tx_hooks)
miner_tx_info hook_data{b, reward_parts, batched_sn_payments};
for (const auto& hook : m_validate_miner_tx_hooks)
{
if (!hook->validate_miner_tx(b, reward_parts, batched_sn_payments))
try {
hook(hook_data);
} catch (const std::exception& e) {
MGINFO_RED("Miner tx failed validation: " << e.what());
return false;
}
}
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < hf::hf19_reward_batching)
@ -1968,7 +1971,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
{
// NOTE: Pulse blocks don't use PoW. They use Service Node signatures.
// Delay signature verification until Service Node List adds the block in
// the block_added hook.
// the block_add hook.
}
else
{
@ -2095,10 +2098,15 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
txs.push_back(tx);
}
for (AltBlockAddedHook *hook : m_alt_block_added_hooks)
block_add_info hook_data{b, txs, checkpoint};
for (const auto& hook : m_alt_block_add_hooks)
{
if (!hook->alt_block_added(b, txs, checkpoint))
return false;
try {
hook(hook_data);
} catch (const std::exception& e) {
LOG_PRINT_L1("Failed to add alt block: " << e.what());
return false;
}
}
}
@ -4297,7 +4305,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
{
// NOTE: Pulse blocks don't use PoW. They use Service Node signatures.
// Delay signature verification until Service Node List adds the block in
// the block_added hook.
// the block_add hook.
}
else // check proof of work
{
@ -4502,14 +4510,14 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
auto abort_block = oxen::defer([&]() {
pop_block_from_blockchain();
auto old_height = m_db->height();
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
hook->blockchain_detached(old_height, false /*by_pop_blocks*/);
detached_info hook_data{m_db->height(), false /*by_pop_blocks*/};
for (const auto& hook : m_blockchain_detached_hooks)
hook(hook_data);
});
// TODO(oxen): Not nice, making the hook take in a vector of pair<transaction,
// std::string> messes with service_node_list::init which only constructs
// a vector of transactions and then subsequently calls block_added, so the
// a vector of transactions and then subsequently calls block_add, so the
// init step would have to intentionally allocate the blobs or retrieve them
// from the DB.
// Secondly we don't use the blobs at all in the hooks, so passing it in
@ -4519,9 +4527,10 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
for (std::pair<transaction, std::string> const &tx_pair : txs)
only_txs.push_back(tx_pair.first);
if (!m_service_node_list.block_added(bl, only_txs, checkpoint))
{
MGINFO_RED("Failed to add block to Service Node List.");
try {
m_service_node_list.block_add(bl, only_txs, checkpoint);
} catch (const std::exception& e) {
MGINFO_RED("Failed to add block to Service Node List: " << e.what());
bvc.m_verifivation_failed = true;
return false;
}
@ -4545,11 +4554,13 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
throw std::logic_error("Blockchain missing SQLite Database");
}
for (BlockAddedHook* hook : m_block_added_hooks)
block_add_info hook_data{bl, only_txs, checkpoint};
for (const auto& hook : m_block_add_hooks)
{
if (!hook->block_added(bl, only_txs, checkpoint))
{
MGINFO_RED("Block added hook signalled failure");
try {
hook(hook_data);
} catch (const std::exception& e) {
MGINFO_RED("Block add hook failed with exception: " << e.what());
bvc.m_verifivation_failed = true;
return false;
}
@ -4607,9 +4618,9 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
if (notify)
{
std::shared_ptr<tools::Notify> block_notify = m_block_notify;
if (block_notify)
block_notify->notify("%s", tools::type_to_hex(id).c_str(), NULL);
block_post_add_info hook_data{bl, /*reorg=*/false};
for (const auto& hook : m_block_post_add_hooks)
hook(hook_data);
}
return true;

View File

@ -778,20 +778,6 @@ namespace cryptonote
void set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold,
blockchain_db_sync_mode sync_mode, bool fast_sync);
/**
* @brief sets a block notify object to call for every new block
*
* @param notify the notify object to call at every new block
*/
void set_block_notify(const std::shared_ptr<tools::Notify> &notify) { m_block_notify = notify; }
/**
* @brief sets a reorg notify object to call for every reorg
*
* @param notify the notify object to call at every reorg
*/
void set_reorg_notify(const std::shared_ptr<tools::Notify> &notify) { m_reorg_notify = notify; }
/**
* @brief Put DB in safe sync mode
*/
@ -978,15 +964,32 @@ namespace cryptonote
void on_new_tx_from_block(const cryptonote::transaction &tx);
/**
* @brief add a hook for processing new blocks and rollbacks for reorgs
*
* TODO: replace these with more versatile std::functions
* @brief add a hook called during new block handling; should throw to abort adding the block.
*/
void hook_block_added (BlockAddedHook& hook) { m_block_added_hooks.push_back(&hook); }
void hook_blockchain_detached(BlockchainDetachedHook& hook) { m_blockchain_detached_hooks.push_back(&hook); }
void hook_init (InitHook& hook) { m_init_hooks.push_back(&hook); }
void hook_validate_miner_tx (ValidateMinerTxHook& hook) { m_validate_miner_tx_hooks.push_back(&hook); }
void hook_alt_block_added (AltBlockAddedHook& hook) { m_alt_block_added_hooks.push_back(&hook); }
void hook_block_add(BlockAddHook hook) { m_block_add_hooks.push_back(std::move(hook)); }
/**
* @brief add a hook to be called after a new block has been added to the (main) chain. Unlike
* the above, this only fires after addition is complete and successful, while the above hook is
* part of the addition process.
*/
void hook_block_post_add(BlockPostAddHook hook) { m_block_post_add_hooks.push_back(std::move(hook)); }
/**
* @brief add a hook called when blocks are removed from the chain.
*/
void hook_blockchain_detached(BlockchainDetachedHook hook) { m_blockchain_detached_hooks.push_back(std::move(hook)); }
/**
* @brief add a hook called during startup and re-initialization
*/
void hook_init(InitHook hook) { m_init_hooks.push_back(std::move(hook)); }
/**
* @brief add a hook to be called to validate miner txes; should throw if the miner tx is
* invalid.
*/
void hook_validate_miner_tx(ValidateMinerTxHook hook) { m_validate_miner_tx_hooks.push_back(std::move(hook)); }
/**
* @brief add a hook to be called when adding an alt-chain block; should throw to abort adding.
*/
void hook_alt_block_add(BlockAddHook hook) { m_alt_block_add_hooks.push_back(std::move(hook)); }
/**
* @brief returns the timestamps of the last N blocks
@ -1119,11 +1122,12 @@ namespace cryptonote
// some invalid blocks
std::set<crypto::hash> m_invalid_blocks;
std::vector<BlockAddedHook*> m_block_added_hooks;
std::vector<BlockchainDetachedHook*> m_blockchain_detached_hooks;
std::vector<InitHook*> m_init_hooks;
std::vector<ValidateMinerTxHook*> m_validate_miner_tx_hooks;
std::vector<AltBlockAddedHook*> m_alt_block_added_hooks;
std::vector<BlockAddHook> m_block_add_hooks;
std::vector<BlockAddHook> m_alt_block_add_hooks;
std::vector<BlockPostAddHook> m_block_post_add_hooks;
std::vector<BlockchainDetachedHook> m_blockchain_detached_hooks;
std::vector<InitHook> m_init_hooks;
std::vector<ValidateMinerTxHook> m_validate_miner_tx_hooks;
checkpoints m_checkpoints;
@ -1145,9 +1149,6 @@ namespace cryptonote
bool m_batch_success;
std::shared_ptr<tools::Notify> m_block_notify;
std::shared_ptr<tools::Notify> m_reorg_notify;
// for prepare_handle_incoming_blocks
uint64_t m_prepare_height;
uint64_t m_prepare_nblocks;
@ -1254,7 +1255,7 @@ namespace cryptonote
* @param bl the block to be added
* @param id the hash of the block
* @param bvc metadata concerning the block's validity
* @param notify if set to true, sends new block notification on success
* @param notify if set to true, fires post-add hooks on success
*
* @return true if the block was added successfully, otherwise false
*/

View File

@ -226,16 +226,6 @@ namespace cryptonote
"replaced by the number of new blocks in the new chain"
, ""
};
static const command_line::arg_descriptor<std::string> arg_block_rate_notify = {
"block-rate-notify"
, "Run a program when the block rate undergoes large fluctuations. This might "
"be a sign of large amounts of hash rate going on and off the Loki network, "
"or could be a sign that oxend is not properly synchronizing with the network. %t will be replaced "
"by the number of minutes for the observation window, %b by the number of "
"blocks observed within that window, and %e by the number of blocks that was "
"expected in that window."
, ""
};
static const command_line::arg_descriptor<bool> arg_keep_alt_blocks = {
"keep-alt-blocks"
, "Keep alternative blocks on restart"
@ -351,7 +341,6 @@ namespace cryptonote
command_line::add_arg(desc, arg_prune_blockchain);
#endif
command_line::add_arg(desc, arg_reorg_notify);
command_line::add_arg(desc, arg_block_rate_notify);
command_line::add_arg(desc, arg_keep_alt_blocks);
command_line::add_arg(desc, arg_store_quorum_history);
@ -724,20 +713,22 @@ namespace cryptonote
m_blockchain_storage.set_user_options(blocks_threads,
sync_on_blocks, sync_threshold, sync_mode, fast_sync);
try
{
if (!command_line::is_arg_defaulted(vm, arg_block_notify))
m_blockchain_storage.set_block_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, arg_block_notify).c_str())));
}
catch (const std::exception &e)
{
MERROR("Failed to parse block notify spec");
}
// We need this hook to get added before the block hook below, so that it fires first and
// catches the start of a reorg before the block hook fires for the block in the reorg.
try
{
if (!command_line::is_arg_defaulted(vm, arg_reorg_notify))
m_blockchain_storage.set_reorg_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, arg_reorg_notify).c_str())));
m_blockchain_storage.hook_block_post_add(
[this, notify=tools::Notify(command_line::get_arg(vm, arg_reorg_notify))]
(const auto& info) {
if (!info.reorg)
return;
auto h = get_current_blockchain_height();
notify.notify(
"%s", info.split_height,
"%h", h,
"%n", h - info.split_height);
});
}
catch (const std::exception &e)
{
@ -746,14 +737,17 @@ namespace cryptonote
try
{
if (!command_line::is_arg_defaulted(vm, arg_block_rate_notify))
m_block_rate_notify.reset(new tools::Notify(command_line::get_arg(vm, arg_block_rate_notify).c_str()));
if (!command_line::is_arg_defaulted(vm, arg_block_notify))
m_blockchain_storage.hook_block_post_add(
[notify=tools::Notify(command_line::get_arg(vm, arg_block_notify))]
(const auto& info) {
notify.notify("%s", tools::type_to_hex(get_block_hash(info.block)));
});
}
catch (const std::exception &e)
{
MERROR("Failed to parse block rate notify spec");
MERROR("Failed to parse block notify spec");
}
cryptonote::test_options regtest_test_options{};
for (auto [it, end] = get_hard_forks(network_type::MAINNET);
@ -764,20 +758,20 @@ namespace cryptonote
}
// Service Nodes
{
m_service_node_list.set_quorum_history_storage(command_line::get_arg(vm, arg_store_quorum_history));
m_service_node_list.set_quorum_history_storage(command_line::get_arg(vm, arg_store_quorum_history));
// NOTE: Implicit dependency. Service node list needs to be hooked before checkpoints.
m_blockchain_storage.hook_blockchain_detached(m_service_node_list);
m_blockchain_storage.hook_init(m_service_node_list);
m_blockchain_storage.hook_validate_miner_tx(m_service_node_list);
m_blockchain_storage.hook_alt_block_added(m_service_node_list);
// NOTE: Implicit dependency. Service node list needs to be hooked before checkpoints.
m_blockchain_storage.hook_blockchain_detached([this] (const auto& info) { m_service_node_list.blockchain_detached(info.height); });
m_blockchain_storage.hook_init([this] { m_service_node_list.init(); });
m_blockchain_storage.hook_validate_miner_tx([this] (const auto& info) { m_service_node_list.validate_miner_tx(info); });
m_blockchain_storage.hook_alt_block_add([this] (const auto& info) { m_service_node_list.alt_block_add(info); });
// NOTE: There is an implicit dependency on service node lists being hooked first!
m_blockchain_storage.hook_init(m_quorum_cop);
m_blockchain_storage.hook_block_added(m_quorum_cop);
m_blockchain_storage.hook_blockchain_detached(m_quorum_cop);
}
// NOTE: There is an implicit dependency on service node lists being hooked first!
m_blockchain_storage.hook_init([this] { m_quorum_cop.init(); });
m_blockchain_storage.hook_block_add([this] (const auto& info) { m_quorum_cop.block_add(info.block, info.txs); });
m_blockchain_storage.hook_blockchain_detached([this] (const auto& info) { m_quorum_cop.blockchain_detached(info.height, info.by_pop_blocks); });
m_blockchain_storage.hook_block_post_add([this] (const auto&) { update_omq_sns(); });
// Checkpoints
m_checkpoints_path = m_config_folder / fs::u8path(JSON_HASH_FILE_NAME);
@ -2446,14 +2440,6 @@ namespace cryptonote
if (p < threshold)
{
MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Loki network or under attack, or your computer's time is off. Or it could be just sheer bad luck.");
std::shared_ptr<tools::Notify> block_rate_notify = m_block_rate_notify;
if (block_rate_notify)
{
auto expected = seconds[n] / tools::to_seconds(TARGET_BLOCK_TIME);
block_rate_notify->notify("%t", std::to_string(seconds[n] / 60).c_str(), "%b", std::to_string(b).c_str(), "%e", std::to_string(expected).c_str(), NULL);
}
break; // no need to look further
}
}

View File

@ -1207,8 +1207,6 @@ namespace cryptonote
bool m_offline;
bool m_pad_transactions;
std::shared_ptr<tools::Notify> m_block_rate_notify;
struct {
std::shared_mutex mutex;
bool building = false;

View File

@ -906,6 +906,10 @@ namespace service_nodes
}
uint64_t unlock_height = get_locked_key_image_unlock_height(nettype, node_info.registration_height, block_height);
uint64_t small_contributor_amount_threshold = mul128_div64(
service_nodes::get_staking_requirement(nettype, unlock_height),
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::num,
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::den);
for (const auto &contributor : node_info.contributors)
{
auto cit = std::find_if(contributor.locked_contributions.begin(),
@ -915,9 +919,22 @@ namespace service_nodes
});
if (cit != contributor.locked_contributions.end())
{
if (hf_version >= hf::hf19_reward_batching)
if (hf_version >= hf::hf20)
{
if (cit->amount < service_nodes::SMALL_CONTRIBUTOR_THRESHOLD && (block_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
if (cit->amount < small_contributor_amount_threshold && (block_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
{
LOG_PRINT_L1("Unlock TX: small contributor trying to unlock node before "
<< std::to_string(service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
<< " blocks have passed, rejected on height: "
<< block_height << " for tx: "
<< get_transaction_hash(tx));
return false;
}
}
//TODO oxen remove this whole if block after HF20 has occurred
if (hf_version == hf::hf19_reward_batching)
{
if (cit->amount < 3749 && (block_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
{
LOG_PRINT_L1("Unlock TX: small contributor trying to unlock node before "
<< std::to_string(service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
@ -946,6 +963,44 @@ namespace service_nodes
return false;
}
//------------------------------------------------------------------
//TODO oxen remove this whole function after HF20 has occurred
bool service_node_list::state_t::is_premature_unlock(cryptonote::network_type nettype, cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction &tx) const
{
if (hf_version != hf::hf19_reward_batching)
return false;
crypto::public_key snode_key;
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key))
return false;
auto it = service_nodes_infos.find(snode_key);
if (it == service_nodes_infos.end())
return false;
const service_node_info &node_info = *it->second;
cryptonote::tx_extra_tx_key_image_unlock unlock;
if (!cryptonote::get_field_from_tx_extra(tx.extra, unlock))
return false;
uint64_t unlock_height = get_locked_key_image_unlock_height(nettype, node_info.registration_height, block_height);
uint64_t small_contributor_amount_threshold = mul128_div64(
service_nodes::get_staking_requirement(nettype, block_height),
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::num,
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::den);
for (const auto &contributor : node_info.contributors)
{
auto cit = std::find_if(contributor.locked_contributions.begin(),
contributor.locked_contributions.end(),
[&unlock](const service_node_info::contribution_t &contribution) {
return unlock.key_image == contribution.key_image;
});
if (cit != contributor.locked_contributions.end())
return cit->amount < small_contributor_amount_threshold && (block_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER;
}
return false;
}
bool is_registration_tx(cryptonote::network_type nettype, hf hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info)
{
auto maybe_reg = reg_tx_extract_fields(tx);
@ -1542,12 +1597,12 @@ namespace service_nodes
}
bool service_node_list::verify_block(const cryptonote::block &block, bool alt_block, cryptonote::checkpoint_t const *checkpoint)
void service_node_list::verify_block(const cryptonote::block &block, bool alt_block, cryptonote::checkpoint_t const *checkpoint)
{
if (block.major_version < hf::hf9_service_nodes)
return true;
return;
std::string_view block_type = alt_block ? "alt block "sv : "block "sv;
std::string_view block_type = alt_block ? "alt block"sv : "block"sv;
//
// NOTE: Verify the checkpoint given on this height that locks in a block in the past.
@ -1558,10 +1613,7 @@ namespace service_nodes
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::checkpointing, checkpoint->height, false, alt_block ? &alt_quorums : nullptr);
if (!quorum)
{
MGINFO("Failed to get testing quorum checkpoint for " << block_type << cryptonote::get_block_hash(block));
return false;
}
throw std::runtime_error{fmt::format("Failed to get testing quorum checkpoint for {} {}", block_type, cryptonote::get_block_hash(block))};
bool failed_checkpoint_verify = !service_nodes::verify_checkpoint(block.major_version, *checkpoint, *quorum);
if (alt_block && failed_checkpoint_verify)
@ -1577,10 +1629,7 @@ namespace service_nodes
}
if (failed_checkpoint_verify)
{
MGINFO("Service node checkpoint failed verification for " << block_type << cryptonote::get_block_hash(block));
return false;
}
throw std::runtime_error{fmt::format("Service node checkpoint failed verification for {} {}", block_type, cryptonote::get_block_hash(block))};
}
//
@ -1595,10 +1644,9 @@ namespace service_nodes
{
cryptonote::block prev_block;
if (!find_block_in_db(m_blockchain.get_db(), block.prev_id, prev_block))
{
MGINFO("Alt block " << cryptonote::get_block_hash(block) << " references previous block " << block.prev_id << " not available in DB.");
return false;
}
throw std::runtime_error{fmt::format(
"Alt block {} references previous block {} not available in DB.",
cryptonote::get_block_hash(block), block.prev_id)};
prev_timestamp = prev_block.timestamp;
}
@ -1609,10 +1657,9 @@ namespace service_nodes
}
if (!pulse::get_round_timings(m_blockchain, height, prev_timestamp, timings))
{
MGINFO("Failed to query the block data for Pulse timings to validate incoming " << block_type << "at height " << height);
return false;
}
throw std::runtime_error{fmt::format(
"Failed to query the block data for Pulse timings to validate incoming {} at height {}",
block_type, height)};
}
//
@ -1670,18 +1717,20 @@ namespace service_nodes
alt_pulse_quorums);
}
return result;
if (!result)
throw std::runtime_error{fmt::format("Failed to verify block components for incoming {} at height {}",
block_type, height)};
}
bool service_node_list::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint)
void service_node_list::block_add(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint)
{
if (block.major_version < hf::hf9_service_nodes)
return true;
return;
std::lock_guard lock(m_sn_mutex);
process_block(block, txs);
bool result = verify_block(block, false /*alt_block*/, checkpoint);
if (result && cryptonote::block_has_pulse_components(block))
verify_block(block, false /*alt_block*/, checkpoint);
if (cryptonote::block_has_pulse_components(block))
{
// NOTE: Only record participation if its a block we recently received.
// Otherwise processing blocks in retrospect/re-loading on restart seeds
@ -1697,10 +1746,8 @@ namespace service_nodes
{
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::pulse, block_height, false, nullptr);
if (!quorum || quorum->validators.empty())
{
MFATAL("Unexpected Pulse error " << (quorum ? " quorum was not generated" : " quorum was empty"));
return false;
}
throw std::runtime_error{fmt::format(
"Unexpected Pulse error: {}", quorum ? " quorum was not generated" : " quorum was empty")};
for (size_t validator_index = 0; validator_index < service_nodes::PULSE_QUORUM_NUM_VALIDATORS; validator_index++)
{
@ -1710,7 +1757,6 @@ namespace service_nodes
}
}
}
return result;
}
void service_node_list::reset_batching_to_latest_height()
@ -2292,7 +2338,7 @@ namespace service_nodes
m_state.update_from_block(m_blockchain.get_db(), nettype, m_transient.state_history, m_transient.state_archive, {}, block, txs, m_service_node_keys);
}
void service_node_list::blockchain_detached(uint64_t height, bool /*by_pop_blocks*/)
void service_node_list::blockchain_detached(uint64_t height)
{
std::lock_guard lock(m_sn_mutex);
@ -2442,17 +2488,15 @@ namespace service_nodes
}
// NOTE: Verify queued service node coinbase or pulse block producer rewards
static bool verify_coinbase_tx_output(cryptonote::transaction const &miner_tx,
static void verify_coinbase_tx_output(cryptonote::transaction const &miner_tx,
uint64_t height,
size_t output_index,
cryptonote::account_public_address const &receiver,
uint64_t reward)
{
if (output_index >= miner_tx.vout.size())
{
MGINFO_RED("Output Index: " << output_index << ", indexes out of bounds in vout array with size: " << miner_tx.vout.size());
return false;
}
throw std::out_of_range{fmt::format("Output Index: {} , indexes out of bounds in vout array with size: ",
output_index, miner_tx.vout.size())};
cryptonote::tx_out const &output = miner_tx.vout[output_index];
@ -2461,16 +2505,10 @@ namespace service_nodes
// 1 ULP difference in the reward calculations.
// TODO(oxen): eliminate all FP math from reward calculations
if (!within_one(output.amount, reward))
{
MGINFO_RED("Service node reward amount incorrect. Should be " << cryptonote::print_money(reward) << ", is: " << cryptonote::print_money(output.amount));
return false;
}
throw std::runtime_error{fmt::format("Service node reward amount incorrect. Should be {}, is: {}", cryptonote::print_money(reward), cryptonote::print_money(output.amount))};
if (!std::holds_alternative<cryptonote::txout_to_key>(output.target))
{
MGINFO_RED("Service node output target type should be txout_to_key");
return false;
}
throw std::runtime_error{"Service node output target type should be txout_to_key"};
// NOTE: Loki uses the governance key in the one-time ephemeral key
// derivation for both Pulse Block Producer/Queued Service Node Winner rewards
@ -2478,25 +2516,23 @@ namespace service_nodes
crypto::public_key out_eph_public_key{};
cryptonote::keypair gov_key = cryptonote::get_deterministic_keypair_from_height(height);
bool r = crypto::generate_key_derivation(receiver.m_view_public_key, gov_key.sec, derivation);
CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << receiver.m_view_public_key << ", " << gov_key.sec << ")");
r = crypto::derive_public_key(derivation, output_index, receiver.m_spend_public_key, out_eph_public_key);
CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< receiver.m_spend_public_key << ")");
if (!crypto::generate_key_derivation(receiver.m_view_public_key, gov_key.sec, derivation))
throw std::runtime_error{"Failed to generate key derivation"};
if (!crypto::derive_public_key(derivation, output_index, receiver.m_spend_public_key, out_eph_public_key))
throw std::runtime_error{"Failed derive public key"};
if (var::get<cryptonote::txout_to_key>(output.target).key != out_eph_public_key)
{
MGINFO_RED("Invalid service node reward at output: " << output_index << ", output key, specifies wrong key");
return false;
}
return true;
throw std::runtime_error{fmt::format("Invalid service node reward at output: {}, output key, specifies wrong key", output_index)};
}
bool service_node_list::validate_miner_tx(const cryptonote::block& block, const cryptonote::block_reward_parts& reward_parts, const std::optional<std::vector<cryptonote::batch_sn_payment>>& batched_sn_payments) const
void service_node_list::validate_miner_tx(const cryptonote::miner_tx_info& info) const
{
const auto& block = info.block;
const auto& reward_parts = info.reward_parts;
const auto& batched_sn_payments = info.batched_sn_payments;
const auto hf_version = block.major_version;
if (hf_version < hf::hf9_service_nodes)
return true;
return;
std::lock_guard lock(m_sn_mutex);
uint64_t const height = cryptonote::get_block_height(block);
@ -2511,10 +2547,7 @@ namespace service_nodes
{
auto const check_block_leader_pubkey = cryptonote::get_service_node_winner_from_tx_extra(miner_tx.extra);
if (block_leader.key != check_block_leader_pubkey)
{
MGINFO_RED("Service node reward winner is incorrect! Expected " << block_leader.key << ", block has " << check_block_leader_pubkey);
return false;
}
throw std::runtime_error{fmt::format("Service node reward winner is incorrect! Expected {}, block has {}", block_leader.key, check_block_leader_pubkey)};
}
enum struct verify_mode
@ -2536,20 +2569,14 @@ namespace service_nodes
std::vector<crypto::hash> entropy = get_pulse_entropy_for_next_block(m_blockchain.get_db(), block.prev_id, block.pulse.round);
quorum pulse_quorum = generate_pulse_quorum(m_blockchain.nettype(), block_leader.key, hf_version, m_state.active_service_nodes_infos(), entropy, block.pulse.round);
if (!verify_pulse_quorum_sizes(pulse_quorum))
{
MGINFO_RED("Pulse block received but Pulse has insufficient nodes for quorum, block hash " << cryptonote::get_block_hash(block) << ", height " << height);
return false;
}
throw std::runtime_error{fmt::format("Pulse block received but Pulse has insufficient nodes for quorum, block hash {}, height {}", cryptonote::get_block_hash(block), height)};
block_producer_key = pulse_quorum.workers[0];
mode = (block_producer_key == block_leader.key) ? verify_mode::pulse_block_leader_is_producer
: verify_mode::pulse_different_block_producer;
if (block.pulse.round == 0 && (mode == verify_mode::pulse_different_block_producer))
{
MGINFO_RED("The block producer in pulse round 0 should be the same node as the block leader: " << block_leader.key << ", actual producer: " << block_producer_key);
return false;
}
throw std::runtime_error{fmt::format("The block producer in pulse round 0 should be the same node as the block leader: {}, actual producer: {}", block_leader.key, block_producer_key)};
}
// NOTE: Verify miner tx vout composition
@ -2578,17 +2605,14 @@ namespace service_nodes
size_t expected_vouts_size;
switch (mode) {
case verify_mode::batched_sn_rewards:
expected_vouts_size = batched_sn_payments ? batched_sn_payments->size() : 0;
expected_vouts_size = batched_sn_payments.size();
break;
case verify_mode::pulse_block_leader_is_producer:
case verify_mode::pulse_different_block_producer:
{
auto info_it = m_state.service_nodes_infos.find(block_producer_key);
if (info_it == m_state.service_nodes_infos.end())
{
MGINFO_RED("The pulse block producer for round: " << +block.pulse.round << " is not currently a Service Node: " << block_producer_key);
return false;
}
throw std::runtime_error{fmt::format("The pulse block producer for round {:d} is not current a Service Node: {}", block.pulse.round, block_producer_key)};
block_producer = info_it->second;
expected_vouts_size = mode == verify_mode::pulse_different_block_producer && reward_parts.miner_fee > 0
@ -2610,20 +2634,16 @@ namespace service_nodes
}
if (miner_tx.vout.size() != expected_vouts_size)
{
auto type =
mode == verify_mode::miner ? "miner"sv :
mode == verify_mode::batched_sn_rewards ? "batch reward"sv :
mode == verify_mode::pulse_block_leader_is_producer ? "pulse"sv : "pulse alt round"sv;
MGINFO_RED("Expected " << type << " block, the miner TX specifies a different amount of outputs vs the expected: " << expected_vouts_size << ", miner tx outputs: " << miner_tx.vout.size());
return false;
}
throw std::runtime_error{fmt::format("Expected {} block, the miner TX specifies a different amount of outputs vs the expected: {}, miner tx outputs: {}",
mode == verify_mode::miner ? "miner"sv :
mode == verify_mode::batched_sn_rewards ? "batch reward"sv :
mode == verify_mode::pulse_block_leader_is_producer ? "pulse"sv :
"pulse alt round"sv,
expected_vouts_size,
miner_tx.vout.size())};
if (hf_version >= hf::hf16_pulse && reward_parts.base_miner != 0)
{
MGINFO_RED("Miner reward is incorrect expected 0 reward, block specified " << cryptonote::print_money(reward_parts.base_miner));
return false;
}
throw std::runtime_error{fmt::format("Miner reward is incorrect expected 0 reward, block specified {}", cryptonote::print_money(reward_parts.base_miner))};
// NOTE: Verify Coinbase Amounts
switch(mode)
@ -2646,8 +2666,7 @@ namespace service_nodes
const auto& payout = block_leader.payouts[i];
if (split_rewards[i])
{
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
return false;
verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]);
vout_index++;
}
}
@ -2666,8 +2685,7 @@ namespace service_nodes
const auto& payout = block_leader.payouts[i];
if (split_rewards[i])
{
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
return false;
verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]);
vout_index++;
}
}
@ -2685,8 +2703,7 @@ namespace service_nodes
const auto& payout = block_producer_payouts.payouts[i];
if (split_rewards[i])
{
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
return false;
verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]);
vout_index++;
}
}
@ -2698,8 +2715,7 @@ namespace service_nodes
const auto& payout = block_leader.payouts[i];
if (split_rewards[i])
{
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
return false;
verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]);
vout_index++;
}
}
@ -2709,66 +2725,47 @@ namespace service_nodes
case verify_mode::batched_sn_rewards:
{
// NB: this amount is in milli-atomics, not atomics
uint64_t total_payout_in_our_db = batched_sn_payments ? std::accumulate(
batched_sn_payments->begin(),
batched_sn_payments->end(),
uint64_t total_payout_in_our_db = std::accumulate(
batched_sn_payments.begin(),
batched_sn_payments.end(),
uint64_t{0},
[](auto&& a, auto&& b) { return a + b.amount; }) : 0;
[](auto&& a, auto&& b) { return a + b.amount; });
uint64_t total_payout_in_vouts = 0;
const auto deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(height);
for (size_t vout_index = 0; vout_index < block.miner_tx.vout.size(); vout_index++)
{
const auto& vout = block.miner_tx.vout[vout_index];
const auto& batch_payment = (*batched_sn_payments)[vout_index];
const auto& batch_payment = batched_sn_payments[vout_index];
if (!std::holds_alternative<cryptonote::txout_to_key>(vout.target))
{
MGINFO_RED("Service node output target type should be txout_to_key");
return false;
}
throw std::runtime_error{"Service node output target type should be txout_to_key"};
constexpr uint64_t max_amount = std::numeric_limits<uint64_t>::max() / cryptonote::BATCH_REWARD_FACTOR;
if (vout.amount > max_amount)
{
// We should never actually hit this limit unless someone is trying something nefarious
MGINFO_RED("Batched reward payout invalid: exceeds maximum possible payout size");
return false;
}
throw std::runtime_error{"Batched reward payout invalid: exceeds maximum possible payout size"};
auto paid_amount = vout.amount * cryptonote::BATCH_REWARD_FACTOR;
total_payout_in_vouts += paid_amount;
if (paid_amount != batch_payment.amount)
{
MGINFO_RED(fmt::format("Batched reward payout incorrect: expected {}, not {}", batch_payment.amount, paid_amount));
return false;
}
throw std::runtime_error{fmt::format("Batched reward payout incorrect: expected {}, not {}", batch_payment.amount, paid_amount)};
crypto::public_key out_eph_public_key{};
if (!cryptonote::get_deterministic_output_key(batch_payment.address_info.address, deterministic_keypair, vout_index, out_eph_public_key))
{
MGINFO_RED("Failed to generate output one-time public key");
return false;
}
throw std::runtime_error{"Failed to generate output one-time public key"};
const auto& out_to_key = var::get<cryptonote::txout_to_key>(vout.target);
if (tools::view_guts(out_to_key) != tools::view_guts(out_eph_public_key))
{
MGINFO_RED("Output Ephermeral Public Key does not match (payment to wrong recipient)");
return false;
}
throw std::runtime_error{"Output Ephermeral Public Key does not match (payment to wrong recipient)"};
}
if (total_payout_in_vouts != total_payout_in_our_db)
{
MGINFO_RED(fmt::format("Total service node reward amount incorrect: expected {}, not {}", total_payout_in_our_db, total_payout_in_vouts));
return false;
}
throw std::runtime_error{fmt::format("Total service node reward amount incorrect: expected {}, not {}", total_payout_in_our_db, total_payout_in_vouts)};
}
break;
}
return true;
}
bool service_node_list::alt_block_added(cryptonote::block const &block, std::vector<cryptonote::transaction> const &txs, cryptonote::checkpoint_t const *checkpoint)
void service_node_list::alt_block_add(const cryptonote::block_add_info& info)
{
// NOTE: The premise is to search the main list and the alternative list for
// the parent of the block we just received, generate the new Service Node
@ -2779,15 +2776,16 @@ namespace service_nodes
// store into the alt-chain until it gathers enough blocks to cause
// a reorganization (more checkpoints/PoW than the main chain).
auto& block = info.block;
if (block.major_version < hf::hf9_service_nodes)
return true;
return;
uint64_t block_height = cryptonote::get_block_height(block);
state_t const *starting_state = nullptr;
crypto::hash const block_hash = get_block_hash(block);
auto it = m_transient.alt_state.find(block_hash);
if (it != m_transient.alt_state.end()) return true; // NOTE: Already processed alt-state for this block
if (it != m_transient.alt_state.end()) return; // NOTE: Already processed alt-state for this block
// NOTE: Check if alt block forks off some historical state on the canonical chain
if (!starting_state)
@ -2805,28 +2803,22 @@ namespace service_nodes
}
if (!starting_state)
{
LOG_PRINT_L1("Received alt block but couldn't find parent state in historical state");
return false;
}
throw std::runtime_error{"Received alt block but couldn't find parent state in historical state"};
if (starting_state->block_hash != block.prev_id)
{
LOG_PRINT_L1("Unexpected state_t's hash: " << starting_state->block_hash
<< ", does not match the block prev hash: " << block.prev_id);
return false;
}
throw std::runtime_error{fmt::format("Unexpected state_t's hash: {}, does not match the block prev hash: {}",
starting_state->block_hash, block.prev_id)};
// NOTE: Generate the next Service Node list state from this Alt block.
state_t alt_state = *starting_state;
alt_state.update_from_block(m_blockchain.get_db(), m_blockchain.nettype(), m_transient.state_history, m_transient.state_archive, m_transient.alt_state, block, txs, m_service_node_keys);
alt_state.update_from_block(m_blockchain.get_db(), m_blockchain.nettype(), m_transient.state_history, m_transient.state_archive, m_transient.alt_state, block, info.txs, m_service_node_keys);
auto alt_it = m_transient.alt_state.find(block_hash);
if (alt_it != m_transient.alt_state.end())
alt_it->second = std::move(alt_state);
else
m_transient.alt_state.emplace(block_hash, std::move(alt_state));
return verify_block(block, true /*alt_block*/, checkpoint);
verify_block(block, true /*alt_block*/, info.checkpoint);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -448,11 +448,6 @@ namespace service_nodes
};
class service_node_list
: public cryptonote::BlockAddedHook,
public cryptonote::BlockchainDetachedHook,
public cryptonote::InitHook,
public cryptonote::ValidateMinerTxHook,
public cryptonote::AltBlockAddedHook
{
public:
explicit service_node_list(cryptonote::Blockchain& blockchain);
@ -460,15 +455,15 @@ namespace service_nodes
service_node_list(const service_node_list &) = delete;
service_node_list &operator=(const service_node_list &) = delete;
bool block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint) override;
void block_add(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, const cryptonote::checkpoint_t* checkpoint);
void reset_batching_to_latest_height();
bool state_history_exists(uint64_t height);
bool process_batching_rewards(const cryptonote::block& block);
bool pop_batching_rewards_block(const cryptonote::block& block);
void blockchain_detached(uint64_t height, bool by_pop_blocks) override;
void init() override;
bool validate_miner_tx(const cryptonote::block& block, const cryptonote::block_reward_parts& base_reward, const std::optional<std::vector<cryptonote::batch_sn_payment>>& batched_sn_payments) const override;
bool alt_block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint) override;
void blockchain_detached(uint64_t height);
void init();
void validate_miner_tx(const cryptonote::miner_tx_info& info) const;
void alt_block_add(const cryptonote::block_add_info& info);
payout get_block_leader() const { std::lock_guard lock{m_sn_mutex}; return m_state.get_block_leader(); }
bool is_service_node(const crypto::public_key& pubkey, bool require_active = true) const;
bool is_key_image_locked(crypto::key_image const &check_image, uint64_t *unlock_height = nullptr, service_node_info::contribution_t *the_locked_contribution = nullptr) const;
@ -728,6 +723,8 @@ namespace service_nodes
const cryptonote::transaction& tx,
const service_node_keys *my_keys);
bool process_key_image_unlock_tx(cryptonote::network_type nettype, cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction &tx);
//TODO oxen delete this function after HF20
bool is_premature_unlock(cryptonote::network_type nettype, cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction &tx) const;
payout get_block_leader() const;
payout get_block_producer(uint8_t pulse_round) const;
};
@ -737,6 +734,9 @@ namespace service_nodes
void record_timestamp_participation(crypto::public_key const &pubkey, bool participated);
void record_timesync_status(crypto::public_key const &pubkey, bool synced);
//TODO oxen delete this function after HF20
bool is_premature_unlock(cryptonote::network_type nettype, cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction &tx) const {return m_state.is_premature_unlock(nettype, hf_version, block_height, tx);}
private:
// Note(maxim): private methods don't have to be protected the mutex
bool m_rescanning = false; /* set to true when doing a rescan so we know not to reset proofs */
@ -744,7 +744,7 @@ namespace service_nodes
void record_pulse_participation(crypto::public_key const &pubkey, uint64_t height, uint8_t round, bool participated);
// Verify block against Service Node state that has just been called with 'state.update_from_block(block)'.
bool verify_block(const cryptonote::block& block, bool alt_block, cryptonote::checkpoint_t const *checkpoint);
void verify_block(const cryptonote::block& block, bool alt_block, cryptonote::checkpoint_t const *checkpoint);
void reset(bool delete_db_entry = false);
bool load(uint64_t current_height);
@ -817,8 +817,6 @@ namespace service_nodes
std::optional<registration_details> reg_tx_extract_fields(const cryptonote::transaction& tx);
uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height);
// validate_registration* and convert_registration_args functions throws this on error:
struct invalid_registration : std::invalid_argument { using std::invalid_argument::invalid_argument; };
// Converts string input values into a partially filled `registration_details`; pubkey and
// signature will be defaulted. Throws invalid_registration on any invalid input.

View File

@ -510,18 +510,12 @@ namespace service_nodes
}
}
bool quorum_cop::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const * /*checkpoint*/)
void quorum_cop::block_add(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs)
{
process_quorums(block);
uint64_t const height = cryptonote::get_block_height(block) + 1; // chain height = new top block height + 1
m_vote_pool.remove_expired_votes(height);
m_vote_pool.remove_used_votes(txs, block.major_version);
// These feels out of place here because the hook system sucks: TODO replace it with
// std::function hooks instead.
m_core.update_omq_sns();
return true;
}
static bool handle_obligations_vote(cryptonote::core &core, const quorum_vote_t& vote, const std::vector<pool_vote_entry>& votes, const quorum& quorum)

View File

@ -110,16 +110,13 @@ namespace service_nodes
};
class quorum_cop
: public cryptonote::BlockAddedHook,
public cryptonote::BlockchainDetachedHook,
public cryptonote::InitHook
{
public:
explicit quorum_cop(cryptonote::core& core);
void init() override;
bool block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const * /*checkpoint*/) override;
void blockchain_detached(uint64_t height, bool by_pop_blocks) override;
void init();
void block_add(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs);
void blockchain_detached(uint64_t height, bool by_pop_blocks);
void set_votes_relayed (std::vector<quorum_vote_t> const &relayed_votes);
std::vector<quorum_vote_t> get_relayable_votes(uint64_t current_height, cryptonote::hf hf_version, bool quorum_relay);

View File

@ -238,22 +238,6 @@ uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amou
return resultlo;
}
static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
if(cur_percent < 0.0 || cur_percent > 100.0) return false;
// Fix for truncation issue when operator cut = 100 for a pool Service Node.
if (cur_percent == 100.0)
{
portions = cryptonote::old::STAKING_PORTIONS;
}
else
{
portions = (cur_percent / 100.0) * (double)cryptonote::old::STAKING_PORTIONS;
}
return true;
}
std::optional<double> parse_fee_percent(std::string_view fee)
{
if (tools::ends_with(fee, "%"))
@ -272,12 +256,18 @@ std::optional<double> parse_fee_percent(std::string_view fee)
return percent;
}
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions) {
uint16_t percent_to_basis_points(std::string percent_string) {
const auto percent = parse_fee_percent(percent_string);
if (!percent)
throw invalid_registration{"could not parse fee percent"};
if (auto pct = parse_fee_percent(cut_str))
return get_portions_from_percent(*pct, portions);
if(*percent < 0.0 || *percent > 100.0)
throw invalid_registration{"fee percent out of bounds"};
return false;
auto basis_points = static_cast<uint16_t>(std::lround(*percent / 100.0 * cryptonote::STAKING_FEE_BASIS));
if (*percent == 100.0)
basis_points = cryptonote::STAKING_FEE_BASIS;
return basis_points;
}
} // namespace service_nodes

View File

@ -7,6 +7,10 @@
#include <chrono>
namespace service_nodes {
// validate_registration* and convert_registration_args functions throws this on error:
struct invalid_registration : std::invalid_argument { using std::invalid_argument::invalid_argument; };
inline constexpr size_t PULSE_QUORUM_ENTROPY_LAG = 21; // How many blocks back from the tip of the Blockchain to source entropy for the Pulse quorums.
inline constexpr auto PULSE_ROUND_TIME = 60s;
inline constexpr auto PULSE_WAIT_FOR_HANDSHAKES_DURATION = 10s;
@ -188,7 +192,7 @@ namespace service_nodes {
// blocks out of sync and sending something that it thinks is legit.
inline constexpr uint64_t VOTE_OR_TX_VERIFY_HEIGHT_BUFFER = 5;
inline constexpr std::array<uint16_t, 3> MIN_STORAGE_SERVER_VERSION{{2, 3, 0}};
inline constexpr std::array<uint16_t, 3> MIN_STORAGE_SERVER_VERSION{{2, 4, 0}};
inline constexpr std::array<uint16_t, 3> MIN_LOKINET_VERSION{{0, 9, 9}};
// The minimum accepted version number, broadcasted by Service Nodes via uptime proofs for each hardfork
@ -201,9 +205,8 @@ namespace service_nodes {
};
inline constexpr std::array MIN_UPTIME_PROOF_VERSIONS = {
proof_version{{cryptonote::hf::hf19_reward_batching, 2}, {10,2,0}, {0,9,9}, {2,4,0}},
proof_version{{cryptonote::hf::hf19_reward_batching, 0}, {10,0,0}, {0,9,9}, {2,3,0}},
proof_version{{cryptonote::hf::hf18, 1}, {9,2,0}, {0,9,5}, {2,2,0}},
proof_version{{cryptonote::hf::hf18, 0}, {9,1,0}, {0,9,0}, {2,1,0}},
};
using swarm_id_t = uint64_t;
@ -248,7 +251,7 @@ namespace service_nodes {
// Small Stake prevented from unlocking stake until a certain number of blocks have passed
constexpr uint64_t SMALL_CONTRIBUTOR_UNLOCK_TIMER = cryptonote::BLOCKS_PER_DAY * 30;
constexpr uint64_t SMALL_CONTRIBUTOR_THRESHOLD = 3749;
using SMALL_CONTRIBUTOR_THRESHOLD = std::ratio<2499, 10000>;
static_assert(cryptonote::old::STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
// return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in oxen atomic units
@ -297,6 +300,6 @@ uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amou
std::optional<double> parse_fee_percent(std::string_view fee);
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions);
uint16_t percent_to_basis_points(std::string percent_string);
}

View File

@ -1676,6 +1676,7 @@ end:
return false;
}
//transaction is ok.
return true;
}
@ -1856,6 +1857,11 @@ end:
try
{
ready = is_transaction_ready_to_go(meta, sorted_it.second, txblob, tx);
// TODO oxen delete this after HF20 has occurred
// after here
if (ready)
ready = !m_blockchain.get_service_node_list().is_premature_unlock(m_blockchain.nettype(), version, height, tx);
// before here
}
catch (const std::exception &e)
{

View File

@ -1901,11 +1901,6 @@ bool rpc_command_executor::print_sn_key()
namespace {
uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
{
return mul128_div64(amount, portions, cryptonote::old::STAKING_PORTIONS);
}
// Returns an error message on invalid, nullopt if good
std::optional<std::string_view> is_invalid_staking_address(
std::string_view addr,
@ -1984,388 +1979,6 @@ std::string highlight_money(uint64_t amount) {
} // anon. namespace
// Legacy pre-HF19 registrations (with 4 contributions and portions instead of amounts).
//
// TODO XXX TODO: This code is vastly duplicated from the post-HF19 version (except that it
// calculates everything in portions rather than amounts). This is very un-DRY, but it is highly
// temporary: it is *only* needed for oxen 10.x clients to be able to produce valid registrations
// prior to the hard fork: it can be removed entirely as soon as HF19 happens.
bool rpc_command_executor::prepare_registration_hf18(hf hf_version, bool force_registration) {
auto scoped_log_cats = std::make_unique<clear_log_categories>();
// Check if the daemon was started in Service Node or not
GET_INFO::response res{};
GET_SERVICE_KEYS::response kres{};
if (!invoke<GET_INFO>({}, res, "Failed to get node info") ||
!invoke<GET_SERVICE_KEYS>({}, kres, "Failed to retrieve service node keys"))
return false;
cryptonote::network_type const nettype =
res.mainnet ? cryptonote::network_type::MAINNET :
res.devnet ? cryptonote::network_type::DEVNET :
res.testnet ? cryptonote::network_type::TESTNET :
res.nettype == "fakechain" ? cryptonote::network_type::FAKECHAIN :
cryptonote::network_type::UNDEFINED;
if (!check_service_node_running(res, force_registration))
return false;
uint64_t block_height = std::max(res.height, res.target_height);
// Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not
if (!check_blockchain_synced(*this, block_height))
return false;
const uint64_t staking_requirement =
std::max(service_nodes::get_staking_requirement(nettype, block_height),
service_nodes::get_staking_requirement(nettype, block_height + 30 * 24)); // allow 1 day
fmt::print("\n\n\x1b[33;1m"
"Oxen Service Node Registration\n"
"------------------------------\n"
"Service Node Pubkey: \x1b[32;1m{}\x1b[33;1m\n"
"Staking requirement: {} from up to {} contributors\n\n",
kres.service_node_pubkey,
highlight_money(staking_requirement),
oxen::MAX_CONTRIBUTORS_V1);
enum struct register_step
{
ask_address,
ask_amount,
get_operator_fee,
summary_info,
final_summary,
cancelled_by_user,
};
struct prepare_registration_state
{
register_step prev_step = register_step::ask_address;
uint64_t operator_fee_portions = cryptonote::old::STAKING_PORTIONS;
uint64_t portions_remaining = cryptonote::old::STAKING_PORTIONS;
uint64_t total_reserved_contributions = 0;
std::vector<std::pair<std::string, uint64_t>> contributions;
};
prepare_registration_state state{};
std::stack<prepare_registration_state> state_stack;
state_stack.push(state);
bool finished = false;
bool go_back = false;
auto step = register_step::ask_address;
auto next_step = [&](register_step next)
{
state.prev_step = step;
step = next;
state_stack.push(state);
std::cout << std::endl;
};
auto check_cancel_back = [&](input_line_result result) -> bool {
switch (result) {
case input_line_result::cancel:
step = register_step::cancelled_by_user;
return true;
case input_line_result::back:
go_back = true;
return true;
default:
return false;
}
};
// anything less than DUST will be added to operator stake
constexpr uint64_t DUST = oxen::MAX_CONTRIBUTORS_V1;
while (!finished)
{
if (go_back)
{
step = state.prev_step;
state_stack.pop();
state = state_stack.top();
go_back = false;
std::cout << std::endl;
}
switch(step)
{
case register_step::ask_address:
{
bool is_operator = state.contributions.empty();
std::string prompt;
if (is_operator)
prompt = "\n\nEnter the OXEN address of the the Service Node operator\n";
else
prompt = fmt::format("\n\nThis service node requires an additional stake of {}.\n\n"
"To add a reserved contribution spot enter the contributor's OXEN address now.\n"
"Leave this blank to leave the remaining stake open to public contributors.\n",
highlight_money(staking_requirement - state.total_reserved_contributions));
auto [result, address_str] = input_line_value(prompt, /*back=*/ !is_operator);
if (check_cancel_back(result))
break;
if (!is_operator && address_str.empty())
next_step(register_step::get_operator_fee);
else if (auto bad = is_invalid_staking_address(address_str, nettype))
tools::fail_msg_writer() << *bad << std::endl;
else if (std::any_of(state.contributions.begin(), state.contributions.end(), [a=address_str](auto& b) { return b.first == a; }))
tools::fail_msg_writer() << "Invalid OXEN address: you cannot provide the same address twice" << std::endl;
else
{
state.contributions.emplace_back(std::move(address_str), 0);
next_step(register_step::ask_amount);
}
break;
}
case register_step::ask_amount:
{
bool is_operator = state.total_reserved_contributions == 0;
uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
uint64_t min_contribution_portions = is_operator
? service_nodes::MINIMUM_OPERATOR_PORTION
: service_nodes::get_min_node_contribution_in_portions(
hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size() - 1);
uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
auto [result, contribution_str] = input_line_value(fmt::format(
"\n\nThe {} must stake between {} and {}.\n\n"
"How much OXEN does {} want to stake?",
is_operator ? "operator" : "next contributor",
highlight_money(min_contribution),
highlight_money(amount_left),
is_operator ? "the operator" : fmt::format("contributor {}", state.contributions.size() - 1)),
true,
"/\x1b[36;1mmax\x1b[0m/\x1b[36;1mmin\x1b[0m",
"max"
);
if (check_cancel_back(result))
break;
uint64_t contribution;
if (contribution_str == "max")
{
fmt::print("Using maximum contribution ({})\n", highlight_money(amount_left));
contribution = amount_left;
}
else if (contribution_str == "min")
{
fmt::print("Using minimum contribution ({})\n", highlight_money(min_contribution));
contribution = min_contribution;
}
else if (auto c = cryptonote::parse_amount(contribution_str))
contribution = *c;
else
{
tools::fail_msg_writer() << "Invalid amount." << std::endl;
break;
}
if (contribution > amount_left)
{
tools::fail_msg_writer() << fmt::format(
"Invalid amount: The contribution exceeds the remaining staking requirement ({}).\n",
highlight_money(amount_left));
break;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
if (portions < min_contribution_portions)
{
tools::fail_msg_writer() << "Invalid amount: that contribution does not meet the minimum staking requirement.\n";
break;
}
if (portions > state.portions_remaining)
portions = state.portions_remaining;
state.contributions.back().second = portions;
state.portions_remaining -= portions;
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
next_step(
state.portions_remaining > 0 ? register_step::ask_address :
register_step::get_operator_fee);
break;
}
case register_step::get_operator_fee:
{
if (state.contributions.size() == 1 && state.portions_remaining == 0)
{
// Solo node, don't need to ask the fee
state.operator_fee_portions = cryptonote::old::STAKING_PORTIONS;
step = register_step::summary_info; // Not next_step() because we are skipping this step
}
else
{
auto [result, operator_fee_str] = input_line_value(R"(
This service node has multiple contributors and thus requires an operator fee
percentage. This percentage is removed from the block reward and assigned to
the operator, then the remaining reward is split among contributors (including
the operator) proportionally to their contribution.
Enter the operator fee as a percentage [0.00-100.00])");
if (check_cancel_back(result))
break;
if (!service_nodes::get_portions_from_percent_str(operator_fee_str, state.operator_fee_portions))
{
tools::fail_msg_writer() << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
break;
}
next_step(register_step::summary_info);
}
break;
}
case register_step::summary_info:
{
uint64_t open_spots = oxen::MAX_CONTRIBUTORS_V1 - state.contributions.size();
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
fmt::print("Total reserved contributions: {}\n", highlight_money(state.total_reserved_contributions));
if (amount_left <= DUST)
{
// Not calling next_step here because we have no state change to push
step = register_step::final_summary;
std::cout << std::endl;
break;
}
fmt::print(R"(
The total reserved amount ({}) is less than the required full stake ({}).
The remaining stake ({}) will be open to contribution from {}.
The Service Node will not activate until the entire stake has been contributed.
)",
highlight_money(state.total_reserved_contributions),
highlight_money(staking_requirement),
highlight_money(amount_left),
open_spots > 1 ? fmt::format("1-{} public contributors", open_spots) : "1 public contributor"
);
auto result = input_line_ask("Is this acceptable?");
if (result == input_line_result::no)
result = input_line_result::cancel;
if (check_cancel_back(result))
break;
next_step(register_step::final_summary);
break;
}
case register_step::final_summary:
{
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
std::cout << "\nRegistration Summary:\n\n";
std::cout << "Service Node Pubkey: \x1b[32;1m" << kres.service_node_pubkey << "\x1b[0m\n" << std::endl;
if (amount_left > 0 || state.contributions.size() > 1)
fmt::print("Operator fee (as % of Service Node rewards): \x1b[33;1m{}%\x1b[0m\n\n",
state.operator_fee_portions * 100.0 / static_cast<double>(cryptonote::old::STAKING_PORTIONS));
constexpr auto row = "{:^14} {:^13} {:>17} {:>8}\n"sv;
fmt::print(row, "Contributor", "Address", "Contribution", "Contr. %");
fmt::print(row, "_____________", "_____________", "_________________", "________");
fmt::print("\n");
for (size_t i = 0; i < state.contributions.size(); ++i)
{
const auto& [addr, portion] = state.contributions[i];
uint64_t amount = get_actual_amount(staking_requirement, portion);
if (amount_left <= DUST && i == 0)
amount += amount_left; // add dust to the operator.
fmt::print(row,
(i==0) ? "Operator" : "Contributor " + std::to_string(i),
addr.substr(0, 9) + ".." + addr.substr(addr.size() - 2),
cryptonote::print_money(amount),
fmt::format("{:.2f}%", portion * 100.0 / (double)cryptonote::old::STAKING_PORTIONS));
}
if (amount_left > DUST)
{
size_t open_spots = oxen::MAX_CONTRIBUTORS_V1 - state.contributions.size();
for (size_t i = 0; i < open_spots; i++) {
fmt::print(row,
"(open)",
"(any)",
i == 0 && open_spots == 1 ? cryptonote::print_money(amount_left) :
i == 0 ? ">=" + cryptonote::print_money((amount_left + open_spots - 1) / open_spots) :
"",
i == 0 && open_spots == 1 ? fmt::format("{:.2f}%", amount_left * 100.0 / staking_requirement) :
i == 0 ? fmt::format(">={:.2f}%", amount_left * 100.0 / staking_requirement / open_spots) :
"");
}
}
else if (amount_left > 0)
std::cout << R"(
Actual amounts may differ slightly from specification because of limitations on
the way fractions are represented on the blockchain.
)";
auto result = input_line_ask("\nIs the staking information above correct?");
if (result == input_line_result::no)
result = input_line_result::cancel;
if (check_cancel_back(result))
break;
finished = true;
break;
}
case register_step::cancelled_by_user:
{
tools::fail_msg_writer() << "Registration preparation cancelled." << std::endl;
return true;
}
}
}
// <operator cut> <address> <fraction> [<address> <fraction> [...]]]
std::vector<std::string> args;
args.push_back(std::to_string(state.operator_fee_portions));
for (const auto& [addr, portion] : state.contributions)
{
args.push_back(addr);
args.push_back(std::to_string(portion));
}
scoped_log_cats.reset();
GET_SERVICE_NODE_REGISTRATION_CMD_RAW::request req{};
req.args = args;
req.make_friendly = true;
req.staking_requirement = staking_requirement;
if (GET_SERVICE_NODE_REGISTRATION_CMD_RAW::response res{};
invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res,
"Failed to validate registration arguments; check the addresses and registration parameters,\n"
"make sure oxend is running as a service node, and check oxend's error log for more details."))
{
std::cout << "\n\n";
tools::success_msg_writer() << res.registration_cmd;
std::cout << "\n\n";
return true;
}
return false;
}
bool rpc_command_executor::prepare_registration(bool force_registration)
{
@ -2374,8 +1987,10 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
return false;
auto hf_version = hf_res.version;
if (hf_version < hf::hf19_reward_batching)
return prepare_registration_hf18(hf_version, force_registration);
if (hf_version < hf::hf19_reward_batching) {
tools::fail_msg_writer() << "Error: this command only supports HF19+";
return false;
}
auto scoped_log_cats = std::make_unique<clear_log_categories>();
@ -2594,14 +2209,12 @@ Enter the operator fee as a percentage [0.00-100.00])");
if (check_cancel_back(result))
break;
auto pct = service_nodes::parse_fee_percent(operator_fee_str);
if (pct)
{
state.operator_fee = static_cast<uint16_t>(std::lround(*pct / 100.0 * cryptonote::STAKING_FEE_BASIS));
try {
state.operator_fee = service_nodes::percent_to_basis_points(operator_fee_str);
next_step(register_step::summary_info);
}
else
} catch(const std::exception &e) {
tools::fail_msg_writer() << "Invalid value: " << operator_fee_str << ". Fee must be between 0 and 100%" << std::endl;
}
}
break;
}

View File

@ -3008,26 +3008,24 @@ namespace cryptonote { namespace rpc {
std::vector<std::string> args;
uint64_t const curr_height = m_core.get_current_blockchain_height();
uint64_t staking_requirement = service_nodes::get_staking_requirement(nettype(), curr_height);
std::optional<uint64_t> height = m_core.get_current_blockchain_height();
auto hf_version = get_network_version(nettype(), *height);
uint64_t staking_requirement = service_nodes::get_staking_requirement(nettype(), *height);
{
uint64_t portions_cut;
if (!service_nodes::get_portions_from_percent_str(req.operator_cut, portions_cut))
{
res.status = "Invalid value: " + req.operator_cut + ". Should be between [0-100]";
try {
args.emplace_back(std::to_string(service_nodes::percent_to_basis_points(req.operator_cut)));
} catch(const std::exception &e) {
res.status = "Invalid value: "s + e.what();
MERROR(res.status);
return res;
}
args.push_back(std::to_string(portions_cut));
}
for (const auto& [address, amount] : req.contributions)
{
uint64_t num_portions = service_nodes::get_portions_to_make_amount(staking_requirement, amount);
args.push_back(address);
args.push_back(std::to_string(num_portions));
args.push_back(address);
args.push_back(std::to_string(amount));
}
GET_SERVICE_NODE_REGISTRATION_CMD_RAW::request req_old{};

View File

@ -2,6 +2,7 @@
#include "lmq_server.h"
#include "cryptonote_config.h"
#include "oxenmq/oxenmq.h"
#include <fmt/core.h>
#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "daemon.rpc"
@ -314,7 +315,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog
}
});
core_.get_blockchain_storage().hook_block_added(*this);
core_.get_blockchain_storage().hook_block_post_add([this] (const auto& info) { send_block_notifications(info.block); return true; });
core_.get_pool().add_notify([this](const crypto::hash& id, const transaction& tx, const std::string& blob, const tx_pool_options& opts) {
send_mempool_notifications(id, tx, blob, opts);
});
@ -356,15 +357,13 @@ static void send_notifies(Mutex& mutex, Subs& subs, const char* desc, Call call)
}
}
bool omq_rpc::block_added(const block& block, const std::vector<transaction>& txs, const checkpoint_t *)
void omq_rpc::send_block_notifications(const block& block)
{
auto& omq = core_.get_omq();
std::string height = std::to_string(get_block_height(block));
std::string height = fmt::format("{}", get_block_height(block));
send_notifies(subs_mutex_, block_subs_, "block", [&](auto& conn, auto& sub) {
omq.send(conn, "notify.block", height, std::string_view{block.hash.data, sizeof(block.hash.data)});
});
return true;
}
void omq_rpc::send_mempool_notifications(const crypto::hash& id, const transaction& tx, const std::string& blob, const tx_pool_options& opts)

View File

@ -35,7 +35,7 @@
namespace oxenmq { class OxenMQ; }
namespace cryptonote { namespace rpc {
namespace cryptonote::rpc {
void init_omq_options(boost::program_options::options_description& desc);
@ -44,7 +44,7 @@ void init_omq_options(boost::program_options::options_description& desc);
* cryptonote_core--but it works with it to add RPC endpoints, make it listen on RPC ports, and
* handles RPC requests.
*/
class omq_rpc final : public cryptonote::BlockAddedHook {
class omq_rpc final {
enum class mempool_sub_type { all, blink };
struct mempool_sub {
@ -65,9 +65,9 @@ class omq_rpc final : public cryptonote::BlockAddedHook {
public:
omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::program_options::variables_map& vm);
bool block_added(const block& block, const std::vector<transaction>& txs, const checkpoint_t *) override;
void send_block_notifications(const block& block);
void send_mempool_notifications(const crypto::hash& id, const transaction& tx, const std::string& blob, const tx_pool_options& opts);
};
}} // namespace cryptonote::rpc
} // namespace cryptonote::rpc

View File

@ -440,7 +440,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
try
{
if (!command_line::is_arg_defaulted(vm, opts.tx_notify))
wallet->set_tx_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, opts.tx_notify).c_str())));
wallet->set_tx_notify(std::make_shared<tools::Notify>(command_line::get_arg(vm, opts.tx_notify)));
}
catch (const std::exception &e)
{
@ -2540,9 +2540,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (notify)
{
std::shared_ptr<tools::Notify> tx_notify = m_tx_notify;
if (tx_notify)
tx_notify->notify("%s", tools::type_to_hex(txid).c_str(), nullptr);
if (auto tx_notify = m_tx_notify)
tx_notify->notify("%s", tools::type_to_hex(txid));
}
}
//----------------------------------------------------------------------------------------------------
@ -4963,12 +4962,11 @@ void wallet2::restore_from_device(const fs::path& wallet_, const epee::wipeable_
m_subaddress_lookahead_major = 5;
m_subaddress_lookahead_minor = 20;
}
if (hwdev_label) {
fs::path hwdev_txt = m_wallet_file;
hwdev_txt += ".hwdev.txt";
if (!tools::dump_file(hwdev_txt, *hwdev_label))
MERROR("failed to write .hwdev.txt comment file");
}
fs::path hwdev_filename = m_wallet_file;
hwdev_filename += ".hwdev.txt";
std::string hwdev_text = hwdev_label.value_or("");
if (!tools::dump_file(hwdev_filename, hwdev_text))
MERROR("failed to write .hwdev.txt comment file");
if (progress_callback)
progress_callback(tr("Setting up account and subaddresses"));
setup_new_blockchain();
@ -8569,7 +8567,11 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry
return result;
}
if (contribution.amount < service_nodes::SMALL_CONTRIBUTOR_THRESHOLD && (curr_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
uint64_t small_contributor_amount_threshold = mul128_div64(
service_nodes::get_staking_requirement(nettype(), curr_height),
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::num,
service_nodes::SMALL_CONTRIBUTOR_THRESHOLD::den);
if (contribution.amount < small_contributor_amount_threshold && (curr_height - node_info.registration_height) < service_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)
{
result.msg.append("You are requesting to unlock a stake of: ");
result.msg.append(cryptonote::print_money(contribution.amount));

View File

@ -3126,20 +3126,22 @@ bool oxen_service_nodes_small_contribution_early_withdrawal::generate(std::vecto
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
CHECK_TEST_CONDITION(sn_list.size() == 1);
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 2);
CHECK_TEST_CONDITION(sn_list[0].info->requested_unlock_height == 0);
service_nodes::service_node_pubkey_info const &pubkey_info = sn_list[0];
CHECK_EQ(pubkey_info.info->total_contributed, staking_requirement);
return true;
});
cryptonote::transaction unstake = gen.create_unlock_stake_tx(sn_keys.pub, stake, alice);
gen.create_and_add_next_block({unstake}, nullptr, false, "Small contributor should not be able to withdraw early");
cryptonote::transaction unstake = gen.create_and_add_unlock_stake_tx(sn_keys.pub, alice, stake);
gen.create_and_add_next_block({unstake}, nullptr, true, "Small contributor should be able to submit transaction to network, but will not be able to withdraw early");
oxen_register_callback(events, "test_unlock_does_not_get_accepted", [sn_keys](cryptonote::core &c, size_t ev_index)
{
DEFINE_TESTS_ERROR_CONTEXT("test_unlock_does_not_get_accepted");
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
CHECK_TEST_CONDITION(sn_list.size() == 1);
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 2);
CHECK_TEST_CONDITION(sn_list[0].info->requested_unlock_height == 0);
return true;
});
@ -3179,6 +3181,7 @@ bool oxen_service_nodes_large_contribution_early_withdrawal::generate(std::vecto
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
CHECK_TEST_CONDITION(sn_list.size() == 1);
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 2);
CHECK_TEST_CONDITION(sn_list[0].info->requested_unlock_height == 0);
service_nodes::service_node_pubkey_info const &pubkey_info = sn_list[0];
CHECK_EQ(pubkey_info.info->total_contributed, staking_requirement);
@ -3193,6 +3196,7 @@ bool oxen_service_nodes_large_contribution_early_withdrawal::generate(std::vecto
DEFINE_TESTS_ERROR_CONTEXT("test_unlock_does_get_accepted");
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
CHECK_TEST_CONDITION(sn_list.size() == 1);
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 2);
CHECK_TEST_CONDITION(sn_list[0].info->requested_unlock_height > 0);
return true;
});

View File

@ -367,7 +367,7 @@ TEST(checkpoints_blockchain_detached, detach_to_checkpoint_height)
test_db->update_block_checkpoint(checkpoint);
// NOTE: Detaching to height. Our top checkpoint should be the 1st checkpoint, we should be deleting the checkpoint at checkpoint.height
cp.blockchain_detached(SECOND_HEIGHT, false /*by_pop_blocks*/);
cp.blockchain_detached(SECOND_HEIGHT);
checkpoint_t top_checkpoint;
ASSERT_TRUE(test_db->get_top_checkpoint(top_checkpoint));
ASSERT_TRUE(top_checkpoint.height == FIRST_HEIGHT);
@ -383,7 +383,7 @@ TEST(checkpoints_blockchain_detached, detach_to_1)
checkpoint.height += service_nodes::CHECKPOINT_INTERVAL;
test_db->update_block_checkpoint(checkpoint);
cp.blockchain_detached(1 /*height*/, false /*by_pop_blocks*/);
cp.blockchain_detached(1 /*height*/);
checkpoint_t top_checkpoint;
ASSERT_FALSE(test_db->get_top_checkpoint(top_checkpoint));
}

View File

@ -68,8 +68,8 @@ TEST(notify, works)
#endif
+ " " + name_template + " %s";
tools::Notify notify(spec.c_str());
notify.notify("%s", "1111111111111111111111111111111111111111111111111111111111111111", NULL);
tools::Notify notify(spec);
notify.notify("%s", "1111111111111111111111111111111111111111111111111111111111111111");
bool ok = false;
for (int i = 0; i < 10; ++i)