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

audi query test #2311

Merged
merged 7 commits into from
Dec 19, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/authority_discovery/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ add_library(address_publisher
target_link_libraries(address_publisher
authority_discovery_proto
logger
p2p::p2p_kademlia
scale_libp2p_types
sha
)
44 changes: 31 additions & 13 deletions core/authority_discovery/publisher/address_publisher.cpp
Original file line number Diff line number Diff line change
@@ -117,6 +117,26 @@ namespace kagome::authority_discovery {
return outcome::success();
}

OUTCOME_TRY(
raw,
audiEncode(ed_crypto_provider_,
sr_crypto_provider_,
*libp2p_key_,
*libp2p_key_pb_,
peer_info,
*audi_key,
std::chrono::system_clock::now().time_since_epoch()));
return kademlia_->putValue(std::move(raw.first), std::move(raw.second));
}

outcome::result<std::pair<Buffer, Buffer>> audiEncode(
std::shared_ptr<crypto::Ed25519Provider> ed_crypto_provider,
std::shared_ptr<crypto::Sr25519Provider> sr_crypto_provider,
const Ed25519Keypair &libp2p_key,
const ProtobufKey &libp2p_key_pb,
const PeerInfo &peer_info,
const Sr25519Keypair &audi_key,
std::optional<std::chrono::nanoseconds> now) {
std::unordered_set<libp2p::multi::Multiaddress> addresses;
for (const auto &address : peer_info.addresses) {
if (address.getPeerId()) {
@@ -133,27 +153,25 @@ namespace kagome::authority_discovery {
for (const auto &address : addresses) {
PB_SPAN_ADD(record, addresses, address.getBytesAddress());
}
TimestampScale time{std::chrono::nanoseconds{
std::chrono::system_clock::now().time_since_epoch()}
.count()};
PB_SPAN_SET(*record.mutable_creation_time(),
timestamp,
scale::encode(time).value());
if (now) {
TimestampScale time{now->count()};
PB_SPAN_SET(*record.mutable_creation_time(),
timestamp,
scale::encode(time).value());
}

auto record_pb = pbEncodeVec(record);
OUTCOME_TRY(signature, ed_crypto_provider_->sign(*libp2p_key_, record_pb));
OUTCOME_TRY(auth_signature,
sr_crypto_provider_->sign(*audi_key, record_pb));
OUTCOME_TRY(signature, ed_crypto_provider->sign(libp2p_key, record_pb));
OUTCOME_TRY(auth_signature, sr_crypto_provider->sign(audi_key, record_pb));

::authority_discovery_v3::SignedAuthorityRecord signed_record;
PB_SPAN_SET(signed_record, auth_signature, auth_signature);
PB_SPAN_SET(signed_record, record, record_pb);
auto &ps = *signed_record.mutable_peer_signature();
PB_SPAN_SET(ps, signature, signature);
PB_SPAN_SET(ps, public_key, libp2p_key_pb_->key);
PB_SPAN_SET(ps, public_key, libp2p_key_pb.key);

auto hash = crypto::sha256(audi_key->public_key);
return kademlia_->putValue({hash.begin(), hash.end()},
pbEncodeVec(signed_record));
auto hash = crypto::sha256(audi_key.public_key);
return std::make_pair(Buffer{hash}, Buffer{pbEncodeVec(signed_record)});
}
} // namespace kagome::authority_discovery
16 changes: 16 additions & 0 deletions core/authority_discovery/publisher/address_publisher.hpp
Original file line number Diff line number Diff line change
@@ -21,6 +21,22 @@
#include <memory>

namespace kagome::authority_discovery {
using crypto::Ed25519Keypair;
using crypto::Ed25519Provider;
using crypto::Sr25519Keypair;
using crypto::Sr25519Provider;
using libp2p::crypto::ProtobufKey;
using libp2p::peer::PeerInfo;

outcome::result<std::pair<Buffer, Buffer>> audiEncode(
std::shared_ptr<crypto::Ed25519Provider> ed_crypto_provider,
std::shared_ptr<crypto::Sr25519Provider> sr_crypto_provider,
const Ed25519Keypair &libp2p_key,
const ProtobufKey &libp2p_key_pb,
const PeerInfo &peer_info,
const Sr25519Keypair &audi_key,
std::optional<std::chrono::nanoseconds> now);

/**
* Publishes listening addresses for authority discovery.
* Authority discovery public key is used for Kademlia DHT key.
4 changes: 2 additions & 2 deletions core/authority_discovery/query/query_impl.cpp
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ namespace kagome::authority_discovery {
std::shared_ptr<application::AppStateManager> app_state_manager,
std::shared_ptr<blockchain::BlockTree> block_tree,
std::shared_ptr<runtime::AuthorityDiscoveryApi> authority_discovery_api,
LazySPtr<network::ValidationProtocol> validation_protocol,
LazySPtr<network::ValidationProtocolReserve> validation_protocol,
std::shared_ptr<crypto::KeyStore> key_store,
std::shared_ptr<AudiStore> audi_store,
std::shared_ptr<crypto::Sr25519Provider> sr_crypto_provider,
@@ -344,7 +344,7 @@ namespace kagome::authority_discovery {
authority,
AuthorityPeerInfo{
.raw = std::move(signed_record_pb),
.time = time.has_value() ? std::make_optional<TimestampScale>(*time)
.time = time.has_value() ? std::make_optional(TimestampScale{*time})
: std::nullopt,
.peer = peer,
});
6 changes: 3 additions & 3 deletions core/authority_discovery/query/query_impl.hpp
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@
#include <random>

namespace kagome::network {
class ValidationProtocol;
class ValidationProtocolReserve;
} // namespace kagome::network

namespace kagome::authority_discovery {
@@ -47,7 +47,7 @@ namespace kagome::authority_discovery {
std::shared_ptr<application::AppStateManager> app_state_manager,
std::shared_ptr<blockchain::BlockTree> block_tree,
std::shared_ptr<runtime::AuthorityDiscoveryApi> authority_discovery_api,
LazySPtr<network::ValidationProtocol> validation_protocol,
LazySPtr<network::ValidationProtocolReserve> validation_protocol,
std::shared_ptr<crypto::KeyStore> key_store,
std::shared_ptr<AudiStore> audi_store,
std::shared_ptr<crypto::Sr25519Provider> sr_crypto_provider,
@@ -85,7 +85,7 @@ namespace kagome::authority_discovery {

std::shared_ptr<blockchain::BlockTree> block_tree_;
std::shared_ptr<runtime::AuthorityDiscoveryApi> authority_discovery_api_;
LazySPtr<network::ValidationProtocol> validation_protocol_;
LazySPtr<network::ValidationProtocolReserve> validation_protocol_;
std::shared_ptr<crypto::KeyStore> key_store_;
std::shared_ptr<AudiStore> audi_store_;
std::shared_ptr<crypto::Sr25519Provider> sr_crypto_provider_;
5 changes: 3 additions & 2 deletions core/injector/application_injector.cpp
Original file line number Diff line number Diff line change
@@ -47,10 +47,10 @@
#include "application/app_configuration.hpp"
#include "application/impl/app_state_manager_impl.hpp"
#include "application/impl/chain_spec_impl.hpp"
#include "application/modes/key.hpp"
#include "application/modes/precompile_wasm.hpp"
#include "application/modes/print_chain_info_mode.hpp"
#include "application/modes/recovery_mode.hpp"
#include "application/modes/key.hpp"
#include "authority_discovery/publisher/address_publisher.hpp"
#include "authority_discovery/query/audi_store_impl.hpp"
#include "authority_discovery/query/query_impl.hpp"
@@ -877,6 +877,7 @@ namespace {
di::bind<crypto::SessionKeys>.template to<crypto::SessionKeysImpl>(),
di::bind<network::SyncProtocol>.template to<network::SyncProtocolImpl>(),
di::bind<network::StateProtocol>.template to<network::StateProtocolImpl>(),
di::bind<network::ValidationProtocolReserve>.template to<network::ValidationProtocol>(),
di::bind<network::BeefyProtocol>.template to<network::BeefyProtocolImpl>(),
di::bind<consensus::beefy::FetchJustification>.template to<network::BeefyJustificationProtocol>(),
di::bind<network::Beefy>.template to<network::BeefyImpl>(),
@@ -942,7 +943,7 @@ namespace kagome::injector {
KagomeNodeInjector::KagomeNodeInjector(
sptr<application::AppConfiguration> app_config)
: pimpl_{std::make_unique<KagomeNodeInjectorImpl>(
makeKagomeNodeInjector(std::move(app_config)))} {}
makeKagomeNodeInjector(std::move(app_config)))} {}

sptr<application::AppConfiguration> KagomeNodeInjector::injectAppConfig() {
return pimpl_->injector_
12 changes: 10 additions & 2 deletions core/network/impl/protocols/parachain.hpp
Original file line number Diff line number Diff line change
@@ -95,7 +95,15 @@ namespace kagome::network {
std::shared_ptr<CollationObserver> observer_;
};

class ValidationProtocol final : public ParachainProtocol {
class ValidationProtocolReserve {
public:
virtual ~ValidationProtocolReserve() = default;

virtual void reserve(const PeerId &peer_id, bool add) = 0;
};

class ValidationProtocol final : public ParachainProtocol,
public ValidationProtocolReserve {
public:
ValidationProtocol(ParachainProtocolInject inject,
std::shared_ptr<ValidationObserver> observer);
@@ -122,7 +130,7 @@ namespace kagome::network {
}
}
void write(const BitfieldDistribution &message);
void reserve(const PeerId &peer_id, bool add);
void reserve(const PeerId &peer_id, bool add) override;

private:
std::shared_ptr<ValidationObserver> observer_;
10 changes: 10 additions & 0 deletions test/core/authority_discovery/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -13,3 +13,13 @@ target_link_libraries(address_publisher_test
logger_for_tests
network
)

addtest(audi_query_test
query.cpp
)
target_link_libraries(audi_query_test
address_publisher
in_memory_storage
key_store
logger_for_tests
)
240 changes: 240 additions & 0 deletions test/core/authority_discovery/query.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#include "authority_discovery/query/query_impl.hpp"

#include <gtest/gtest.h>
#include <mock/libp2p/basic/scheduler_mock.hpp>
#include <mock/libp2p/crypto/key_marshaller_mock.hpp>
#include <mock/libp2p/host/host_mock.hpp>
#include <mock/libp2p/peer/address_repository_mock.hpp>
#include <mock/libp2p/peer/peer_repository_mock.hpp>

#include "authority_discovery/publisher/address_publisher.hpp"
#include "authority_discovery/query/audi_store_impl.hpp"
#include "mock/core/application/app_state_manager_mock.hpp"
#include "mock/core/blockchain/block_tree_mock.hpp"
#include "mock/core/crypto/ed25519_provider_mock.hpp"
#include "mock/core/crypto/key_store_mock.hpp"
#include "mock/core/crypto/sr25519_provider_mock.hpp"
#include "mock/core/network/protocols/parachain.hpp"
#include "mock/core/runtime/authority_discovery_api_mock.hpp"
#include "mock/libp2p/crypto/crypto_provider.hpp"
#include "mock/libp2p/protocol/kademlia/kademlia_mock.hpp"
#include "testutil/lazy.hpp"
#include "testutil/literals.hpp"
#include "testutil/prepare_loggers.hpp"
#include "testutil/storage/in_memory/in_memory_spaced_storage.hpp"

using kagome::application::AppStateManagerMock;
using kagome::authority_discovery::audiEncode;
using kagome::authority_discovery::AudiStoreImpl;
using kagome::authority_discovery::QueryImpl;
using kagome::blockchain::BlockTreeMock;
using kagome::crypto::Ed25519ProviderMock;
using kagome::crypto::KeyStoreMock;
using kagome::crypto::Sr25519Keypair;
using kagome::crypto::Sr25519ProviderMock;
using kagome::network::ValidationProtocolReserve;
using kagome::network::ValidationProtocolReserveMock;
using kagome::runtime::AuthorityDiscoveryApiMock;
using kagome::storage::InMemorySpacedStorage;
using libp2p::HostMock;
using libp2p::Multiaddress;
using libp2p::PeerId;
using libp2p::basic::SchedulerMock;
using libp2p::crypto::CryptoProviderMock;
using libp2p::crypto::ProtobufKey;
using libp2p::crypto::marshaller::KeyMarshallerMock;
using libp2p::peer::AddressRepositoryMock;
using libp2p::peer::PeerInfo;
using libp2p::peer::PeerRepositoryMock;
using libp2p::protocol::kademlia::Kademlia;
using libp2p::protocol::kademlia::KademliaMock;
using testing::_;
using testing::Return;
using testing::ReturnRef;
using testutil::sptr_to_lazy;

struct QueryTest : testing::Test {
static void SetUpTestCase() {
testutil::prepareLoggers();
}

void SetUp() override {
auto app_state_manager = std::make_shared<AppStateManagerMock>();
EXPECT_CALL(*app_state_manager, atLaunch(_));
EXPECT_CALL(*block_tree_, bestBlock());
EXPECT_CALL(*api_, authorities(_))
.WillRepeatedly(Return(std::vector{audi_key_.public_key}));
EXPECT_CALL(*validation_protocol_, reserve(_, true))
.Times(testing::AnyNumber())
.WillRepeatedly([this](const PeerId &peer_id, bool) {
reserved_.emplace(peer_id);
});
EXPECT_CALL(key_store_->sr25519(), getPublicKeys(_))
.WillRepeatedly(Return(outcome::success()));
EXPECT_CALL(*sr25519_provider_, sign(_, _))
.WillRepeatedly(Return(outcome::success()));
EXPECT_CALL(*sr25519_provider_, verify(_, _, _)).WillRepeatedly([this] {
return sig_ok_;
});
EXPECT_CALL(*libp2p_crypto_provider_, verify(_, _, _))
.WillRepeatedly([this] { return sig_ok_; });
EXPECT_CALL(*ed25519_provider_, sign(_, _))
.WillRepeatedly(Return(outcome::success()));
EXPECT_CALL(*key_marshaller_, unmarshalPublicKey(_))
.WillRepeatedly(Return(outcome::success()));
EXPECT_CALL(*host_, getId()).WillRepeatedly(Return("b"_peerid));
EXPECT_CALL(*host_, getPeerRepository())
.WillRepeatedly(ReturnRef(peer_repo_));
EXPECT_CALL(peer_repo_, getAddressRepository())
.WillRepeatedly(ReturnRef(address_repo_));
EXPECT_CALL(address_repo_, addAddresses(_, _, _))
.WillRepeatedly(Return(outcome::success()));
EXPECT_CALL(*scheduler_, scheduleImpl(_, _, _));
query_ = std::make_shared<QueryImpl>(
app_state_manager,
block_tree_,
api_,
sptr_to_lazy<ValidationProtocolReserve>(validation_protocol_),
key_store_,
audi_store_,
sr25519_provider_,
libp2p_crypto_provider_,
key_marshaller_,
*host_,
sptr_to_lazy<Kademlia>(kademlia_),
scheduler_);
query_->update().value();
}

auto info(size_t i) {
auto addr = Multiaddress::create(
fmt::format("/tcp/{}/p2p/{}", i, peer_id_.toBase58()))
.value();
return PeerInfo{peer_id_, {addr}};
}

void receive(size_t i, std::optional<int> time) {
auto raw =
audiEncode(ed25519_provider_,
sr25519_provider_,
{},
key_pb_,
info(i),
audi_key_,
time ? std::make_optional(std::chrono::nanoseconds{*time})
: std::nullopt)
.value();
std::ignore = query_->validate(raw.first, raw.second);
}

void expect(std::optional<size_t> i) {
auto r = query_->get(audi_key_.public_key);
EXPECT_EQ(r, i ? std::make_optional(info(*i)) : std::nullopt);
}

std::shared_ptr<BlockTreeMock> block_tree_ =
std::make_shared<BlockTreeMock>();
std::shared_ptr<AuthorityDiscoveryApiMock> api_ =
std::make_shared<AuthorityDiscoveryApiMock>();
std::shared_ptr<ValidationProtocolReserveMock> validation_protocol_ =
std::make_shared<ValidationProtocolReserveMock>();
std::shared_ptr<KeyStoreMock> key_store_ = std::make_shared<KeyStoreMock>();
std::shared_ptr<AudiStoreImpl> audi_store_ = std::make_shared<AudiStoreImpl>(
std::make_shared<InMemorySpacedStorage>());
std::shared_ptr<Sr25519ProviderMock> sr25519_provider_ =
std::make_shared<Sr25519ProviderMock>();
std::shared_ptr<CryptoProviderMock> libp2p_crypto_provider_ =
std::make_shared<CryptoProviderMock>();
std::shared_ptr<KeyMarshallerMock> key_marshaller_ =
std::make_shared<KeyMarshallerMock>();
std::shared_ptr<HostMock> host_ = std::make_shared<HostMock>();
std::shared_ptr<KademliaMock> kademlia_ = std::make_shared<KademliaMock>();
std::shared_ptr<SchedulerMock> scheduler_ = std::make_shared<SchedulerMock>();
std::shared_ptr<QueryImpl> query_;
Sr25519Keypair audi_key_;
ProtobufKey key_pb_{{0, 1}};
PeerId peer_id_ = PeerId::fromPublicKey(key_pb_).value();
std::shared_ptr<Ed25519ProviderMock> ed25519_provider_ =
std::make_shared<Ed25519ProviderMock>();
AddressRepositoryMock address_repo_;
PeerRepositoryMock peer_repo_;
bool sig_ok_ = true;
std::set<PeerId> reserved_;
};

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/polkadot/node/network/gossip-support/src/tests.rs#L812
/**
* @given record about peer
* @when receive record
* @then connect to peer
*/
TEST_F(QueryTest, test_quickly_connect_to_authorities_that_changed_address) {
receive(1, std::nullopt);
EXPECT_TRUE(reserved_.contains(peer_id_));
}

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/substrate/client/authority-discovery/src/worker/tests.rs#L707
/**
* @given record without timestamp
* @when receive record
* @then record is inserted
*/
TEST_F(QueryTest, strict_accept_address_without_creation_time) {
expect(std::nullopt);
receive(1, std::nullopt);
expect(1);
}

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/substrate/client/authority-discovery/src/worker/tests.rs#L728
/**
* @given old record
* @when receive new record
* @then new record overwrites old record
*/
TEST_F(QueryTest, keep_last_received_if_no_creation_time) {
receive(1, std::nullopt);
receive(2, std::nullopt);
expect(2);
}

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/substrate/client/authority-discovery/src/worker/tests.rs#L775
/**
* @given record with invalid signature
* @when receive record
* @then record ignored
*/
TEST_F(QueryTest, records_with_incorrectly_signed_creation_time_are_ignored) {
sig_ok_ = false;
receive(1, std::nullopt);
expect(std::nullopt);
}

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/substrate/client/authority-discovery/src/worker/tests.rs#L829
/**
* @given old record
* @when receive new record
* @then new record overwrites old record
*/
TEST_F(QueryTest, newer_records_overwrite_older_ones) {
receive(1, 1);
receive(2, 2);
expect(2);
}

// https://github.com/paritytech/polkadot-sdk/blob/da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306/substrate/client/authority-discovery/src/worker/tests.rs#L880
/**
* @given new record
* @when receive old record
* @then old record is ignored
*/
TEST_F(QueryTest, older_records_dont_affect_newer_ones) {
receive(2, 2);
receive(1, 1);
expect(2);
}
7 changes: 6 additions & 1 deletion test/mock/core/crypto/key_store_mock.hpp
Original file line number Diff line number Diff line change
@@ -58,7 +58,12 @@ namespace kagome::crypto {
std::make_unique<KeySuiteStoreMock<EcdsaProvider>>(),
std::make_unique<KeySuiteStoreMock<BandersnatchProvider>>(),
std::make_shared<Ed25519ProviderMock>(),
std::make_shared<application::AppStateManagerMock>(),
[] {
auto app_state_manager =
std::make_shared<application::AppStateManagerMock>();
EXPECT_CALL(*app_state_manager, atPrepare(testing::_));
return app_state_manager;
}(),
KeyStore::Config{{}}},
sr25519_{dynamic_cast<KeySuiteStoreMock<Sr25519Provider> &>(
KeyStore ::sr25519())},
18 changes: 18 additions & 0 deletions test/mock/core/network/protocols/parachain.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include "network/impl/protocols/parachain.hpp"

#include <gmock/gmock.h>

namespace kagome::network {
class ValidationProtocolReserveMock : public ValidationProtocolReserve {
public:
MOCK_METHOD(void, reserve, (const PeerId &, bool), (override));
};
} // namespace kagome::network
45 changes: 45 additions & 0 deletions test/mock/libp2p/crypto/crypto_provider.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <libp2p/crypto/crypto_provider.hpp>

#include <gmock/gmock.h>

namespace libp2p::crypto {
class CryptoProviderMock : public CryptoProvider {
public:
MOCK_METHOD(outcome::result<KeyPair>,
generateKeys,
(Key::Type, common::RSAKeyType),
(const, override));

MOCK_METHOD(outcome::result<PublicKey>,
derivePublicKey,
(const PrivateKey &),
(const, override));

MOCK_METHOD(outcome::result<Buffer>,
sign,
(BytesIn, const PrivateKey &),
(const, override));

MOCK_METHOD(outcome::result<bool>,
verify,
(BytesIn, BytesIn, const PublicKey &),
(const, override));
MOCK_METHOD(outcome::result<EphemeralKeyPair>,
generateEphemeralKeyPair,
(common::CurveType),
(const, override));

MOCK_METHOD((outcome::result<std::pair<StretchedKey, StretchedKey>>),
stretchKey,
(common::CipherType, common::HashType, const Buffer &),
(const, override));
};
} // namespace libp2p::crypto