Skip to content

Commit 7792cfc

Browse files
committed
fix confirmed spender height lookup
return a terminal height when a prevout has no confirmed spender, instead of zero. previously, unspent outputs could get a height of zero which implies they were spent by the genesis block. more specifically, outputs that are unspent because the spending transaction was not confirmed would get marked as already spent by the genesis block when attempting to broadcast the transaction. also check the computed block link when short-circuiting find_strong(tx_link), so unconfirmed duplicate tx links fall back to hash-based strong lookup instead of returning a terminal block link.
1 parent 7ad4548 commit 7792cfc

2 files changed

Lines changed: 86 additions & 3 deletions

File tree

include/bitcoin/database/impl/query/consensus/consensus_strong.ipp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ height_link CLASS::find_strong_spender_height(
7272
size_t out{};
7373
for (const auto& in: to_spenders(point))
7474
if (const auto tx = to_input_tx(in); get_tx_height(out, tx))
75-
break;
75+
return { system::possible_narrow_cast<height_link::integer>(out) };
7676

77-
return { system::possible_narrow_cast<height_link::integer>(out) };
77+
return {};
7878
}
7979

8080
// find_strong (block)
@@ -84,7 +84,7 @@ TEMPLATE
8484
header_link CLASS::find_strong(const tx_link& link) const NOEXCEPT
8585
{
8686
// Shortcuircuit hash-based search by testing self.
87-
if (const auto fk = to_block(link); !link.is_terminal())
87+
if (const auto fk = to_block(link); !fk.is_terminal())
8888
return fk;
8989

9090
return find_strong(get_tx_key(link));

test/query/confirmed.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,89 @@ BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_tx__confirm__expected)
144144
BOOST_REQUIRE(query.is_confirmed_tx(2));
145145
}
146146

147+
BOOST_AUTO_TEST_CASE(query_confirmed__find_strong__unconfirmed_duplicate__expected)
148+
{
149+
settings settings{};
150+
settings.path = TEST_DIRECTORY;
151+
test::chunk_store store{ settings };
152+
test::query_accessor query{ store };
153+
BOOST_REQUIRE(!store.create(test::events_handler));
154+
BOOST_REQUIRE(query.initialize(test::genesis));
155+
BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, false));
156+
BOOST_REQUIRE(query.set_strong(1));
157+
158+
BOOST_REQUIRE(query.set(test::tx4));
159+
const auto unconfirmed = query.to_tx(test::tx4.hash(false));
160+
161+
BOOST_REQUIRE(query.set(test::block_spend_1a, context{ 0, 2, 0 }, false, false));
162+
BOOST_REQUIRE(query.set_strong(2));
163+
164+
BOOST_REQUIRE_EQUAL(query.find_strong(unconfirmed),
165+
query.to_header(test::block_spend_1a.hash()));
166+
}
167+
168+
BOOST_AUTO_TEST_CASE(query_confirmed__find_strong_spender_height__unspent__terminal)
169+
{
170+
settings settings{};
171+
settings.path = TEST_DIRECTORY;
172+
test::chunk_store store{ settings };
173+
test::query_accessor query{ store };
174+
BOOST_REQUIRE(!store.create(test::events_handler));
175+
BOOST_REQUIRE(query.initialize(test::genesis));
176+
BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, false));
177+
BOOST_REQUIRE(query.set_strong(1));
178+
179+
const system::chain::point point
180+
{
181+
test::block1a.transactions_ptr()->front()->hash(false), 0
182+
};
183+
184+
BOOST_REQUIRE(query.find_strong_spender_height(point).is_terminal());
185+
}
186+
187+
BOOST_AUTO_TEST_CASE(query_confirmed__find_strong_spender_height__unconfirmed__terminal)
188+
{
189+
settings settings{};
190+
settings.path = TEST_DIRECTORY;
191+
test::chunk_store store{ settings };
192+
test::query_accessor query{ store };
193+
BOOST_REQUIRE(!store.create(test::events_handler));
194+
BOOST_REQUIRE(query.initialize(test::genesis));
195+
BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, false));
196+
BOOST_REQUIRE(query.set_strong(1));
197+
BOOST_REQUIRE(query.set(test::tx5));
198+
199+
const system::chain::point point
200+
{
201+
test::block1a.transactions_ptr()->front()->hash(false), 0
202+
};
203+
204+
BOOST_REQUIRE(query.find_strong_spender_height(point).is_terminal());
205+
}
206+
207+
BOOST_AUTO_TEST_CASE(query_confirmed__find_strong_spender_height__confirmed__expected)
208+
{
209+
settings settings{};
210+
settings.path = TEST_DIRECTORY;
211+
test::chunk_store store{ settings };
212+
test::query_accessor query{ store };
213+
BOOST_REQUIRE(!store.create(test::events_handler));
214+
BOOST_REQUIRE(query.initialize(test::genesis));
215+
BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, false));
216+
BOOST_REQUIRE(query.set_strong(1));
217+
BOOST_REQUIRE(query.set(test::block_spend_1a, context{ 0, 2, 0 }, false, false));
218+
BOOST_REQUIRE(query.set_strong(2));
219+
220+
const system::chain::point point
221+
{
222+
test::block1a.transactions_ptr()->front()->hash(false), 0
223+
};
224+
225+
const auto height = query.find_strong_spender_height(point);
226+
BOOST_REQUIRE(!height.is_terminal());
227+
BOOST_REQUIRE_EQUAL(height.value, 2u);
228+
}
229+
147230
BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_input__genesis__true)
148231
{
149232
settings settings{};

0 commit comments

Comments
 (0)