Skip to content

Commit

Permalink
feature: equivocation report in the babe consensus (and common part f…
Browse files Browse the repository at this point in the history
…or block production)

Signed-off-by: Dmitriy Khaustov aka xDimon <khaustov.dm@gmail.com>
  • Loading branch information
xDimon committed Mar 1, 2024
1 parent ee70925 commit d25c448
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 6 deletions.
103 changes: 103 additions & 0 deletions core/consensus/babe/impl/babe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#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,6 +84,7 @@ 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<common::MainPoolHandler> main_pool_handler,
std::shared_ptr<common::WorkerPoolHandler> worker_pool_handler)
Expand All @@ -104,6 +106,7 @@ 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)),
main_pool_handler_(std::move(main_pool_handler)),
worker_pool_handler_(std::move(worker_pool_handler)),
Expand All @@ -123,6 +126,7 @@ 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(main_pool_handler_);
BOOST_ASSERT(worker_pool_handler_);
Expand Down Expand Up @@ -171,6 +175,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 +237,100 @@ 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 {
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;
auto authority = authorities[authority_index].id;

// TODO(xDimon):
// Make sense to get data of both blocks and check it for equality

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();

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
12 changes: 11 additions & 1 deletion core/consensus/babe/impl/babe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ namespace kagome::network {
}

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

namespace kagome::storage::changes_trie {
class StorageChangesTrackerImpl;
Expand Down Expand Up @@ -106,6 +107,7 @@ 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<common::MainPoolHandler> main_pool_handler,
std::shared_ptr<common::WorkerPoolHandler> worker_pool_handler);
Expand All @@ -118,12 +120,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,6 +175,7 @@ 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<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
9 changes: 9 additions & 0 deletions core/consensus/production_consensus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace kagome::consensus {
SingleValidator,
};

using AuthorityIndex = uint32_t;

/// Consensus responsible for choice slot leaders, block production, etc.
class ProductionConsensus {
public:
Expand All @@ -40,12 +42,19 @@ namespace kagome::consensus {
virtual outcome::result<SlotNumber> getSlot(
const primitives::BlockHeader &header) const = 0;

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

virtual outcome::result<void> processSlot(
SlotNumber slot, const primitives::BlockInfo &parent) = 0;

virtual outcome::result<void> validateHeader(
const primitives::BlockHeader &block_header) const = 0;

virtual outcome::result<void> reportEquivocation(
const primitives::BlockHash &first,
const primitives::BlockHash &second) const = 0;

protected:
/// Changes epoch
/// @param epoch epoch that switch to
Expand Down
7 changes: 6 additions & 1 deletion core/consensus/timeline/impl/block_header_appender_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@
#include "consensus/babe/babe_config_repository.hpp"
#include "consensus/timeline/impl/block_addition_error.hpp"
#include "consensus/timeline/impl/block_appender_base.hpp"
#include "consensus/timeline/timeline.hpp"

namespace kagome::consensus {

BlockHeaderAppenderImpl::BlockHeaderAppenderImpl(
std::shared_ptr<blockchain::BlockTree> block_tree,
std::shared_ptr<crypto::Hasher> hasher,
std::unique_ptr<BlockAppenderBase> appender)
std::unique_ptr<BlockAppenderBase> appender,
LazySPtr<Timeline> timeline)
: block_tree_{std::move(block_tree)},
hasher_{std::move(hasher)},
appender_{std::move(appender)},
timeline_{std::move(timeline)},
logger_{log::createLogger("BlockHeaderAppender", "block_appender")} {
BOOST_ASSERT(block_tree_ != nullptr);
BOOST_ASSERT(hasher_ != nullptr);
Expand Down Expand Up @@ -60,6 +63,8 @@ namespace kagome::consensus {
return;
}

timeline_.get()->checkAndReportEquivocation(block.header);

appender_->applyJustifications(
block_info,
justification,
Expand Down
10 changes: 8 additions & 2 deletions core/consensus/timeline/impl/block_header_appender_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <libp2p/peer/peer_id.hpp>

#include "consensus/babe/types/babe_configuration.hpp"
#include "injector/lazy.hpp"
#include "log/logger.hpp"
#include "primitives/block_header.hpp"

Expand All @@ -22,6 +23,10 @@ namespace kagome::crypto {
class Hasher;
}

namespace kagome::consensus {
class Timeline;
}

namespace kagome::consensus {

class BlockAppenderBase;
Expand All @@ -32,7 +37,8 @@ namespace kagome::consensus {
public:
BlockHeaderAppenderImpl(std::shared_ptr<blockchain::BlockTree> block_tree,
std::shared_ptr<crypto::Hasher> hasher,
std::unique_ptr<BlockAppenderBase> appender);
std::unique_ptr<BlockAppenderBase> appender,
LazySPtr<Timeline> timeline);

void appendHeader(
primitives::BlockHeader &&block_header,
Expand All @@ -42,8 +48,8 @@ namespace kagome::consensus {
private:
std::shared_ptr<blockchain::BlockTree> block_tree_;
std::shared_ptr<crypto::Hasher> hasher_;

std::unique_ptr<BlockAppenderBase> appender_;
LazySPtr<Timeline> timeline_;

struct {
std::chrono::high_resolution_clock::time_point time;
Expand Down
50 changes: 50 additions & 0 deletions core/consensus/timeline/impl/timeline_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,4 +766,54 @@ namespace kagome::consensus {
remains_ms);
}

void TimelineImpl::checkAndReportEquivocation(
const primitives::BlockHeader &header) {
auto consensus = consensus_selector_.get()->getProductionConsensus(header);
BOOST_ASSERT_MSG(consensus, "Must be returned at least fallback consensus");

auto slot_res = consensus->getSlot(header);
if (slot_res.has_error()) {
return;
}
auto slot = slot_res.value();

if (slot + kMaxSlotObserveForEquivocation < current_slot_) {
return;
}

auto authority_index_res = consensus->getAuthority(header);
if (authority_index_res.has_error()) {
return;
}
AuthorityIndex authority_index = authority_index_res.value();

auto &hash = header.hash();

auto [it, just_added] = data_for_equvocation_checks_.emplace(
std::tuple(slot, authority_index), std::tuple(hash, false));

if (just_added) { // Newly registered block
return;
}

auto &is_reported = std::get<1>(it->second);
if (is_reported) { // Known equivocation
return;
}

const auto &prev_block_hash = std::get<0>(it->second);
if (prev_block_hash == hash) { // Duplicate
return;
}

auto report_res = consensus->reportEquivocation(prev_block_hash, hash);

if (report_res.has_error()) {
SL_WARN(log_, "Can't report equivocation: {}", report_res.error());
return;
}

is_reported = true;
}

} // namespace kagome::consensus
Loading

0 comments on commit d25c448

Please sign in to comment.