1
0
Fork 0
forked from lthn/blockchain

atomics: tests for altchain switching

This commit is contained in:
cryptozoidberg 2021-02-28 23:36:38 +01:00
parent 8fe41c8df2
commit f05bd7a3fb
No known key found for this signature in database
GPG key ID: 22DEB97A54C6FDEC
6 changed files with 294 additions and 9 deletions

View file

@ -1302,6 +1302,68 @@ void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector<s
}
}
//----------------------------------------------------------------------------------------------------
void wallet2::unprocess_htlc_triggers_on_block_removed(uint64_t height)
{
if (!m_htlcs.size())
return;
if (height > m_htlcs.rbegin()->first)
{
//there is no active htlc that at this height
CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size());
return;
}
//we have to check if there is a htlc that has to become deactivated
auto pair_of_it = m_htlcs.equal_range(height);
for (auto it = pair_of_it.first; it != pair_of_it.second; it++)
{
auto& tr = m_transfers[it->second.transfer_index];
//found contract that supposed to be re-activated and set to active
if (it->second.is_wallet_owns_redeem)
{
// this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated
//but we keep spend flag anyway
tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag
tr.m_spent_height = 0;
}
else
{
// this means that wallet created atomic by itself, and second part didn't redeem it,
// so refund money became available, and now we back again to unavailable state
tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag
m_found_free_amounts.clear(); //reset free amounts cache
tr.m_spent_height = 0;
}
//re-add to active contracts
auto pair_key = std::make_pair(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].amount, tr.m_global_output_index);
auto it_active_htlc = m_active_htlcs.find(pair_key);
if (it_active_htlc != m_active_htlcs.end())
{
LOG_ERROR("Error at putting back htlc: already exist?");
it_active_htlc->second = it->second.transfer_index;
}
else
{
m_active_htlcs[pair_key] = it->second.transfer_index;
m_active_htlcs_txid[tr.tx_hash()] = it->second.transfer_index;
}
const crypto::hash tx_id = tr.tx_hash();
auto tx_id_it = m_active_htlcs_txid.find(tx_id);
if (tx_id_it != m_active_htlcs_txid.end())
{
LOG_ERROR("Error at putting back htlc_txid: already exist?");
tx_id_it->second = it->second.transfer_index;
}
else
{
m_active_htlcs_txid[tx_id] = it->second.transfer_index;
}
}
}
void wallet2::process_htlc_triggers_on_block_added(uint64_t height)
{
if (!m_htlcs.size())
@ -1361,7 +1423,7 @@ void wallet2::process_new_blockchain_entry(const currency::block& b, const curre
!(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error,
"current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size()));
process_htlc_triggers_on_block_added(height);
//optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup
@ -1393,6 +1455,8 @@ void wallet2::process_new_blockchain_entry(const currency::block& b, const curre
if (!is_pos_block(b))
m_last_pow_block_h = height;
process_htlc_triggers_on_block_added(height);
m_wcallback->on_new_block(height, b);
}
//----------------------------------------------------------------------------------------------------
@ -2213,6 +2277,10 @@ void wallet2::detach_blockchain(uint64_t including_height)
}
}
for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--)
{
unprocess_htlc_triggers_on_block_removed(i);
}
size_t blocks_detached = detach_from_block_ids(including_height);
//rollback spends
@ -2226,14 +2294,6 @@ void wallet2::detach_blockchain(uint64_t including_height)
WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1);
tr.m_spent_height = 0;
//check if it's hltc contract
if (tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].target.type() == typeid(txout_htlc) && tr.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)
{
//only if htlc was spent as a redeem, then we put htlc back as active
const txout_htlc& htlc = boost::get<txout_htlc>(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].target);
auto amount_gindex_pair = std::make_pair(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].amount, tr.m_global_output_index);
m_active_htlcs[amount_gindex_pair] = i;
m_active_htlcs_txid[tr.tx_hash()] = i;
}
}
}
@ -4035,6 +4095,7 @@ void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_publ
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
origin = ft.htlc_origin;
tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
void wallet2::get_list_of_active_htlc(std::list<wallet_public::htlc_entry_info>& htlcs, bool only_redeem_txs)

View file

