Port monero-lws to wownero-lws

Adapts monero-lws for Wownero cryptocurrency:

- Rename all monero-lws-* binaries to wownero-lws-*
- Update submodule to point to official Wownero repo
- Use Wownero default ports (RPC: 34568, ZMQ: 34569)
- Update data directory to ~/.wownero/light_wallet_server
- Adapt next_difficulty() calls for Wownero API signature

Key technical changes for Wownero compatibility:

- BulletproofPlus (RCTTypeBulletproofPlus, type 8) commitment verification:
  Wownero stores BP+ commitments in 'divided by 8' form. Must call
  rct::scalarmult8() on outPk commitment before comparing with computed
  commitment (mask*G + amount*H). This is essential for amount decryption.

- Pass rct_type to decode_amount() for proper commitment handling

- Handle Wownero's ZMQ JSON format for ecdhTuple (32-byte mask/amount fields)

No fork of Wownero is required - uses official codeberg.org/wownero/wownero.
This commit is contained in:
jwinterm
2026-01-04 13:12:56 -05:00
parent f2b3534002
commit 3427c60315
35 changed files with 789 additions and 395 deletions

View File

@@ -27,6 +27,7 @@
#include "scanner.h"
#include <algorithm>
#include "string_tools.h"
#include <boost/asio/use_future.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/range/combine.hpp>
@@ -528,7 +529,7 @@ namespace lws
{
const bool bulletproof2 = (rct::RCTTypeBulletproof2 <= tx.rct_signatures.type);
const auto decrypted = lws::decode_amount(
tx.rct_signatures.outPk.at(index).mask, tx.rct_signatures.ecdhInfo.at(index), active_derived, index, bulletproof2
tx.rct_signatures.outPk.at(index).mask, tx.rct_signatures.ecdhInfo.at(index), active_derived, index, bulletproof2, tx.rct_signatures.type
);
if (!decrypted)
{
@@ -743,6 +744,12 @@ namespace lws
if (fetched->blocks.empty())
throw std::runtime_error{"Daemon unexpectedly returned zero blocks"};
MINFO("Processing batch: " << fetched->start_height << " to " << (fetched->start_height + fetched->blocks.size() - 1) << " (" << fetched->blocks.size() << " blocks)");
if (!fetched->blocks.empty())
{
MINFO("First block hash: " << cryptonote::get_block_hash(fetched->blocks.front().block) << ", Last block hash: " << cryptonote::get_block_hash(fetched->blocks.back().block));
}
if (fetched->start_height != req.start_height)
{
MWARNING("Daemon sent wrong blocks, resetting state");
@@ -907,7 +914,7 @@ namespace lws
if (self.stop_)
return false;
diff = cryptonote::next_difficulty(pow_window.pow_timestamps, pow_window.cumulative_diffs, get_target_time(db::block_id(fetched->start_height)));
diff = cryptonote::next_difficulty(pow_window.pow_timestamps, pow_window.cumulative_diffs, get_target_time(db::block_id(fetched->start_height)), std::uint64_t(fetched->start_height), lws::config::network);
// skip POW hashing if done previously
if (disk && last_pow < db::block_id(fetched->start_height))
@@ -963,6 +970,8 @@ namespace lws
reader.reader = std::error_code{common_error::kInvalidArgument}; // cleanup reader before next write
MINFO("Thread " << thread_n << " processed " << blockchain.size() << " blocks(s) @ height " << fetched->start_height << " against " << users.size() << " account(s)");
if (!blockchain.empty())
MINFO("First block hash: " << blockchain.front() << " at height " << (fetched->start_height - blockchain.size() + 1) << ", last block hash: " << blockchain.back() << " at height " << fetched->start_height);
if (!store(self.io_, client, self.webhooks_, epee::to_span(blockchain), epee::to_span(users), epee::to_span(new_pow)))
return false;
@@ -1151,7 +1160,10 @@ namespace lws
for (;;)
{
if (req.known_hashes.empty())
{
MERROR("sync_quick: req.known_hashes is empty");
return {lws::error::bad_blockchain};
}
auto resp = fetch_chain<rpc::get_hashes_fast>(self, client, "get_hashes_fast", req);
if (!resp)
@@ -1163,6 +1175,7 @@ namespace lws
if (resp->hashes.size() <= 1 || resp->hashes.back() == req.known_hashes.front())
return {std::move(client)};
MINFO("Syncing " << resp->hashes.size() << " hashes from height " << resp->start_height);
MONERO_CHECK(disk.sync_chain(db::block_id(resp->start_height), epee::to_span(resp->hashes), regtest));
req.known_hashes.erase(req.known_hashes.begin(), --(req.known_hashes.end()));
@@ -1193,7 +1206,10 @@ namespace lws
for (;;)
{
if (req.block_ids.empty())
{
MERROR("sync_full: req.block_ids is empty");
return {lws::error::bad_blockchain};
}
auto resp = fetch_chain<rpc::get_blocks_fast>(self, client, "get_blocks_fast", req);
if (!resp)
@@ -1204,7 +1220,10 @@ namespace lws
crypto::hash hash{};
if (!cryptonote::get_block_hash(resp->blocks.front().block, hash))
{
MERROR("sync_full: failed to get hash of first block");
return {lws::error::bad_blockchain};
}
//
// exit loop if it appears we have synced to top of chain
@@ -1243,11 +1262,17 @@ namespace lws
// important check, ensure we haven't deviated from chain
if (block.prev_id != hash)
{
MERROR("sync_full: block.prev_id (" << block.prev_id << ") != hash (" << hash << ") at height " << std::uint64_t(height));
return {lws::error::bad_blockchain};
}
// compute block id hash
if (!cryptonote::get_block_hash(block, hash))
{
MERROR("sync_full: failed to get hash of block at height " << std::uint64_t(height));
return {lws::error::bad_blockchain};
}
req.block_ids.push_front(hash);
update_window(pow_window.pow_timestamps);
@@ -1260,7 +1285,7 @@ namespace lws
if (self.has_shutdown())
return {error::signal_abort_process};
diff = cryptonote::next_difficulty(pow_window.pow_timestamps, pow_window.cumulative_diffs, get_target_time(height));
diff = cryptonote::next_difficulty(pow_window.pow_timestamps, pow_window.cumulative_diffs, get_target_time(height), std::uint64_t(height), lws::config::network);
// skip POW hashing when sync is within checkpoint
// storage::sync_pow(...) currently verifies checkpoint hashes