mirror of https://github.com/oxen-io/oxen-core.git
Test suite compilation updates
This commit is contained in:
parent
442f2182d2
commit
9a022d92c2
|
@ -92,10 +92,8 @@ namespace tests
|
|||
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
|
||||
uint64_t get_target_blockchain_height() const { return 1; }
|
||||
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
|
||||
virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {}
|
||||
virtual crypto::hash on_transaction_relayed(const cryptonote::blobdata& tx) { return crypto::null_hash; }
|
||||
cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; }
|
||||
bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; }
|
||||
bool pool_has_tx(const crypto::hash &txid) const { return false; }
|
||||
bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; }
|
||||
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; }
|
||||
bool get_block_by_hash(const crypto::hash &h, cryptonote::block &blk, bool *orphan = NULL) const { return false; }
|
||||
|
@ -111,5 +109,21 @@ namespace tests
|
|||
bool pad_transactions() const { return false; }
|
||||
uint32_t get_blockchain_pruning_seed() const { return 0; }
|
||||
bool prune_blockchain(uint32_t pruning_seed) const { return true; }
|
||||
|
||||
bool handle_incoming_blinks(const std::vector<cryptonote::serializable_blink_metadata> &blinks, std::vector<crypto::hash> *bad_blinks = nullptr, std::vector<crypto::hash> *missing_txs = nullptr) { return true; }
|
||||
|
||||
class fake_pool {
|
||||
public:
|
||||
void add_missing_blink_hashes(const std::map<uint64_t, std::vector<crypto::hash>> &potential) {}
|
||||
template <typename... Args>
|
||||
int blink_shared_lock(Args &&...args) { return 42; }
|
||||
std::shared_ptr<cryptonote::blink_tx> get_blink(crypto::hash &) { return nullptr; }
|
||||
bool get_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; }
|
||||
bool have_tx(const crypto::hash &txid) const { return false; }
|
||||
};
|
||||
fake_pool &get_pool() { return m_pool; }
|
||||
|
||||
private:
|
||||
fake_pool m_pool;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -161,8 +161,7 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev
|
|||
CHECK_EQ(MK_COINS(3), get_balance(m_recipient_account_4, chain, mtx));
|
||||
|
||||
std::vector<transaction> tx_pool;
|
||||
r = c.get_pool().get_transactions(tx_pool);
|
||||
CHECK_TEST_CONDITION(r);
|
||||
c.get_pool().get_transactions(tx_pool);
|
||||
CHECK_EQ(1, tx_pool.size());
|
||||
|
||||
std::vector<size_t> tx_outs;
|
||||
|
|
|
@ -106,9 +106,9 @@ service_nodes::quorum_manager loki_chain_generator::quorum(uint64_t height) cons
|
|||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<const service_nodes::testing_quorum> loki_chain_generator::get_testing_quorum(service_nodes::quorum_type type, uint64_t height) const
|
||||
std::shared_ptr<const service_nodes::quorum> loki_chain_generator::get_quorum(service_nodes::quorum_type type, uint64_t height) const
|
||||
{
|
||||
// TODO(loki): Bad copy pasta from get_testing_quorum, if it ever changes at the source this will break :<
|
||||
// TODO(loki): Bad copy pasta from get_quorum, if it ever changes at the source this will break :<
|
||||
if (type == service_nodes::quorum_type::checkpointing)
|
||||
{
|
||||
assert(height >= service_nodes::REORG_SAFETY_BUFFER_BLOCKS_POST_HF12);
|
||||
|
@ -117,7 +117,7 @@ std::shared_ptr<const service_nodes::testing_quorum> loki_chain_generator::get_t
|
|||
|
||||
assert(height > 0 && height < blocks_.size());
|
||||
service_nodes::quorum_manager manager = blocks_[height].service_node_state.quorums;
|
||||
std::shared_ptr<const service_nodes::testing_quorum> result = manager.get(type);
|
||||
std::shared_ptr<const service_nodes::quorum> result = manager.get(type);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ cryptonote::transaction loki_chain_generator::create_state_change_tx(service_nod
|
|||
cryptonote::checkpoint_t loki_chain_generator::create_service_node_checkpoint(uint64_t block_height, size_t num_votes) const
|
||||
{
|
||||
assert(block_height % service_nodes::CHECKPOINT_INTERVAL == 0);
|
||||
service_nodes::testing_quorum const &quorum = *get_testing_quorum(service_nodes::quorum_type::checkpointing, block_height);
|
||||
service_nodes::quorum const &quorum = *get_quorum(service_nodes::quorum_type::checkpointing, block_height);
|
||||
assert(num_votes < quorum.validators.size());
|
||||
|
||||
loki_blockchain_entry const &entry = blocks_[block_height];
|
||||
|
|
|
@ -445,7 +445,7 @@ struct output_index {
|
|||
|
||||
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
|
||||
typedef std::pair<crypto::hash, size_t> output_hasher;
|
||||
typedef boost::hash<output_hasher> output_hasher_hasher;
|
||||
struct output_hasher_hasher { size_t operator()(const output_hasher &h) const { return *reinterpret_cast<const size_t *>(h.first.data) + h.second; } };
|
||||
typedef std::map<uint64_t, std::vector<size_t> > map_output_t;
|
||||
typedef std::map<uint64_t, std::vector<output_index> > map_output_idx_t;
|
||||
typedef std::unordered_map<crypto::hash, cryptonote::block> map_block_t;
|
||||
|
@ -1375,7 +1375,7 @@ struct loki_chain_generator
|
|||
const loki_blockchain_entry& top() const { return blocks_.back(); }
|
||||
service_nodes::quorum_manager top_quorum() const;
|
||||
service_nodes::quorum_manager quorum(uint64_t height) const;
|
||||
std::shared_ptr<const service_nodes::testing_quorum> get_testing_quorum(service_nodes::quorum_type type, uint64_t height) const;
|
||||
std::shared_ptr<const service_nodes::quorum> get_quorum(service_nodes::quorum_type type, uint64_t height) const;
|
||||
|
||||
cryptonote::account_base add_account();
|
||||
loki_blockchain_entry &add_block(loki_blockchain_entry const &entry, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {});
|
||||
|
|
|
@ -110,7 +110,7 @@ bool loki_checkpointing_alt_chain_handle_alt_blocks_at_tip::generate(std::vector
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -184,7 +184,7 @@ bool loki_checkpointing_alt_chain_more_service_node_checkpoints_less_pow_overtak
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -232,7 +232,7 @@ bool loki_checkpointing_alt_chain_receive_checkpoint_votes_should_reorg_back::ge
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -249,7 +249,7 @@ bool loki_checkpointing_alt_chain_receive_checkpoint_votes_should_reorg_back::ge
|
|||
uint64_t first_checkpointed_height = fork.height();
|
||||
uint64_t first_checkpointed_height_hf = fork.top().block.major_version;
|
||||
crypto::hash first_checkpointed_hash = cryptonote::get_block_hash(fork.top().block);
|
||||
std::shared_ptr<const service_nodes::testing_quorum> first_quorum = fork.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> first_quorum = fork.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
|
||||
for (size_t i = 0; i < service_nodes::CHECKPOINT_INTERVAL; i++)
|
||||
{
|
||||
|
@ -260,7 +260,7 @@ bool loki_checkpointing_alt_chain_receive_checkpoint_votes_should_reorg_back::ge
|
|||
uint64_t second_checkpointed_height = fork.height();
|
||||
uint64_t second_checkpointed_height_hf = fork.top().block.major_version;
|
||||
crypto::hash second_checkpointed_hash = cryptonote::get_block_hash(fork.top().block);
|
||||
std::shared_ptr<const service_nodes::testing_quorum> second_quorum = fork.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> second_quorum = fork.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
|
||||
// NOTE: Fork generates service node votes, upon sending them over and the
|
||||
// main chain collecting them validly (they should be able to verify
|
||||
|
@ -324,7 +324,7 @@ bool loki_checkpointing_alt_chain_with_increasing_service_node_checkpoints::gene
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -407,7 +407,7 @@ bool loki_checkpointing_service_node_checkpoint_from_votes::generate(std::vector
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -415,7 +415,7 @@ bool loki_checkpointing_service_node_checkpoint_from_votes::generate(std::vector
|
|||
// NOTE: Generate service node votes
|
||||
uint64_t checkpointed_height = gen.height();
|
||||
crypto::hash checkpointed_hash = cryptonote::get_block_hash(gen.top().block);
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::vector<service_nodes::quorum_vote_t> checkpoint_votes(service_nodes::CHECKPOINT_MIN_VOTES);
|
||||
for (size_t i = 0; i < service_nodes::CHECKPOINT_MIN_VOTES; i++)
|
||||
{
|
||||
|
@ -487,7 +487,7 @@ bool loki_checkpointing_service_node_checkpoints_check_reorg_windows::generate(s
|
|||
for (; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -1029,12 +1029,12 @@ bool loki_service_nodes_alt_quorums::generate(std::vector<test_event_entry>& eve
|
|||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("check_alt_quorums_exist");
|
||||
|
||||
std::vector<std::shared_ptr<const service_nodes::testing_quorum>> alt_quorums;
|
||||
c.get_testing_quorum(service_nodes::quorum_type::obligations, height_with_fork, false /*include_old*/, &alt_quorums);
|
||||
std::vector<std::shared_ptr<const service_nodes::quorum>> alt_quorums;
|
||||
c.get_quorum(service_nodes::quorum_type::obligations, height_with_fork, false /*include_old*/, &alt_quorums);
|
||||
CHECK_TEST_CONDITION_MSG(alt_quorums.size() == 1, "alt_quorums.size(): " << alt_quorums.size());
|
||||
|
||||
service_nodes::testing_quorum const &fork_obligation_quorum = *fork_quorums.obligations;
|
||||
service_nodes::testing_quorum const &real_obligation_quorum = *(alt_quorums[0]);
|
||||
service_nodes::quorum const &fork_obligation_quorum = *fork_quorums.obligations;
|
||||
service_nodes::quorum const &real_obligation_quorum = *(alt_quorums[0]);
|
||||
CHECK_TEST_CONDITION(fork_obligation_quorum.validators.size() == real_obligation_quorum.validators.size());
|
||||
CHECK_TEST_CONDITION(fork_obligation_quorum.workers.size() == real_obligation_quorum.workers.size());
|
||||
|
||||
|
@ -1081,7 +1081,7 @@ bool loki_service_nodes_checkpoint_quorum_size::generate(std::vector<test_event_
|
|||
loki_register_callback(events, "check_checkpoint_quorum_should_be_empty", [&events, check_height_1](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("check_checkpoint_quorum_should_be_empty");
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = c.get_testing_quorum(service_nodes::quorum_type::checkpointing, check_height_1);
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = c.get_quorum(service_nodes::quorum_type::checkpointing, check_height_1);
|
||||
CHECK_TEST_CONDITION(quorum != nullptr);
|
||||
CHECK_TEST_CONDITION(quorum->validators.size() == 0);
|
||||
return true;
|
||||
|
@ -1093,7 +1093,7 @@ bool loki_service_nodes_checkpoint_quorum_size::generate(std::vector<test_event_
|
|||
for (tries = 0; tries < MAX_TRIES; tries++)
|
||||
{
|
||||
gen.add_blocks_until_next_checkpointable_height();
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = gen.get_testing_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = gen.get_quorum(service_nodes::quorum_type::checkpointing, gen.height());
|
||||
if (quorum && quorum->validators.size()) break;
|
||||
}
|
||||
assert(tries != MAX_TRIES);
|
||||
|
@ -1102,7 +1102,7 @@ bool loki_service_nodes_checkpoint_quorum_size::generate(std::vector<test_event_
|
|||
loki_register_callback(events, "check_checkpoint_quorum_should_be_populated", [&events, check_height_2](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("check_checkpoint_quorum_should_be_populated");
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum = c.get_testing_quorum(service_nodes::quorum_type::checkpointing, check_height_2);
|
||||
std::shared_ptr<const service_nodes::quorum> quorum = c.get_quorum(service_nodes::quorum_type::checkpointing, check_height_2);
|
||||
CHECK_TEST_CONDITION(quorum != nullptr);
|
||||
CHECK_TEST_CONDITION(quorum->validators.size() > 0);
|
||||
return true;
|
||||
|
@ -1241,7 +1241,7 @@ bool loki_service_nodes_test_rollback::generate(std::vector<test_event_entry>& e
|
|||
cryptonote::get_service_node_state_change_from_tx_extra(
|
||||
dereg_tx.data.tx.extra, deregistration, c.get_blockchain_storage().get_current_hard_fork_version());
|
||||
|
||||
const auto uptime_quorum = c.get_testing_quorum(service_nodes::quorum_type::obligations, deregistration.block_height);
|
||||
const auto uptime_quorum = c.get_quorum(service_nodes::quorum_type::obligations, deregistration.block_height);
|
||||
CHECK_TEST_CONDITION(uptime_quorum);
|
||||
const auto pk_a = uptime_quorum->workers.at(deregistration.service_node_index);
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ public:
|
|||
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
|
||||
uint64_t get_target_blockchain_height() const { return 1; }
|
||||
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
|
||||
virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {}
|
||||
virtual crypto::hash on_transaction_relayed(const cryptonote::blobdata& tx) { return crypto::null_hash; }
|
||||
cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; }
|
||||
bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; }
|
||||
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; }
|
||||
|
@ -87,6 +87,22 @@ public:
|
|||
|
||||
// TODO(loki): Write tests
|
||||
bool add_service_node_vote(const service_nodes::quorum_vote_t& vote, cryptonote::vote_verification_context &vvc) { return false; }
|
||||
|
||||
bool handle_incoming_blinks(const std::vector<cryptonote::serializable_blink_metadata> &blinks, std::vector<crypto::hash> *bad_blinks = nullptr, std::vector<crypto::hash> *missing_txs = nullptr) { return true; }
|
||||
|
||||
class fake_pool {
|
||||
public:
|
||||
void add_missing_blink_hashes(const std::map<uint64_t, std::vector<crypto::hash>> &potential) {}
|
||||
template <typename... Args>
|
||||
int blink_shared_lock(Args &&...args) { return 42; }
|
||||
std::shared_ptr<cryptonote::blink_tx> get_blink(crypto::hash &) { return nullptr; }
|
||||
bool get_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; }
|
||||
bool have_tx(const crypto::hash &txid) const { return false; }
|
||||
};
|
||||
fake_pool &get_pool() { return m_pool; }
|
||||
|
||||
private:
|
||||
fake_pool m_pool;
|
||||
};
|
||||
|
||||
typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server;
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
static constexpr const std::uint8_t source[] = {
|
||||
alignas(size_t) static constexpr const std::uint8_t source[] = {
|
||||
0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea,
|
||||
0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94,
|
||||
0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94,
|
||||
|
@ -55,7 +55,7 @@ namespace
|
|||
{
|
||||
T value{};
|
||||
|
||||
static_assert(alignof(T) == 1, "T must have 1 byte alignment");
|
||||
static_assert(alignof(T) <= alignof(size_t), "T must have size_t-or-smaller alignment");
|
||||
static_assert(sizeof(T) <= sizeof(source), "T is too large for source");
|
||||
static_assert(sizeof(T) * 2 <= sizeof(expected), "T is too large for destination");
|
||||
std::memcpy(addressof(value), source, sizeof(T));
|
||||
|
|
|
@ -134,7 +134,7 @@ TEST(service_nodes, staking_requirement)
|
|||
static bool verify_vote(service_nodes::quorum_vote_t const &vote,
|
||||
uint64_t latest_height,
|
||||
cryptonote::vote_verification_context &vvc,
|
||||
service_nodes::testing_quorum const &quorum)
|
||||
service_nodes::quorum const &quorum)
|
||||
{
|
||||
bool result = service_nodes::verify_vote_age(vote, latest_height, vvc);
|
||||
result &= service_nodes::verify_vote_signature(cryptonote::network_version_count - 1, vote, vvc, quorum);
|
||||
|
@ -151,7 +151,7 @@ TEST(service_nodes, vote_validation)
|
|||
voter_keys.pub = service_node_voter.pub;
|
||||
voter_keys.key = service_node_voter.sec;
|
||||
|
||||
service_nodes::testing_quorum state = {};
|
||||
service_nodes::quorum state = {};
|
||||
{
|
||||
state.validators.resize(10);
|
||||
state.workers.resize(state.validators.size());
|
||||
|
@ -228,7 +228,7 @@ TEST(service_nodes, tx_extra_state_change_validation)
|
|||
// Generate a quorum and the voter
|
||||
std::array<service_nodes::service_node_keys, 10> voters = {};
|
||||
|
||||
service_nodes::testing_quorum state = {};
|
||||
service_nodes::quorum state = {};
|
||||
{
|
||||
state.validators.resize(voters.size());
|
||||
state.workers.resize(voters.size());
|
||||
|
|
Loading…
Reference in New Issue