Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: report equivocation #1999

Merged
merged 11 commits into from
Mar 21, 2024
114 changes: 114 additions & 0 deletions core/consensus/babe/impl/babe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <latch>

#include <boost/range/adaptor/transformed.hpp>
#include <libp2p/common/final_action.hpp>

#include "application/app_configuration.hpp"
#include "application/app_state_manager.hpp"
Expand All @@ -30,11 +31,14 @@
#include "dispute_coordinator/dispute_coordinator.hpp"
#include "metrics/histogram_timer.hpp"
#include "network/block_announce_transmitter.hpp"
#include "offchain/offchain_worker_factory.hpp"
#include "offchain/offchain_worker_pool.hpp"
#include "parachain/availability/bitfield/store.hpp"
#include "parachain/backing/store.hpp"
#include "parachain/parachain_inherent_data.hpp"
#include "parachain/validator/parachain_processor.hpp"
#include "primitives/inherent_data.hpp"
#include "runtime/runtime_api/babe_api.hpp"
#include "runtime/runtime_api/offchain_worker_api.hpp"
#include "storage/changes_trie/impl/storage_changes_tracker_impl.hpp"
#include "storage/trie/serialization/ordered_trie_hash.hpp"
Expand Down Expand Up @@ -83,7 +87,10 @@ namespace kagome::consensus::babe {
primitives::events::StorageSubscriptionEnginePtr storage_sub_engine,
primitives::events::ChainSubscriptionEnginePtr chain_sub_engine,
std::shared_ptr<network::BlockAnnounceTransmitter> announce_transmitter,
std::shared_ptr<runtime::BabeApi> babe_api,
std::shared_ptr<runtime::OffchainWorkerApi> offchain_worker_api,
std::shared_ptr<offchain::OffchainWorkerFactory> offchain_worker_factory,
std::shared_ptr<offchain::OffchainWorkerPool> offchain_worker_pool,
std::shared_ptr<common::MainPoolHandler> main_pool_handler,
std::shared_ptr<common::WorkerPoolHandler> worker_pool_handler)
: log_(log::createLogger("Babe", "babe")),
Expand All @@ -104,7 +111,10 @@ namespace kagome::consensus::babe {
storage_sub_engine_(std::move(storage_sub_engine)),
chain_sub_engine_(std::move(chain_sub_engine)),
announce_transmitter_(std::move(announce_transmitter)),
babe_api_(std::move(babe_api)),
offchain_worker_api_(std::move(offchain_worker_api)),
offchain_worker_factory_(std::move(offchain_worker_factory)),
offchain_worker_pool_(std::move(offchain_worker_pool)),
main_pool_handler_(std::move(main_pool_handler)),
worker_pool_handler_(std::move(worker_pool_handler)),
is_validator_by_config_(app_config.roles().flags.authority != 0),
Expand All @@ -123,7 +133,10 @@ namespace kagome::consensus::babe {
BOOST_ASSERT(chain_sub_engine_);
BOOST_ASSERT(chain_sub_engine_);
BOOST_ASSERT(announce_transmitter_);
BOOST_ASSERT(babe_api_);
BOOST_ASSERT(offchain_worker_api_);
BOOST_ASSERT(offchain_worker_factory_);
BOOST_ASSERT(offchain_worker_pool_);
BOOST_ASSERT(main_pool_handler_);
BOOST_ASSERT(worker_pool_handler_);

Expand Down Expand Up @@ -171,6 +184,11 @@ namespace kagome::consensus::babe {
return babe::getSlot(header);
}

outcome::result<AuthorityIndex> Babe::getAuthority(
const primitives::BlockHeader &header) const {
return babe::getAuthority(header);
}

outcome::result<void> Babe::processSlot(
SlotNumber slot, const primitives::BlockInfo &best_block) {
auto slot_timestamp = clock_.now();
Expand Down Expand Up @@ -228,6 +246,102 @@ namespace kagome::consensus::babe {
return validating_->validateHeader(block_header);
}

outcome::result<void> Babe::reportEquivocation(
const primitives::BlockHash &first_hash,
const primitives::BlockHash &second_hash) const {
BOOST_ASSERT(first_hash != second_hash);

auto first_header_res = block_tree_->getBlockHeader(first_hash);
if (first_header_res.has_error()) {
SL_WARN(log_,
"Can't obtain equivocating header of block {}: {}",
first_hash,
first_header_res.error());
return first_header_res.as_failure();
}
auto &first_header = first_header_res.value();

auto second_header_res = block_tree_->getBlockHeader(second_hash);
if (second_header_res.has_error()) {
SL_WARN(log_,
"Can't obtain equivocating header of block {}: {}",
second_hash,
second_header_res.error());
return second_header_res.as_failure();
}
auto &second_header = second_header_res.value();

auto slot_res = getSlot(first_header);
BOOST_ASSERT(slot_res.has_value());
auto slot = slot_res.value();
BOOST_ASSERT_MSG(
[&] {
slot_res = getSlot(second_header);
return slot_res.has_value() and slot_res.value() == slot;
}(),
"Equivocating blocks must be block of one slot");

auto authority_index_res = getAuthority(first_header);
BOOST_ASSERT(authority_index_res.has_value());
auto authority_index = authority_index_res.value();
BOOST_ASSERT_MSG(
[&] {
authority_index_res = getAuthority(second_header);
return authority_index_res.has_value()
and authority_index_res.value() == authority_index;
}(),
"Equivocating blocks must be block of one authority");

auto parent = second_header.parentInfo().value();
auto epoch_res = slots_util_.get()->slotToEpoch(parent, slot);
if (epoch_res.has_error()) {
SL_WARN(log_, "Can't compute epoch by slot: {}", epoch_res.error());
return epoch_res.as_failure();
}
auto epoch = epoch_res.value();

auto config_res = config_repo_->config(parent, epoch);
if (config_res.has_error()) {
SL_WARN(log_, "Can't obtain config: {}", config_res.error());
return config_res.as_failure();
}
auto &config = config_res.value();

const auto &authorities = config->authorities;
const auto &authority = authorities[authority_index].id;

EquivocationProof equivocation_proof{
.offender = authority,
.slot = slot,
.first_header = std::move(first_header),
.second_header = std::move(second_header),
};

auto ownership_proof_res = babe_api_->generate_key_ownership_proof(
block_tree_->bestBlock().hash, slot, equivocation_proof.offender);
if (ownership_proof_res.has_error()) {
SL_WARN(
log_, "Can't get ownership proof: {}", ownership_proof_res.error());
return ownership_proof_res.as_failure();
}
auto &ownership_proof_opt = ownership_proof_res.value();
if (not ownership_proof_opt.has_value()) {
SL_WARN(log_,
"Can't get ownership proof: runtime call returns none",
ownership_proof_res.error());
return ownership_proof_res.as_failure(); // FIXME;
}
auto &ownership_proof = ownership_proof_opt.value();

offchain_worker_pool_->addWorker(offchain_worker_factory_->make());
::libp2p::common::FinalAction remove(
[&] { offchain_worker_pool_->removeWorker(); });
return babe_api_->submit_report_equivocation_unsigned_extrinsic(
second_header.parent_hash,
std::move(equivocation_proof),
ownership_proof);
}

bool Babe::changeEpoch(EpochNumber epoch,
const primitives::BlockInfo &block) const {
return lottery_->changeEpoch(epoch, block);
Expand Down
30 changes: 25 additions & 5 deletions core/consensus/babe/impl/babe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,25 @@ namespace kagome::dispute {
class DisputeCoordinator;
}

namespace kagome::network {
class BlockAnnounceTransmitter;
}

namespace kagome::offchain {
class OffchainWorkerFactory;
class OffchainWorkerPool;
} // namespace kagome::offchain

namespace kagome::parachain {
class BitfieldStore;
struct ParachainProcessorImpl;
struct BackedCandidatesSource;
} // namespace kagome::parachain

namespace kagome::network {
class BlockAnnounceTransmitter;
}

namespace kagome::runtime {
class BabeApi;
class OffchainWorkerApi;
}
} // namespace kagome::runtime

namespace kagome::storage::changes_trie {
class StorageChangesTrackerImpl;
Expand Down Expand Up @@ -106,7 +112,11 @@ namespace kagome::consensus::babe {
primitives::events::StorageSubscriptionEnginePtr storage_sub_engine,
primitives::events::ChainSubscriptionEnginePtr chain_sub_engine,
std::shared_ptr<network::BlockAnnounceTransmitter> announce_transmitter,
std::shared_ptr<runtime::BabeApi> babe_api,
std::shared_ptr<runtime::OffchainWorkerApi> offchain_worker_api,
std::shared_ptr<offchain::OffchainWorkerFactory>
offchain_worker_factory,
std::shared_ptr<offchain::OffchainWorkerPool> offchain_worker_pool,
std::shared_ptr<common::MainPoolHandler> main_pool_handler,
std::shared_ptr<common::WorkerPoolHandler> worker_pool_handler);

Expand All @@ -118,12 +128,19 @@ namespace kagome::consensus::babe {
outcome::result<SlotNumber> getSlot(
const primitives::BlockHeader &header) const override;

outcome::result<AuthorityIndex> getAuthority(
const primitives::BlockHeader &header) const override;

outcome::result<void> processSlot(
SlotNumber slot, const primitives::BlockInfo &best_block) override;

outcome::result<void> validateHeader(
const primitives::BlockHeader &block_header) const override;

outcome::result<void> reportEquivocation(
xDimon marked this conversation as resolved.
Show resolved Hide resolved
const primitives::BlockHash &first,
const primitives::BlockHash &second) const override;

private:
bool changeEpoch(EpochNumber epoch,
const primitives::BlockInfo &block) const override;
Expand Down Expand Up @@ -166,7 +183,10 @@ namespace kagome::consensus::babe {
primitives::events::StorageSubscriptionEnginePtr storage_sub_engine_;
primitives::events::ChainSubscriptionEnginePtr chain_sub_engine_;
std::shared_ptr<network::BlockAnnounceTransmitter> announce_transmitter_;
std::shared_ptr<runtime::BabeApi> babe_api_;
std::shared_ptr<runtime::OffchainWorkerApi> offchain_worker_api_;
std::shared_ptr<offchain::OffchainWorkerFactory> offchain_worker_factory_;
std::shared_ptr<offchain::OffchainWorkerPool> offchain_worker_pool_;
std::shared_ptr<common::MainPoolHandler> main_pool_handler_;
std::shared_ptr<common::WorkerPoolHandler> worker_pool_handler_;

Expand Down
6 changes: 6 additions & 0 deletions core/consensus/babe/impl/babe_digests_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ namespace kagome::consensus::babe {
return babe_block_header.slot_number;
}

outcome::result<AuthorityIndex> getAuthority(
const primitives::BlockHeader &header) {
OUTCOME_TRY(babe_block_header, getBabeBlockHeader(header));
return babe_block_header.authority_index;
}

outcome::result<BabeBlockHeader> getBabeBlockHeader(
const primitives::BlockHeader &block_header) {
[[unlikely]] if (block_header.number == 0) {
Expand Down
3 changes: 3 additions & 0 deletions core/consensus/babe/impl/babe_digests_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ namespace kagome::consensus::babe {

outcome::result<SlotNumber> getSlot(const primitives::BlockHeader &header);

outcome::result<AuthorityIndex> getAuthority(
const primitives::BlockHeader &header);

outcome::result<BabeBlockHeader> getBabeBlockHeader(
const primitives::BlockHeader &block_header);

Expand Down
9 changes: 9 additions & 0 deletions core/consensus/babe/types/equivocation_proof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

namespace kagome::consensus::babe {

/// An opaque type used to represent the key ownership proof at the runtime
/// API boundary. The inner value is an encoded representation of the actual
/// key ownership proof which will be parameterized when defining the runtime.
/// At the runtime API boundary this type is unknown and as such we keep this
/// opaque representation, implementors of the runtime API will have to make
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
using OpaqueKeyOwnershipProof =
Tagged<common::Buffer, struct OpaqueKeyOwnershipProofTag>;

/// Represents an equivocation proof. An equivocation happens when a validator
/// produces more than one block on the same slot. The proof of equivocation
/// are the given distinct headers that were signed by the validator and which
Expand Down
12 changes: 11 additions & 1 deletion core/consensus/grandpa/environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "consensus/grandpa/chain.hpp"
#include "consensus/grandpa/justification_observer.hpp"
#include "consensus/grandpa/types/equivocation_proof.hpp"

namespace kagome::primitives {
struct Justification;
Expand All @@ -18,7 +19,7 @@ namespace libp2p::peer {
}

namespace kagome::consensus::grandpa {
class Grandpa;
class VotingRound;
struct MovableRoundState;
} // namespace kagome::consensus::grandpa

Expand Down Expand Up @@ -116,6 +117,15 @@ namespace kagome::consensus::grandpa {
*/
virtual outcome::result<GrandpaJustification> getJustification(
const BlockHash &block_hash) = 0;

/// Report the given equivocation to the GRANDPA runtime module. This method
/// generates a session membership proof of the offender and then submits an
/// extrinsic to report the equivocation. In particular, the session
/// membership proof must be generated at the block at which the given set
/// was active which isn't necessarily the best block if there are pending
/// authority set changes.
virtual outcome::result<void> reportEquivocation(
const VotingRound &round, const Equivocation &equivocation) const = 0;
};

} // namespace kagome::consensus::grandpa
Loading
Loading