Skip to content

Commit

Permalink
parachain pvf (#1383)
Browse files Browse the repository at this point in the history
Signed-off-by: turuslan <[email protected]>
  • Loading branch information
turuslan authored Nov 18, 2022
1 parent 96ab3f7 commit 74d5c47
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 44 deletions.
1 change: 1 addition & 0 deletions core/common/visitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef KAGOME_VISITOR_HPP
#define KAGOME_VISITOR_HPP

#include <optional>
#include <type_traits> // for std::decay
#include <utility> // for std::forward

Expand Down
4 changes: 2 additions & 2 deletions core/injector/application_injector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
#include "outcome/outcome.hpp"
#include "parachain/availability/bitfield/store_impl.hpp"
#include "parachain/availability/store/store_impl.hpp"
#include "parachain/pvf/pvf_impl.hpp"
#include "parachain/validator/parachain_observer.hpp"
#include "parachain/validator/parachain_processor.hpp"
#include "runtime/binaryen/binaryen_memory_provider.hpp"
Expand Down Expand Up @@ -866,7 +867,6 @@ namespace {
application::AppConfiguration::RuntimeExecutionMethod method,
Ts &&...args) {
return di::make_injector(
di::bind<runtime::TrieStorageProvider>.template to<runtime::TrieStorageProviderImpl>(),
di::bind<runtime::RuntimeUpgradeTrackerImpl>.template to(
[](auto const &injector) {
return get_runtime_upgrade_tracker(injector);
Expand Down Expand Up @@ -1186,7 +1186,6 @@ namespace {
return get_key_file_storage(config, chain_spec);
}),
di::bind<crypto::CryptoStore>.template to<crypto::CryptoStoreImpl>(),
di::bind<host_api::HostApi>.template to<host_api::HostApiImpl>(),
di::bind<host_api::HostApiFactory>.template to<host_api::HostApiFactoryImpl>(),
makeRuntimeInjector(config.runtimeExecMethod()),
di::bind<transaction_pool::TransactionPool>.template to<transaction_pool::TransactionPoolImpl>(),
Expand All @@ -1196,6 +1195,7 @@ namespace {
bind_by_lambda<network::SyncProtocolObserver>(get_sync_observer_impl),
di::bind<parachain::AvailabilityStore>.template to<parachain::AvailabilityStoreImpl>(),
di::bind<parachain::BitfieldStore>.template to<parachain::BitfieldStoreImpl>(),
di::bind<parachain::Pvf>.template to<parachain::PvfImpl>(),
di::bind<parachain::ParachainObserverImpl>.to([](auto const &injector) {
return get_parachain_observer_impl(injector);
}),
Expand Down
11 changes: 11 additions & 0 deletions core/network/types/collator_messages.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ namespace kagome::network {
primitives::BlockHash
para_head_hash; /// Hash of the parachain head data of this candidate.
primitives::BlockHash para_runtime_hash; /// Hash of the parachain Runtime.

common::Buffer signable() const {
return common::Buffer{
scale::encode(relay_parent,
para_id,
persisted_data_hash,
pov_hash,
para_runtime_hash)
.value(),
};
}
};

/**
Expand Down
1 change: 1 addition & 0 deletions core/parachain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_library(validator_parachain
availability/bitfield/signer.cpp
availability/bitfield/store_impl.cpp
availability/store/store_impl.cpp
pvf/pvf_impl.cpp
validator/impl/parachain_observer.cpp
validator/impl/parachain_processor.cpp
validator/signer.cpp
Expand Down
30 changes: 30 additions & 0 deletions core/parachain/pvf/pvf.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef KAGOME_PARACHAIN_PVF_PVF_HPP
#define KAGOME_PARACHAIN_PVF_PVF_HPP

#include "network/types/collator_messages.hpp"
#include "runtime/runtime_api/parachain_host_types.hpp"

namespace kagome::parachain {
/// Executes pvf (Parachain Validation Function, wasm)
class Pvf {
public:
using CandidateReceipt = network::CandidateReceipt;
using ParachainBlock = network::ParachainBlock;
using CandidateCommitments = network::CandidateCommitments;
using PersistedValidationData = runtime::PersistedValidationData;
using Result = std::pair<CandidateCommitments, PersistedValidationData>;

virtual ~Pvf() = default;

/// Execute pvf synchronously
virtual outcome::result<Result> pvfSync(
const CandidateReceipt &receipt, const ParachainBlock &pov) const = 0;
};
} // namespace kagome::parachain

#endif // KAGOME_PARACHAIN_PVF_PVF_HPP
233 changes: 233 additions & 0 deletions core/parachain/pvf/pvf_impl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/**
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#include "parachain/pvf/pvf_impl.hpp"

#include "runtime/common/executor.hpp"
#include "runtime/common/uncompress_code_if_needed.hpp"
#include "runtime/module.hpp"
#include "runtime/runtime_code_provider.hpp"

OUTCOME_CPP_DEFINE_CATEGORY(kagome::parachain, PvfError, e) {
using kagome::parachain::PvfError;
switch (e) {
case PvfError::NO_PERSISTED_DATA:
return "PersistedValidationData was not found";
case PvfError::POV_SIZE:
return "PoV is too big";
case PvfError::POV_HASH:
return "PoV hash mismatch";
case PvfError::CODE_HASH:
return "Code hash mismatch";
case PvfError::SIGNATURE:
return "Signature is invalid";
case PvfError::HEAD_HASH:
return "Head hash mismatch";
case PvfError::COMMITMENTS_HASH:
return "Commitments hash mismatch";
case PvfError::OUTPUTS:
return "ValidationResult is invalid";
}
return fmt::format("PvfError({})", e);
}

namespace kagome::parachain {
using common::Hash256;
using network::HeadData;
using network::OutboundHorizontal;
using network::ParachainBlock;
using network::ParachainRuntime;
using network::UpwardMessage;
using primitives::BlockNumber;
using runtime::PersistedValidationData;

struct DontProvideCode : runtime::RuntimeCodeProvider {
outcome::result<gsl::span<const uint8_t>> getCodeAt(
const storage::trie::RootHash &) const override {
abort();
}
};

struct ReturnModuleInstance : runtime::ModuleRepository {
ReturnModuleInstance(std::shared_ptr<runtime::ModuleInstance> instance)
: instance{std::move(instance)} {}

outcome::result<std::shared_ptr<runtime::ModuleInstance>> getInstanceAt(
std::shared_ptr<const runtime::RuntimeCodeProvider>,
const primitives::BlockInfo &,
const primitives::BlockHeader &) override {
return instance;
}

std::shared_ptr<runtime::ModuleInstance> instance;
};

struct ValidationParams {
SCALE_TIE(4);

HeadData parent_head;
ParachainBlock block_data;
BlockNumber relay_parent_number;
Hash256 relay_parent_storage_root;
};

struct ValidationResult {
SCALE_TIE(6);

HeadData head_data;
std::optional<ParachainRuntime> new_validation_code;
std::vector<UpwardMessage> upward_messages;
std::vector<OutboundHorizontal> horizontal_messages;
uint32_t processed_downward_messages;
BlockNumber hrmp_watermark;
};

PvfImpl::PvfImpl(
std::shared_ptr<crypto::Hasher> hasher,
std::shared_ptr<runtime::ModuleFactory> module_factory,
std::shared_ptr<runtime::RuntimePropertiesCache> runtime_properties_cache,
std::shared_ptr<blockchain::BlockHeaderRepository>
block_header_repository,
std::shared_ptr<crypto::Sr25519Provider> sr25519_provider,
std::shared_ptr<runtime::ParachainHost> parachain_api)
: hasher_{std::move(hasher)},
module_factory_{std::move(module_factory)},
runtime_properties_cache_{std::move(runtime_properties_cache)},
block_header_repository_{std::move(block_header_repository)},
sr25519_provider_{std::move(sr25519_provider)},
parachain_api_{std::move(parachain_api)},
log_{log::createLogger("Pvf")} {}

outcome::result<Pvf::Result> PvfImpl::pvfSync(
const CandidateReceipt &receipt, const ParachainBlock &pov) const {
SL_DEBUG(log_,
"pvfSync relay_parent={} para_id={}",
receipt.descriptor.relay_parent,
receipt.descriptor.para_id);
OUTCOME_TRY(data_code, findData(receipt.descriptor));
auto &[data, code] = data_code;
OUTCOME_TRY(pov_encoded, scale::encode(pov));
if (pov_encoded.size() > data.max_pov_size) {
return PvfError::POV_SIZE;
}
auto pov_hash = hasher_->blake2b_256(pov_encoded);
if (pov_hash != receipt.descriptor.pov_hash) {
return PvfError::POV_HASH;
}
auto code_hash = hasher_->blake2b_256(code);
if (code_hash != receipt.descriptor.para_runtime_hash) {
return PvfError::CODE_HASH;
}
OUTCOME_TRY(signature_valid,
sr25519_provider_->verify(receipt.descriptor.signature,
receipt.descriptor.signable(),
receipt.descriptor.collator_id));
if (!signature_valid) {
return PvfError::SIGNATURE;
}

ValidationParams params;
params.parent_head = data.parent_head;
OUTCOME_TRY(runtime::uncompressCodeIfNeeded(pov.payload,
params.block_data.payload));
params.relay_parent_number = data.relay_parent_number;
params.relay_parent_storage_root = data.relay_parent_storage_root;
OUTCOME_TRY(result, callWasm(code, params));

OUTCOME_TRY(commitments, fromOutputs(receipt, std::move(result)));
return std::make_pair(std::move(commitments), std::move(data));
}

outcome::result<std::pair<PersistedValidationData, ParachainRuntime>>
PvfImpl::findData(const CandidateDescriptor &descriptor) const {
for (auto assumption : {
runtime::OccupiedCoreAssumption::Included,
runtime::OccupiedCoreAssumption::TimedOut,
}) {
OUTCOME_TRY(data,
parachain_api_->persisted_validation_data(
descriptor.relay_parent, descriptor.para_id, assumption));
if (!data) {
SL_VERBOSE(log_,
"findData relay_parent={} para_id={}: not found "
"(persisted_validation_data)",
descriptor.relay_parent,
descriptor.para_id);
return PvfError::NO_PERSISTED_DATA;
}
auto data_hash = hasher_->blake2b_256(scale::encode(*data).value());
if (descriptor.persisted_data_hash != data_hash) {
continue;
}
OUTCOME_TRY(code,
parachain_api_->validation_code(
descriptor.relay_parent, descriptor.para_id, assumption));
if (!code) {
SL_VERBOSE(
log_,
"findData relay_parent={} para_id={}: not found (validation_code)",
descriptor.relay_parent,
descriptor.para_id);
return PvfError::NO_PERSISTED_DATA;
}
return std::make_pair(*data, *code);
}
SL_VERBOSE(log_,
"findData relay_parent={} para_id={}: not found",
descriptor.relay_parent,
descriptor.para_id);
return PvfError::NO_PERSISTED_DATA;
}

outcome::result<ValidationResult> PvfImpl::callWasm(
const ParachainRuntime &code_zstd, const ValidationParams &params) const {
ParachainRuntime code;
OUTCOME_TRY(runtime::uncompressCodeIfNeeded(code_zstd, code));
OUTCOME_TRY(module, module_factory_->make(code));
OUTCOME_TRY(instance, module->instantiate());
auto env_factory = std::make_shared<runtime::RuntimeEnvironmentFactory>(
std::make_shared<DontProvideCode>(),
std::make_shared<ReturnModuleInstance>(instance),
block_header_repository_);
auto executor = std::make_unique<runtime::Executor>(
env_factory, runtime_properties_cache_);
return executor->callAtGenesis<ValidationResult>("validate_block", params);
}

outcome::result<Pvf::CandidateCommitments> PvfImpl::fromOutputs(
const CandidateReceipt &receipt, ValidationResult &&result) const {
auto head_hash = hasher_->blake2b_256(result.head_data);
if (head_hash != receipt.descriptor.para_head_hash) {
return PvfError::HEAD_HASH;
}
CandidateCommitments commitments{
.upward_msgs = std::move(result.upward_messages),
.outbound_hor_msgs = std::move(result.horizontal_messages),
.opt_para_runtime = std::move(result.new_validation_code),
.para_head = std::move(result.head_data),
.downward_msgs_count = result.processed_downward_messages,
.watermark = result.hrmp_watermark,
};
auto commitments_hash =
hasher_->blake2b_256(scale::encode(commitments).value());
if (commitments_hash != receipt.commitments_hash) {
return PvfError::COMMITMENTS_HASH;
}
OUTCOME_TRY(valid,
parachain_api_->check_validation_outputs(
receipt.descriptor.relay_parent,
receipt.descriptor.para_id,
commitments));
if (!valid) {
SL_VERBOSE(log_,
"fromOutputs relay_parent={} para_id={}: invalid "
"(check_validation_outputs)",
receipt.descriptor.relay_parent,
receipt.descriptor.para_id);
return PvfError::OUTPUTS;
}
return commitments;
}
} // namespace kagome::parachain
Loading

0 comments on commit 74d5c47

Please sign in to comment.