@ -802,6 +802,7 @@ namespace tools
const crypto::hash& bl_id,
uint64_t height);
void process_htlc_triggers_on_block_added(uint64_t height);
void unprocess_htlc_triggers_on_block_removed(uint64_t height);
bool get_pos_entries(currency::COMMAND_RPC_SCAN_POS::request& req);
bool build_minted_block(const currency::COMMAND_RPC_SCAN_POS::request& req, const currency::COMMAND_RPC_SCAN_POS::response& rsp, uint64_t new_block_expected_height = UINT64_MAX);
bool build_minted_block(const currency::COMMAND_RPC_SCAN_POS::request& req, const currency::COMMAND_RPC_SCAN_POS::response& rsp, const currency::account_public_address& miner_address, uint64_t new_block_expected_height = UINT64_MAX);

View file

@ -454,3 +454,195 @@ bool atomic_test_wrong_redeem_wrong_refund::c1(currency::core& c, size_t ev_inde
return true;
}
//------------------------------------------------------------------------------
//==============================================================================
//==============================================================================
//==============================================================================
atomic_test_altchain_simple::atomic_test_altchain_simple()
{
REGISTER_CALLBACK_METHOD(atomic_test_altchain_simple, c1);
}
bool atomic_test_altchain_simple::generate(std::vector<test_event_entry>& events) const
{
epee::debug::get_set_enable_assert(true, true);
currency::account_base genesis_acc;
genesis_acc.generate();
m_mining_accunt.generate();
block blk_0 = AUTO_VAL_INIT(blk_0);
generator.construct_genesis_block(blk_0, genesis_acc, test_core_time::get_time());
events.push_back(blk_0);
REWIND_BLOCKS_N(events, blk_0r, blk_0, m_mining_accunt, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5);
DO_CALLBACK(events, "c1");
epee::debug::get_set_enable_assert(true, false);
return true;
}
bool atomic_test_altchain_simple::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
/*
1. Create HTLC proposal
2. Do switch to altchain to the point before HTLC proposal creation (core and wallet)
3. Do redeem in altchain
4. Do switch to altchain to the point before HTLC redeem (core and wallet)
5. Do redeem in altchain
6. Validate correct state of core and wallet
7. Do redeem in altchain
8. Validate state
TODO: play with expirations
TODO: do refund, and then redeem in altchain
*/
LOG_PRINT_MAGENTA("Mining Address: " << currency::get_account_address_as_str(m_mining_accunt.get_public_address()), LOG_LEVEL_0);
//create wallet instances and calculate balances
INIT_RUNTIME_WALLET(alice_a_wlt_instance);
INIT_RUNTIME_WALLET(alice_b_wlt_instance);
#define AMOUNT_TO_TRANSFER_HTLC (TESTS_DEFAULT_FEE*10)
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, m_mining_accunt);
size_t blocks_fetched = 0;
bool received_money = false;
std::atomic<bool> atomic_false = ATOMIC_VAR_INIT(false);
miner_wlt->refresh(blocks_fetched, received_money, atomic_false);
CHECK_AND_FORCE_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool");
uint64_t transfer_amount = AMOUNT_TO_TRANSFER_HTLC + TESTS_DEFAULT_FEE;
miner_wlt->transfer(transfer_amount, alice_a_wlt_instance->get_account().get_public_address());
LOG_PRINT_MAGENTA("Transaction sent to Alice A: " << transfer_amount, LOG_LEVEL_0);
bool r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
std::string alice_origin; //will be deterministically generated by Alice's A wallet
currency::transaction htlc_proposal_tx = AUTO_VAL_INIT(htlc_proposal_tx);
alice_a_wlt_instance->create_htlc_proposal(transfer_amount - TESTS_DEFAULT_FEE, alice_b_wlt_instance->get_account().get_public_address(), 12, htlc_proposal_tx, currency::null_hash, alice_origin);
crypto::hash split_id = c.get_blockchain_storage().get_top_block_id();
uint64_t split_height = c.get_blockchain_storage().get_top_block_height();
//forward blockchain to create redeem transaction
r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
//memorize block id at split height before split to make sure split happened
crypto::hash id_first_splited_block = c.get_blockchain_storage().get_block_id_by_height(split_height + 1);
//validate state of a
std::list<tools::wallet_public::htlc_entry_info> htlcs_a;
alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false);
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched");
//validate state of b
std::list<tools::wallet_public::htlc_entry_info> htlcs_b;
alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true);
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched");
//create altchain
std::vector<currency::transaction> txs;
txs.push_back(htlc_proposal_tx);
r = mine_next_pow_blocks_in_playtime_with_given_txs(m_mining_accunt.get_public_address(), txs, c, 10, split_id);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
//make sure reorganize happened
crypto::hash id_new_chain = c.get_blockchain_storage().get_block_id_by_height(split_height + 1);
CHECK_AND_ASSERT_MES(id_new_chain != id_first_splited_block, false, "Reorganize didn't happen");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
htlcs_a.clear();
alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false);
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched");
//validate state of b
htlcs_b.clear();
alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true);
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched");
currency::transaction result_redeem_tx = AUTO_VAL_INIT(result_redeem_tx);
alice_b_wlt_instance->redeem_htlc(htlcs_b.front().tx_id, alice_origin, result_redeem_tx);
crypto::hash split_id_2 = c.get_blockchain_storage().get_top_block_id();
uint64_t split_height_2 = c.get_blockchain_storage().get_top_block_height();
//forward blockchain
r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
//memorize block id at split height before split to make sure split happened
crypto::hash id_first_splited_block_2 = c.get_blockchain_storage().get_block_id_by_height(split_height_2 + 1);
txs.clear();
txs.push_back(result_redeem_tx);
r = mine_next_pow_blocks_in_playtime_with_given_txs(m_mining_accunt.get_public_address(), txs, c, 10, split_id_2);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
//make sure reorganize happened
crypto::hash id_new_chain_2 = c.get_blockchain_storage().get_block_id_by_height(split_height_2 + 1);
CHECK_AND_ASSERT_MES(id_new_chain_2 != id_first_splited_block_2, false, "Reorganize didn't happen");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
htlcs_a.clear();
alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false);
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched");
//validate state of b
htlcs_b.clear();
alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true);
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found");
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched");
//forward blockchain
r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
alice_a_wlt_instance->refresh();
alice_b_wlt_instance->refresh();
htlcs_a.clear();
alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false);
CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 0, false, "htlc contracts count is wrong");
//validate state of b
htlcs_b.clear();
alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true);
CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 0, false, "htlc contracts count is wrong");
return true;
}

