Allow state changes if non conflicting states (#718)

* Only dupe check state changes against the latest valid change

* Check the service node info for dupe state change

* Gate dupe state changes behind HF12

* Actually properly gate dupe state change and revert breaking changes

* Use is_decommissioned() to get service node state, change msg log level
This commit is contained in:
Doyle 2019-07-09 13:59:42 +10:00 committed by GitHub
parent 5a13bb845e
commit ca37156c38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 45 deletions

View File

@ -67,7 +67,7 @@ namespace service_nodes {
decommission,
recommission,
ip_change_penalty,
_count
_count,
};
};

View File

@ -3186,9 +3186,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
if (tx.type == txtype::state_change)
{
// Check the inputs (votes) of the transaction have not already been
// submitted to the blockchain under another transaction using a different
// combination of votes.
tx_extra_service_node_state_change state_change;
if (!get_service_node_state_change_from_tx_extra(tx.extra, state_change, hf_version))
{
@ -3216,51 +3213,77 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
crypto::public_key const &state_change_service_node_pubkey = quorum->workers[state_change.service_node_index];
const uint64_t height = state_change.block_height;
constexpr size_t num_blocks_to_check = service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS;
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
std::vector<cryptonote::blobdata> txs;
if (!get_blocks(height, num_blocks_to_check, blocks, txs))
if (hf_version >= cryptonote::network_version_12_checkpointing)
{
MERROR_VER("Failed to get historical blocks to check against previous state changes for de-duplication");
return false;
}
for (blobdata const &blob : txs)
{
transaction existing_tx;
if (!parse_and_validate_tx_from_blob(blob, existing_tx))
//
// NOTE: Query the Service Node List for the in question Service Node the state change is for and disallow if conflicting
//
std::vector<service_nodes::service_node_pubkey_info> service_node_array = m_service_node_list.get_service_node_list_state({state_change_service_node_pubkey});
if (service_node_array.empty())
{
MERROR_VER("tx could not be validated from blob, possibly corrupt blockchain");
continue;
LOG_PRINT_L2("Service Node no longer exists on the network, state change can be ignored");
return false;
}
if (existing_tx.type != txtype::state_change)
continue;
tx_extra_service_node_state_change existing_state_change;
if (!get_service_node_state_change_from_tx_extra(existing_tx.extra, existing_state_change, hf_version))
service_nodes::service_node_info const &service_node_info = service_node_array[0].info;
if (!service_node_info.can_transition_to_state(state_change.state))
{
MERROR_VER("could not get service node state change from tx extra, possibly corrupt tx");
continue;
}
crypto::public_key existing_state_change_service_node_pubkey;
if (!m_service_node_list.get_quorum_pubkey(quorum_type,
service_nodes::quorum_group::worker,
existing_state_change.block_height,
existing_state_change.service_node_index,
existing_state_change_service_node_pubkey))
continue;
if (existing_state_change_service_node_pubkey == state_change_service_node_pubkey)
{
MERROR_VER("Already seen this state change tx (aka double spend)");
LOG_PRINT_L2("State change trying to vote Service Node into the same state it already is in, (aka double spend)");
tvc.m_double_spend = true;
return false;
}
}
else
{
// Check the inputs (votes) of the transaction have not already been
// submitted to the blockchain under another transaction using a different
// combination of votes.
const uint64_t height = state_change.block_height;
constexpr size_t num_blocks_to_check = service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS;
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
std::vector<cryptonote::blobdata> txs;
if (!get_blocks(height, num_blocks_to_check, blocks, txs))
{
MERROR_VER("Failed to get historical blocks to check against previous state changes for de-duplication");
return false;
}
for (blobdata const &blob : txs)
{
transaction existing_tx;
if (!parse_and_validate_tx_from_blob(blob, existing_tx))
{
MERROR_VER("tx could not be validated from blob, possibly corrupt blockchain");
continue;
}
if (existing_tx.type != txtype::state_change)
continue;
tx_extra_service_node_state_change existing_state_change;
if (!get_service_node_state_change_from_tx_extra(existing_tx.extra, existing_state_change, hf_version))
{
MERROR_VER("could not get service node state change from tx extra, possibly corrupt tx");
continue;
}
const auto existing_quorum = m_service_node_list.get_testing_quorum(quorum_type, existing_state_change.block_height);
if (!existing_quorum)
{
MERROR_VER("Could not get obligations quorum for recent state change tx");
continue;
}
if (existing_quorum->workers[existing_state_change.service_node_index] == state_change_service_node_pubkey)
{
MERROR_VER("Already seen this state change tx (aka double spend)");
tvc.m_double_spend = true;
return false;
}
}
}
}
else if (tx.type == txtype::key_image_unlock)
{

View File

@ -2168,5 +2168,21 @@ namespace service_nodes
cmd = stream.str();
return true;
}
bool service_node_info::can_transition_to_state(new_state proposed_state) const
{
if (is_decommissioned())
{
return proposed_state != new_state::decommission &&
proposed_state != new_state::ip_change_penalty;
}
else
{
return proposed_state != new_state::recommission;
}
return true;
}
}

View File

@ -120,6 +120,8 @@ namespace service_nodes
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
bool is_decommissioned() const { return active_since_height < 0; }
bool is_active() const { return is_fully_funded() && !is_decommissioned(); }
bool can_transition_to_state(new_state proposed_state) const;
size_t total_num_locked_contributions() const;
BEGIN_SERIALIZE_OBJECT()

View File

@ -157,19 +157,28 @@ namespace cryptonote
continue;
}
crypto::public_key service_node_to_change_in_the_pool;
if (service_node_list.get_quorum_pubkey(quorum_type, quorum_group, pool_tx_state_change.block_height, pool_tx_state_change.service_node_index, service_node_to_change_in_the_pool))
if (hard_fork_version >= cryptonote::network_version_12_checkpointing)
{
if (service_node_to_change == service_node_to_change_in_the_pool)
crypto::public_key service_node_to_change_in_the_pool;
bool specifying_same_service_node = false;
if (service_node_list.get_quorum_pubkey(quorum_type, quorum_group, pool_tx_state_change.block_height, pool_tx_state_change.service_node_index, service_node_to_change_in_the_pool))
{
specifying_same_service_node = (service_node_to_change == service_node_to_change_in_the_pool);
}
else
{
MWARNING("Could not resolve the service node public key from the information in a pooled tx state change, falling back to primitive checking method");
specifying_same_service_node = (state_change == pool_tx_state_change);
}
if (specifying_same_service_node && pool_tx_state_change.state == state_change.state)
return true;
}
else
{
MWARNING("Could not resolve the service node public key from the information in a pooled tx state change, possibly corrupt tx in your blockchain, falling back to primitive checking method");
if (state_change == pool_tx_state_change)
return true;
}
}
}
else if (tx.type == txtype::key_image_unlock)