diff --git a/CMakeLists.txt b/CMakeLists.txt index 3088734..8797a4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(p2pool) +project(p2pool-salvium) include(cmake/standard.cmake) diff --git a/sidechain_config.json b/sidechain_config.json index 396e53c..5c49a7d 100644 --- a/sidechain_config.json +++ b/sidechain_config.json @@ -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 -} \ No newline at end of file +} diff --git a/src/block_template.cpp b/src/block_template.cpp index 88fdeed..a073db0 100644 --- a/src/block_template.cpp +++ b/src/block_template.cpp @@ -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; diff --git a/src/common.h b/src/common.h index 8bf011b..1d70da9 100644 --- a/src/common.h +++ b/src/common.h @@ -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; diff --git a/src/log.h b/src/log.h index 469ccac..95eebf4 100644 --- a/src/log.h +++ b/src/log.h @@ -400,15 +400,15 @@ template<> struct log::Stream::Entry { 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); } diff --git a/src/main.cpp b/src/main.cpp index 5c9aa54..66c8e1b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(p2pool::DEFAULT_STRATUM_BAN_TIME), p2pool::log::MAX_GLOBAL_LOG_LEVEL, diff --git a/src/p2p_server.cpp b/src/p2p_server.cpp index edd8f78..2122580 100644 --- a/src/p2p_server.cpp +++ b/src/p2p_server.cpp @@ -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() << "********************************************************************************"); } } diff --git a/src/p2p_server.h b/src/p2p_server.h index e424ad0..88dc24f 100644 --- a/src/p2p_server.h +++ b/src/p2p_server.h @@ -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; diff --git a/src/params.cpp b/src/params.cpp index 9d2f6ff..eac9efe 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -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]; diff --git a/src/params.h b/src/params.h index 257b39b..08e5c1f 100644 --- a/src/params.h +++ b/src/params.h @@ -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) diff --git a/src/pool_block.h b/src/pool_block.h index 12460b9..7497e0c 100644 --- a/src/pool_block.h +++ b/src/pool_block.h @@ -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 }; diff --git a/src/wallet.cpp b/src/wallet.cpp index de9fb14..fb99063 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -22,6 +22,7 @@ #include "wallet.h" #include "keccak.h" #include "crypto.h" +#include 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 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(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(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(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(addr_ptr[j])]; + if (digit < 0) { + return false; + } - address += block_sizes.back(); + uint64_t hi; + const uint64_t tmp = num + umul128(order, static_cast(digit), &hi); + if ((tmp < num) || hi) { + return false; + } - for (int j = static_cast((i < num_full_blocks) ? sizeof(num) : last_block_size_index) - 1; j >= 0; --j) { - data[data_index++] = static_cast(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((i < actual_num_full_blocks) ? sizeof(num) : actual_last_block_size_index) - 1; j >= 0; --j) { + data[data_index++] = static_cast(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(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) diff --git a/src/wallet.h b/src/wallet.h index ae8ba2f..99e7f36 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -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); diff --git a/src/zmq_reader.cpp b/src/zmq_reader.cpp index 18ce565..330e8c0 100644 --- a/src/zmq_reader.cpp +++ b/src/zmq_reader.cpp @@ -348,47 +348,83 @@ static std::vector 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 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 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 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 t;