Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/bitcoin/server/interfaces/electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct electrum_methods
method<"blockchain.transaction.broadcast", string_t>{ "raw_tx" },
method<"blockchain.transaction.get", string_t, boolean_t>{ "tx_hash", "verbose" },
method<"blockchain.transaction.get_merkle", string_t, number_t>{ "tx_hash", "height" },
method<"blockchain.transaction.id_from_pos", number_t, number_t, boolean_t>{ "height", "tx_pos", "merkle" },
method<"blockchain.transaction.id_from_pos", number_t, number_t, optional<false>>{ "height", "tx_pos", "merkle" },

/// Server methods.
method<"server.add_peer", object_t>{ "features" },
Expand Down
72 changes: 67 additions & 5 deletions src/protocols/protocol_electrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ void protocol_electrum::blockchain_block_headers(size_t starting,

array_t branch(proof.size());
std::ranges::transform(proof, branch.begin(),
[](const auto& hash) { return encode_hash(hash); });
[](const auto& hash) NOEXCEPT { return encode_hash(hash); });

result["branch"] = std::move(branch);
result["root"] = encode_hash(root);
Expand Down Expand Up @@ -526,11 +526,73 @@ void protocol_electrum::handle_blockchain_transaction_get_merkle(const code& ec,
}

void protocol_electrum::handle_blockchain_transaction_id_from_pos(const code& ec,
rpc_interface::blockchain_transaction_id_from_pos, double ,
double , bool ) NOEXCEPT
rpc_interface::blockchain_transaction_id_from_pos, double height,
double tx_pos, bool merkle) NOEXCEPT
{
if (stopped(ec)) return;
send_code(error::not_implemented);
if (stopped(ec))
return;

size_t position{};
size_t block_height{};
if (!to_integer(block_height, height) ||
!to_integer(position, tx_pos))
{
send_code(error::invalid_argument);
return;
}

const auto& query = archive();
const auto block_link = query.to_confirmed(block_height);
const auto tx_link = query.get_position_tx(block_link, position);
if (tx_link.is_terminal())
{
send_code(error::not_found);
return;
}

using namespace system;
const auto hash = query.get_tx_key(tx_link);
if (hash == null_hash)
{
send_code(error::server_error);
return;
}

if (!merkle)
{
send_result(encode_hash(hash), two * hash_size, BIND(complete, _1));
}
else
{
auto hashes = query.get_tx_keys(block_link);
if (hashes.empty())
{
send_code(error::server_error);
return;
}

if (position >= hashes.size())
{
send_code(error::not_found);
return;
}

using namespace chain;
const auto proof = block::merkle_branch(position, std::move(hashes));

array_t branch(proof.size());
std::ranges::transform(proof, branch.begin(),
[](const auto& hash) NOEXCEPT { return encode_hash(hash); });

send_result(
{
object_t
{
{ "tx_hash", encode_hash(hash) },
{ "merkle", std::move(branch) }
}
}, two * hash_size * add1(branch.size()), BIND(complete, _1));
}
}

// Handlers (server).
Expand Down
67 changes: 59 additions & 8 deletions test/protocols/electrum/electrum_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_get__missing_param__droppe
BOOST_CHECK(handshake());

const auto& coinbase = *genesis.transactions_ptr()->front();
const auto tx_hash = encode_hash(coinbase.hash(false));
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":80,"method":"blockchain.transaction.get","params":["%1%"]})" "\n";
const auto response = get((boost::format(request) % tx_hash).str());
const auto response = get((boost::format(request) % tx0_hash).str());
BOOST_CHECK(response.at("dropped").as_bool());
}

Expand All @@ -109,9 +109,9 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_get__extra_param__dropped)
BOOST_CHECK(handshake());

const auto& coinbase = *genesis.transactions_ptr()->front();
const auto tx_hash = encode_hash(coinbase.hash(false));
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":81,"method":"blockchain.transaction.get","params":["%1%",false,"extra"]})" "\n";
const auto response = get((boost::format(request) % tx_hash).str());
const auto response = get((boost::format(request) % tx0_hash).str());
BOOST_CHECK(response.at("dropped").as_bool());
}

Expand All @@ -120,9 +120,9 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_get__genesis_coinbase_verb
BOOST_CHECK(handshake());

const auto& coinbase = *genesis.transactions_ptr()->front();
const auto tx_hash = encode_hash(coinbase.hash(false));
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":82,"method":"blockchain.transaction.get","params":["%1%",false]})" "\n";
const auto response = get((boost::format(request) % tx_hash).str());
const auto response = get((boost::format(request) % tx0_hash).str());
BOOST_CHECK_EQUAL(response.at("result").as_string(), encode_base16(coinbase.to_data(true)));
}

Expand All @@ -131,9 +131,9 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_get__genesis_coinbase_verb
BOOST_CHECK(handshake());

const auto& coinbase = *genesis.transactions_ptr()->front();
const auto tx_hash = encode_hash(coinbase.hash(false));
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":83,"method":"blockchain.transaction.get","params":["%1%",true]})" "\n";
const auto response = get((boost::format(request) % tx_hash).str());
const auto response = get((boost::format(request) % tx0_hash).str());

auto expected = value_from(bitcoind(coinbase));
BOOST_CHECK(expected.is_object());
Expand All @@ -150,6 +150,57 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_get__genesis_coinbase_verb
}

// blockchain.transaction.get_merkle

// blockchain.transaction.id_from_pos

BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_id_from_pos__genesis_coinbase_default__expected)
{
BOOST_CHECK(handshake());

const auto& coinbase = *genesis.transactions_ptr()->front();
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":90,"method":"blockchain.transaction.id_from_pos","params":[0,0]})" "\n";
const auto response = get(request);
BOOST_CHECK_EQUAL(response.at("result").as_string(), tx0_hash);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_id_from_pos__coinbase_false__expected)
{
BOOST_CHECK(handshake());

const auto& coinbase = *block2.transactions_ptr()->front();
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":91,"method":"blockchain.transaction.id_from_pos","params":[2,0,false]})" "\n";
const auto response = get(request);
BOOST_CHECK_EQUAL(response.at("result").as_string(), tx0_hash);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_id_from_pos__merkle_proof_one_tx__empty)
{
BOOST_CHECK(handshake());

const auto& coinbase = *block9.transactions_ptr()->front();
const auto tx0_hash = encode_hash(coinbase.hash(false));
const auto request = R"({"id":92,"method":"blockchain.transaction.id_from_pos","params":[9,0,true]})" "\n";
const auto& object = get(request).at("result").as_object();
BOOST_CHECK_EQUAL(object.at("tx_hash").as_string(), tx0_hash);
BOOST_CHECK(object.at("merkle").as_array().empty());
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_id_from_pos__missing_block__not_found)
{
BOOST_CHECK(handshake());

const auto request = R"({"id":93,"method":"blockchain.transaction.id_from_pos","params":[11,0]})" "\n";
BOOST_CHECK_EQUAL(get(request).at("error").as_object().at("code").as_int64(), not_found.value());
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_transaction_id_from_pos__missing_position__not_found)
{
BOOST_CHECK(handshake());

const auto request = R"({"id":94,"method":"blockchain.transaction.id_from_pos","params":[0,1]})" "\n";
BOOST_CHECK_EQUAL(get(request).at("error").as_object().at("code").as_int64(), not_found.value());
}

BOOST_AUTO_TEST_SUITE_END()
Loading