Skip to content

Commit

Permalink
Feature: report equivocation (#1999)
Browse files Browse the repository at this point in the history
* feature: equivocation report in the grandpa consensus

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* feature: equivocation report in the babe consensus (and common part for block production)

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* fix: voting round test

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* feature: equivocation in timeline test

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* feature: test of an equivocation report in babe

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* fix: review issues

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* fix: ocw context for equivocation report submission

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* fix: review issues

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

* fix: broken tests

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>

---------

Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>
  • Loading branch information
xDimon authored Mar 21, 2024
1 parent 5a3dc70 commit 982153e
Show file tree
Hide file tree
Showing 36 changed files with 980 additions and 86 deletions.
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(
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

0 comments on commit 982153e

Please sign in to comment.