Port P2Pool to Salvium

This commit is contained in:
Matt Hess
2025-11-12 15:20:13 +00:00
parent 0f5f0e8386
commit 62c654f77c
14 changed files with 226 additions and 132 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(p2pool)
project(p2pool-salvium)
include(cmake/standard.cmake)
+2 -2
View File
@@ -5,10 +5,10 @@
// Fixed difficulty for miners that connect to your node must be set in their config.json
//
{
"name": "default",
"name": "salvium_main",
"password": "",
"block_time": 10,
"min_diff": 100000,
"pplns_window": 2160,
"uncle_penalty": 20
}
}
+16 -3
View File
@@ -166,8 +166,18 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
static FORCEINLINE uint64_t get_base_reward(uint64_t already_generated_coins)
{
const uint64_t result = ~already_generated_coins >> 19;
return (result < BASE_BLOCK_REWARD) ? BASE_BLOCK_REWARD : result;
// Salvium emission formula
if (already_generated_coins == 0) {
return PREMINE_AMOUNT;
}
uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> EMISSION_SPEED_FACTOR;
if (base_reward < BASE_BLOCK_REWARD) {
base_reward = BASE_BLOCK_REWARD;
}
return base_reward;
}
static FORCEINLINE uint64_t get_block_reward(uint64_t base_reward, uint64_t median_weight, uint64_t fees, uint64_t weight)
@@ -332,7 +342,10 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
select_mempool_transactions(mempool);
const uint64_t base_reward = get_base_reward(data.already_generated_coins);
uint64_t base_reward = get_base_reward(data.already_generated_coins);
// Salvium: 20% goes to staking, miners get 80%
base_reward = (base_reward * 4) / 5; // 80% of total reward
uint64_t total_tx_fees = 0;
uint64_t total_tx_weight = 0;
+1
View File
@@ -125,6 +125,7 @@ constexpr uint8_t EXTRA_NONCE_MAX_SIZE = EXTRA_NONCE_SIZE + 10;
constexpr uint8_t TX_VERSION = 2;
constexpr uint8_t TXIN_GEN = 0xFF;
constexpr uint8_t TXOUT_TO_TAGGED_KEY = 3;
constexpr uint8_t TXOUT_TO_CARROT_V1 = 4;
constexpr uint8_t TX_EXTRA_TAG_PUBKEY = 1;
constexpr uint8_t TX_EXTRA_NONCE = 2;
constexpr uint8_t TX_EXTRA_MERGE_MINING_TAG = 3;
+3 -3
View File
@@ -400,15 +400,15 @@ template<> struct log::Stream::Entry<XMRAmount>
{
static NOINLINE void put(XMRAmount value, Stream* wrapper)
{
constexpr uint64_t denomination = 1000000000000ULL;
constexpr uint64_t denomination = 100000000ULL;
const int w = wrapper->getNumberWidth();
wrapper->setNumberWidth(1);
*wrapper << value.m_data / denomination << '.';
wrapper->setNumberWidth(12);
*wrapper << value.m_data % denomination << " XMR";
wrapper->setNumberWidth(8);
*wrapper << value.m_data % denomination << " SAL";
wrapper->setNumberWidth(w);
}
+11 -11
View File
@@ -58,13 +58,13 @@
void p2pool_usage()
{
printf("P2Pool %s\n"
printf("P2Pool-Salvium %s\n"
"\nUsage:\n\n" \
"--wallet Main wallet address (the one that starts with 4...). To mine to a subaddress of this wallet, use it together with --subaddress\n"
"--wallet Main wallet address (the one that starts with SC1...). To mine to a subaddress of this wallet, use it together with --subaddress\n"
"--subaddress Subaddress to mine to. It must belong to the same wallet that was specified with --wallet parameter\n"
"--host IP address of your Monero node, default is 127.0.0.1\n"
"--rpc-port monerod RPC API port number, default is 18081\n"
"--zmq-port monerod ZMQ pub port number, default is 18083 (same port as in monerod's \"--zmq-pub\" command line parameter)\n"
"--host IP address of your Salvium node, default is 127.0.0.1\n"
"--rpc-port salviumd RPC API port number, default is 19081\n"
"--zmq-port salviumd ZMQ pub port number, default is 19083 (same port as in salviumd's \"--zmq-pub\" command line parameter)\n"
"--stratum Comma-separated list of IP:port for stratum server to listen on\n"
"--p2p Comma-separated list of IP:port for p2p server to listen on\n"
"--addpeers Comma-separated list of IP:port of other p2pool nodes to connect to\n"
@@ -79,7 +79,7 @@ void p2pool_usage()
"--no-cache Disable p2pool.cache\n"
"--no-color Disable colors in console output\n"
#ifdef WITH_RANDOMX
"--no-randomx Disable internal RandomX hasher: p2pool will use RPC calls to monerod to check PoW hashes\n"
"--no-randomx Disable internal RandomX hasher: p2pool will use RPC calls to salviumd to check PoW hashes\n"
#endif
"--out-peers N Maximum number of outgoing connections for p2p server (any value between 10 and 450)\n"
"--in-peers N Maximum number of incoming connections for p2p server (any value between 10 and 450)\n"
@@ -87,7 +87,7 @@ void p2pool_usage()
"--mini Connect to p2pool-mini sidechain. Note that it will also change default p2p port from %d to %d\n"
"--nano Connect to p2pool-nano sidechain. Note that it will also change default p2p port from %d to %d\n"
"--no-autodiff Disable automatic difficulty adjustment for miners connected to stratum (WARNING: incompatible with Nicehash and MRR)\n"
"--rpc-login Specify username[:password] required for Monero RPC server\n"
"--rpc-login Specify username[:password] required for Salvium RPC server\n"
"--socks5 Specify IP:port of a SOCKS5 proxy to use for outgoing connections\n"
"--no-dns Disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too)\n"
"--p2p-external-port Port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections\n"
@@ -96,13 +96,13 @@ void p2pool_usage()
"--no-igd An alias for --no-upnp\n"
"--upnp-stratum Port forward Stratum port (it's not forwarded by default)\n"
#endif
"--merge-mine IP:port and wallet address for another blockchain to merge mine with\n"
// "--merge-mine IP:port and wallet address for another blockchain to merge mine with\n"
"--version Print p2pool's version and build details\n"
#ifdef WITH_TLS
"--tls-cert file Load TLS certificate chain from \"file\" in the PEM format\n"
"--tls-cert-key file Load TLS certificate private key from \"file\" in the PEM format\n"
"--rpc-ssl Enable SSL on RPC connections to the Monero node\n"
"--rpc-ssl-fingerprint base64-encoded fingerprint of the Monero node's certificate (optional, use it for certificate pinning)\n"
"--rpc-ssl Enable SSL on RPC connections to the Salvium node\n"
"--rpc-ssl-fingerprint base64-encoded fingerprint of the Salvium node's certificate (optional, use it for certificate pinning)\n"
#endif
"--no-stratum-http Disable HTTP on Stratum ports\n"
"--full-validation Enables full share validation / increases CPU usage\n"
@@ -110,7 +110,7 @@ void p2pool_usage()
"--no-clearnet-p2p Forces P2P server to listen on 127.0.0.1 and to not connect to clearnet IPs\n"
"--help Show this help message\n\n"
"Example command line:\n\n"
"%s --host 127.0.0.1 --rpc-port 18081 --zmq-port 18083 --wallet YOUR_WALLET_ADDRESS --stratum 0.0.0.0:%d --p2p 0.0.0.0:%d\n\n",
"%s-salvium --host 127.0.0.1 --rpc-port 19081 --zmq-port 19083 --wallet YOUR_WALLET_ADDRESS --stratum 0.0.0.0:%d --p2p 0.0.0.0:%d\n\n",
p2pool::VERSION,
static_cast<uint32_t>(p2pool::DEFAULT_STRATUM_BAN_TIME),
p2pool::log::MAX_GLOBAL_LOG_LEVEL,
+7 -7
View File
@@ -48,9 +48,9 @@ LOG_CATEGORY(P2PServer)
static constexpr char saved_peer_list_file_name[] = "p2pool_peers.txt";
static constexpr char saved_onion_peer_list_file_name[] = "p2pool_onion_peers.txt";
static const char* seed_nodes[] = { "seeds.p2pool.io", "main.p2poolpeers.net", "main.gupax.io", "" };
static const char* seed_nodes_mini[] = { "seeds-mini.p2pool.io", "mini.p2poolpeers.net", "mini.gupax.io", "" };
static const char* seed_nodes_nano[] = { "seeds-nano.p2pool.io", "nano.p2poolpeers.net", "nano.gupax.io", ""};
static const char* seed_nodes[] = { "" };
static const char* seed_nodes_mini[] = { "" };
static const char* seed_nodes_nano[] = { "" };
static constexpr int DEFAULT_BACKLOG = 16;
static constexpr uint64_t DEFAULT_BAN_TIME = 600;
@@ -416,7 +416,7 @@ void P2PServer::update_peer_connections()
m_seenGoodPeers = true;
}
else if (!m_peerListMonero.empty()) {
LOGINFO(3, "Scanning monerod peers, " << m_peerListMonero.size() << " left");
LOGINFO(3, "Scanning salviumd peers, " << m_peerListMonero.size() << " left");
for (uint32_t i = 0; (i < 25) && !m_peerListMonero.empty(); ++i) {
peer_list.push_back(m_peerListMonero.back());
m_peerListMonero.pop_back();
@@ -473,7 +473,7 @@ void P2PServer::update_peer_connections()
}
if (!has_good_peers && ((m_timerCounter % 10) == 0) && (SideChain::network_type() == NetworkType::Mainnet)) {
LOGERR(1, "no connections to other p2pool nodes, check your monerod/p2pool/network/firewall setup!!!");
LOGERR(1, "no connections to other p2pool nodes, check your salviumd/p2pool/network/firewall setup!!!");
load_peer_list();
if (m_peerListMonero.empty()) {
load_monerod_peer_list();
@@ -866,7 +866,7 @@ void P2PServer::load_monerod_peer_list()
// Put recently active peers last in the list (it will be scanned backwards)
std::sort(m_peerListMonero.begin(), m_peerListMonero.end(), [](const Peer& a, const Peer& b) { return a.m_lastSeen < b.m_lastSeen; });
LOGINFO(4, "monerod peer list loaded (" << m_peerListMonero.size() << " peers)");
LOGINFO(4, "salviumd peer list loaded (" << m_peerListMonero.size() << " peers)");
},
[](const char* data, size_t size, double)
{
@@ -1504,7 +1504,7 @@ void P2PServer::check_for_updates(bool forced) const
if ((forced || s.precalcFinished()) && m_newP2PoolVersionDetected && s.p2pool_update_available()) {
LOGINFO(0, log::LightCyan() << "********************************************************************************");
LOGINFO(0, log::LightCyan() << "* An updated P2Pool version is available, visit p2pool.io for more information *");
LOGINFO(0, log::LightCyan() << "* An updated P2Pool-salvium version is available, visit whiskymine.io for more information *");
LOGINFO(0, log::LightCyan() << "********************************************************************************");
}
}
+4 -4
View File
@@ -32,11 +32,11 @@ static constexpr uint64_t P2P_BUF_SIZE = MAX_BLOCK_SIZE + (1 + sizeof(uint32_t))
static_assert((P2P_BUF_SIZE & (P2P_BUF_SIZE - 1)) == 0, "P2P_BUF_SIZE is not a power of 2, fix MAX_BLOCK_SIZE");
static constexpr size_t PEER_LIST_RESPONSE_MAX_PEERS = 16;
static constexpr int DEFAULT_P2P_PORT = 37889;
static constexpr int DEFAULT_P2P_PORT_MINI = 37888;
static constexpr int DEFAULT_P2P_PORT_NANO = 37890;
static constexpr int DEFAULT_P2P_PORT = 38889;
static constexpr int DEFAULT_P2P_PORT_MINI = 38888;
static constexpr int DEFAULT_P2P_PORT_NANO = 38890;
static constexpr int DEFAULT_P2P_PORT_ONION = 28722;
static constexpr int DEFAULT_P2P_PORT_ONION = 29722;
static constexpr uint32_t PROTOCOL_VERSION_1_0 = 0x00010000UL;
static constexpr uint32_t PROTOCOL_VERSION_1_1 = 0x00010001UL;
+3
View File
@@ -243,12 +243,15 @@ Params::Params(int argc, char* const argv[])
}
#endif
/*
if ((strcmp(argv[i], "--merge-mine") == 0) && (i + 2 < argc)) {
m_mergeMiningHosts.emplace_back(argv[i + 1], argv[i + 2]);
i += 2;
ok = true;
}
*/
#ifdef WITH_TLS
if ((strcmp(argv[i], "--tls-cert") == 0) && (i + 1 < argc)) {
m_tlsCert = argv[++i];
+1 -1
View File
@@ -35,7 +35,7 @@ struct Params
struct Host
{
Host() : m_address("127.0.0.1"), m_rpcPort(18081), m_zmqPort(18083), m_rpcSSL(false) {}
Host() : m_address("127.0.0.1"), m_rpcPort(19081), m_zmqPort(19083), m_rpcSSL(false) {}
Host(const char* address, int32_t rpcPort, int32_t zmqPort, const char* rpcLogin)
: m_address(address)
+9 -2
View File
@@ -53,8 +53,15 @@ struct MinerShare;
// 128 KB minus BLOCK_RESPONSE P2P protocol header (5 bytes)
static constexpr uint64_t MAX_BLOCK_SIZE = 128 * 1024 - 5;
// 0.6 XMR
static constexpr uint64_t BASE_BLOCK_REWARD = 600000000000ULL;
// Salvium emission constants (from cryptonote_config.h)
static constexpr uint64_t MONEY_SUPPLY = 18440000000000000ULL; // 184.4M * 10^8
static constexpr uint64_t FINAL_SUBSIDY_PER_MINUTE = 30000000ULL; // 0.3 SAL
static constexpr uint64_t PREMINE_AMOUNT = 2210000000000000ULL;
static constexpr int EMISSION_SPEED_FACTOR_PER_MINUTE = 21;
static constexpr int DIFFICULTY_TARGET_V2 = 120; // 2 minutes
static constexpr int TARGET_MINUTES = DIFFICULTY_TARGET_V2 / 60; // 2
static constexpr int EMISSION_SPEED_FACTOR = EMISSION_SPEED_FACTOR_PER_MINUTE - (TARGET_MINUTES - 1); // 20
static constexpr uint64_t BASE_BLOCK_REWARD = FINAL_SUBSIDY_PER_MINUTE * TARGET_MINUTES; // Tail emission
// 1000 years at 1 TH/s. It should be enough for any normal use.
static constexpr difficulty_type MAX_CUMULATIVE_DIFFICULTY{ 13019633956666736640ULL, 1710ULL };
+97 -60
View File
@@ -22,6 +22,7 @@
#include "wallet.h"
#include "keccak.h"
#include "crypto.h"
#include <ios>
extern "C" {
#include "crypto-ops.h"
@@ -35,9 +36,9 @@ namespace {
// Allow only regular addresses (no integrated addresses, no subaddresses)
// Values taken from cryptonote_config.h (CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX)
constexpr uint64_t valid_prefixes[] = { 18, 53, 24 };
constexpr uint64_t valid_prefixes_subaddress[] = { 42, 63, 36 };
constexpr uint64_t valid_prefixes[] = { 0x180c96, 0x254c96, 0x24cc96 }; // SC1, SC1T, SC1S (mainnet, testnet, stagenet)
constexpr uint64_t valid_prefixes_subaddress[] = { 0x314c96, 0x3c54c96, 0x384cc96 }; // SC1s, SC1Ts, SC1Ss
constexpr std::array<int, 9> block_sizes{ 0, 2, 3, 5, 6, 7, 9, 10, 11 };
constexpr int num_full_blocks = p2pool::Wallet::ADDRESS_LENGTH / block_sizes.back();
@@ -113,84 +114,120 @@ Wallet& Wallet::operator=(const Wallet& w)
bool Wallet::decode(const char* address)
{
m_type = NetworkType::Invalid;
m_type = NetworkType::Invalid;
if (!address || (strlen(address) != ADDRESS_LENGTH)) {
return false;
}
if (!address) {
return false;
}
constexpr int last_block_size_index = block_sizes_lookup[last_block_size];
const size_t addr_len = strlen(address);
if (addr_len < 97 || addr_len > 98) {
return false;
}
static_assert(last_block_size_index >= 0, "Check ADDRESS_LENGTH");
// Calculate based on actual address length
const int actual_num_full_blocks = addr_len / block_sizes.back();
const int actual_last_block_size = addr_len % block_sizes.back();
const int actual_last_block_size_index = block_sizes_lookup[actual_last_block_size];
uint8_t data[static_cast<size_t>(num_full_blocks) * sizeof(uint64_t) + last_block_size_index] = {};
int data_index = 0;
if (actual_last_block_size_index < 0) {
return false;
}
for (int i = 0; i <= num_full_blocks; ++i) {
uint64_t num = 0;
uint64_t order = 1;
uint8_t data[73] = {};
int data_index = 0;
for (int j = ((i < num_full_blocks) ? block_sizes.back() : last_block_size) - 1; j >= 0; --j) {
const int8_t digit = rev_alphabet.data[static_cast<uint8_t>(address[j])];
if (digit < 0) {
return false;
}
const char* addr_ptr = address; // Use separate pointer for iteration
uint64_t hi;
const uint64_t tmp = num + umul128(order, static_cast<uint64_t>(digit), &hi);
if ((tmp < num) || hi) {
return false;
}
for (int i = 0; i <= actual_num_full_blocks; ++i) {
uint64_t num = 0;
uint64_t order = 1;
num = tmp;
order *= alphabet_size;
}
for (int j = ((i < actual_num_full_blocks) ? block_sizes.back() : actual_last_block_size) - 1; j >= 0; --j) {
const int8_t digit = rev_alphabet.data[static_cast<uint8_t>(addr_ptr[j])];
if (digit < 0) {
return false;
}
address += block_sizes.back();
uint64_t hi;
const uint64_t tmp = num + umul128(order, static_cast<uint64_t>(digit), &hi);
if ((tmp < num) || hi) {
return false;
}
for (int j = static_cast<int>((i < num_full_blocks) ? sizeof(num) : last_block_size_index) - 1; j >= 0; --j) {
data[data_index++] = static_cast<uint8_t>(num >> (j * 8));
}
}
num = tmp;
order *= alphabet_size;
}
m_prefix = data[0];
addr_ptr += (i < actual_num_full_blocks) ? block_sizes.back() : actual_last_block_size; // Advance by actual size
switch (m_prefix)
{
case valid_prefixes[0]: m_type = NetworkType::Mainnet; break;
case valid_prefixes[1]: m_type = NetworkType::Testnet; break;
case valid_prefixes[2]: m_type = NetworkType::Stagenet; break;
for (int j = static_cast<int>((i < actual_num_full_blocks) ? sizeof(num) : actual_last_block_size_index) - 1; j >= 0; --j) {
data[data_index++] = static_cast<uint8_t>(num >> (j * 8));
}
}
case valid_prefixes_subaddress[0]: m_type = NetworkType::Mainnet; m_subaddress = true; break;
case valid_prefixes_subaddress[1]: m_type = NetworkType::Testnet; m_subaddress = true; break;
case valid_prefixes_subaddress[2]: m_type = NetworkType::Stagenet; m_subaddress = true; break;
// Decode varint tag from start of data
uint64_t tag = 0;
int varint_len = 0;
for (int i = 0; i < 8; ++i) {
tag |= static_cast<uint64_t>(data[i] & 0x7F) << (i * 7);
++varint_len;
if ((data[i] & 0x80) == 0) {
break;
}
}
default:
return false;
}
char hex_buf[32];
snprintf(hex_buf, sizeof(hex_buf), "0x%lx", tag);
memcpy(m_spendPublicKey.h, data + 1, HASH_SIZE);
memcpy(m_viewPublicKey.h, data + 1 + HASH_SIZE, HASH_SIZE);
memcpy(&m_checksum, data + 1 + HASH_SIZE * 2, sizeof(m_checksum));
m_prefix = tag;
uint8_t md[200];
keccak(data, sizeof(data) - sizeof(m_checksum), md);
switch (m_prefix)
{
case valid_prefixes[0]: m_type = NetworkType::Mainnet; break;
case valid_prefixes[1]: m_type = NetworkType::Testnet; break;
case valid_prefixes[2]: m_type = NetworkType::Stagenet; break;
if (memcmp(&m_checksum, md, sizeof(m_checksum)) != 0) {
m_type = NetworkType::Invalid;
}
case valid_prefixes_subaddress[0]: m_type = NetworkType::Mainnet; m_subaddress = true; break;
case valid_prefixes_subaddress[1]: m_type = NetworkType::Testnet; m_subaddress = true; break;
case valid_prefixes_subaddress[2]: m_type = NetworkType::Stagenet; m_subaddress = true; break;
ge_p3 point;
if ((ge_frombytes_vartime(&point, m_spendPublicKey.h) != 0) || (ge_frombytes_vartime(&point, m_viewPublicKey.h) != 0)) {
m_type = NetworkType::Invalid;
}
default:
return false;
}
if (!torsion_check()) {
LOGWARN(1, "Torsion check failed for wallet " << *this << "! It will not be compatible with FCMP++.");
// TODO: add "m_type = NetworkType::Invalid;" and return false in a later release, closer to FCMP++ hardfork
}
memcpy(m_spendPublicKey.h, data + varint_len, HASH_SIZE);
memcpy(m_viewPublicKey.h, data + varint_len + HASH_SIZE, HASH_SIZE);
return valid();
// Load checksum from correct position (at end of decoded data)
memcpy(&m_checksum, data + data_index - sizeof(m_checksum), sizeof(m_checksum));
uint8_t md[200];
keccak(data, data_index - sizeof(m_checksum), md);
uint32_t calculated_checksum;
memcpy(&calculated_checksum, md, sizeof(calculated_checksum));
if (m_checksum != calculated_checksum) {
LOGINFO(1, "Checksum FAILED");
m_type = NetworkType::Invalid;
}
if (memcmp(&m_checksum, md, sizeof(m_checksum)) != 0) {
LOGINFO(1, "Checksum FAILED");
m_type = NetworkType::Invalid;
}
ge_p3 point;
if ((ge_frombytes_vartime(&point, m_spendPublicKey.h) != 0) || (ge_frombytes_vartime(&point, m_viewPublicKey.h) != 0)) {
m_type = NetworkType::Invalid;
}
if (!torsion_check()) {
LOGWARN(1, "Torsion check failed for wallet " << *this << "! It will not be compatible with FCMP++.");
}
return valid();
}
bool Wallet::assign(const hash& spend_pub_key, const hash& view_pub_key, NetworkType type, bool subaddress)
+1 -4
View File
@@ -24,10 +24,7 @@ namespace p2pool {
class Wallet
{
public:
// public keys: 64 bytes -> 88 characters in base58
// prefix (1 byte) + checksum (4 bytes) -> 7 characters in base58
// 95 characters in total
static constexpr int ADDRESS_LENGTH = 95;
static constexpr int ADDRESS_LENGTH = 97;
explicit Wallet(const char* address);
+70 -34
View File
@@ -348,47 +348,83 @@ static std::vector<uint8_t> construct_monero_block_blob(rapidjson::Value* value,
writeVarint(arr.Size(), blob);
for (auto* i = arr.begin(); i != arr.end(); ++i) {
auto amount = i->FindMember("amount");
if ((amount == i->MemberEnd()) || !amount->value.IsUint64()) {
LOGWARN(3, "construct_monero_block_blob: amount not found or is not UInt64");
return empty_blob;
}
auto to_tagged_key = i->FindMember("to_tagged_key");
if ((to_tagged_key == i->MemberEnd()) || !to_tagged_key->value.IsObject()) {
LOGWARN(3, "construct_monero_block_blob: to_tagged_key not found or is not an object");
return empty_blob;
}
// Salvium uses target.carrot_v1 instead of to_tagged_key
auto target = i->FindMember("target");
if ((target == i->MemberEnd()) || !target->value.IsObject()) {
// LOGWARN(3, "construct_salvium_block_blob: target not found or is not an object");
continue;
}
auto carrot_v1 = target->value.FindMember("carrot_v1");
if ((carrot_v1 == target->value.MemberEnd()) || !carrot_v1->value.IsObject()) {
LOGWARN(3, "construct_salvium_block_blob: carrot_v1 not found or is not an object");
return empty_blob;
}
auto key = carrot_v1->value.FindMember("key");
if ((key == carrot_v1->value.MemberEnd()) || !key->value.IsString()) {
LOGWARN(3, "construct_salvium_block_blob: key not found or is not a string");
return empty_blob;
}
auto asset_type = carrot_v1->value.FindMember("asset_type");
if ((asset_type == carrot_v1->value.MemberEnd()) || !asset_type->value.IsString()) {
LOGWARN(3, "construct_salvium_block_blob: asset_type not found or is not a string");
return empty_blob;
}
auto view_tag = carrot_v1->value.FindMember("view_tag");
if ((view_tag == carrot_v1->value.MemberEnd()) || !view_tag->value.IsString()) {
LOGWARN(3, "construct_salvium_block_blob: view_tag not found or is not a string");
return empty_blob;
}
auto encrypted_janus_anchor = carrot_v1->value.FindMember("encrypted_janus_anchor");
if ((encrypted_janus_anchor == carrot_v1->value.MemberEnd()) || !encrypted_janus_anchor->value.IsString()) {
LOGWARN(3, "construct_salvium_block_blob: encrypted_janus_anchor not found or is not a string");
return empty_blob;
}
// Serialize: amount + type + key + asset_type + view_tag + encrypted_janus_anchor
writeVarint(amount->value.GetUint64(), blob);
blob.push_back(TXOUT_TO_CARROT_V1);
// Key (32 bytes)
if (!from_hex(key->value.GetString(), key->value.GetStringLength(), h)) {
LOGWARN(3, "construct_salvium_block_blob: invalid key " << key->value.GetString());
return empty_blob;
}
blob.insert(blob.end(), h.h, h.h + HASH_SIZE);
// Asset type (string with length prefix)
const char* asset_str = asset_type->value.GetString();
const size_t asset_len = asset_type->value.GetStringLength();
writeVarint(asset_len, blob);
blob.insert(blob.end(), asset_str, asset_str + asset_len);
// View tag (3 bytes)
std::vector<uint8_t> vt;
if (!from_hex(view_tag->value.GetString(), view_tag->value.GetStringLength(), vt) || (vt.size() != 3)) {
LOGWARN(3, "construct_salvium_block_blob: invalid view_tag " << view_tag->value.GetString());
return empty_blob;
}
blob.insert(blob.end(), vt.begin(), vt.end());
// Encrypted janus anchor (16 bytes)
std::vector<uint8_t> eja;
if (!from_hex(encrypted_janus_anchor->value.GetString(), encrypted_janus_anchor->value.GetStringLength(), eja) || (eja.size() != 16)) {
LOGWARN(3, "construct_salvium_block_blob: invalid encrypted_janus_anchor " << encrypted_janus_anchor->value.GetString());
return empty_blob;
}
blob.insert(blob.end(), eja.begin(), eja.end());
auto key = to_tagged_key->value.FindMember("key");
if ((key == to_tagged_key->value.MemberEnd()) || !key->value.IsString()) {
LOGWARN(3, "construct_monero_block_blob: key not found or is not a string");
return empty_blob;
}
auto view_tag = to_tagged_key->value.FindMember("view_tag");
if ((view_tag == to_tagged_key->value.MemberEnd()) || !view_tag->value.IsString()) {
LOGWARN(3, "construct_monero_block_blob: view_tag not found or is not a string");
return empty_blob;
}
writeVarint(amount->value.GetUint64(), blob);
blob.push_back(TXOUT_TO_TAGGED_KEY);
if (!from_hex(key->value.GetString(), key->value.GetStringLength(), h)) {
LOGWARN(3, "construct_monero_block_blob: invalid key " << key->value.GetString());
return empty_blob;
}
blob.insert(blob.end(), h.h, h.h + HASH_SIZE);
std::vector<uint8_t> t;
if (!from_hex(view_tag->value.GetString(), view_tag->value.GetStringLength(), t) || (t.size() != 1)) {
LOGWARN(3, "construct_monero_block_blob: invalid view_tag " << view_tag->value.GetString());
return empty_blob;
}
blob.push_back(t[0]);
}
std::vector<uint8_t> t;