View file

@ -26,3 +26,12 @@ private:
mutable currency::account_base m_mining_accunt;
};
struct atomic_test_altchain_simple : public wallet_test
{
atomic_test_altchain_simple();
bool generate(std::vector<test_event_entry>& events) const;
bool c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
private:
mutable currency::account_base m_mining_accunt;
};

View file

@ -137,6 +137,27 @@ inline bool mine_next_pow_blocks_in_playtime(const currency::account_public_addr
return true;
}
inline bool mine_next_pow_blocks_in_playtime_with_given_txs(const currency::account_public_address& miner_addr, const std::vector<currency::transaction>& txs, currency::core& c, size_t blocks_count, const crypto::hash& prev_id)
{
std::vector<currency::transaction> txs_local = txs;
crypto::hash prev_id_internal = prev_id;
currency::block prv_block = AUTO_VAL_INIT(prv_block);
bool r = c.get_blockchain_storage().get_block_by_hash(prev_id, prv_block);
CHECK_AND_ASSERT_MES(r, false, "block with id " << prev_id << " not found");
for (size_t i = 0; i != blocks_count; i++)
{
if (!mine_next_pow_block_in_playtime_with_given_txs(miner_addr, c, txs_local, prev_id_internal, currency::get_block_height(prv_block)+1, &prv_block))
return false;
prev_id_internal = get_block_hash(prv_block);
txs_local.clear();
}
return true;
}
// NOTE: stake coins return back to the wallet, newly generated coins go to miner_address (by default they are the same destinations)
inline bool mine_next_pos_block_in_playtime_with_wallet(tools::wallet2& w, const currency::account_public_address& miner_address, size_t& pos_entries_count)
{

View file

@ -1023,6 +1023,7 @@ int main(int argc, char* argv[])
// atomics
GENERATE_AND_PLAY(atomic_simple_test);
GENERATE_AND_PLAY(atomic_test_wrong_redeem_wrong_refund);
GENERATE_AND_PLAY(atomic_test_altchain_simple);
// GENERATE_AND_PLAY(gen_block_reward);
// END OF TESTS */