Skip to content

Commit

Permalink
statement grid (#1891)
Browse files Browse the repository at this point in the history
Signed-off-by: turuslan <turuslan.devbox@gmail.com>
  • Loading branch information
turuslan authored Dec 14, 2023
1 parent 357fb81 commit 6290884
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 0 deletions.
80 changes: 80 additions & 0 deletions core/crypto/chacha.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <openssl/evp.h>
#include <array>
#include <cstdint>
#include <memory>
#include <span>

namespace kagome::crypto {
/// https://github.com/rust-random/rand
class RandChaCha20 {
public:
RandChaCha20(std::span<const uint8_t, 32> seed)
: ctx_{EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free},
index_{block_.size()} {
check(EVP_EncryptInit(ctx_.get(), EVP_chacha20(), seed.data(), nullptr));
}

template <std::ranges::contiguous_range R>
void shuffle(R &&items) {
[[unlikely]] if (items.empty()) { return; }
for (auto i = items.size() - 1; i != 0; --i) {
std::swap(items[i], items[next(i + 1)]);
}
}

private:
void check(int r) {
if (r != 1) {
throw std::logic_error{"RandChaCha20"};
}
}

using Block = std::array<uint32_t, 64>;
Block block() {
std::array<uint8_t, sizeof(Block)> zero{};
Block r;
int len = 0;
check(EVP_CipherUpdate(ctx_.get(),
reinterpret_cast<uint8_t *>(r.data()),
&len,
zero.data(),
zero.size()));
if (len != zero.size()) {
throw std::logic_error{"RandChaCha20"};
}
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
for (auto &x : r) {
x = __builtin_bswap32(x);
}
#endif
return r;
}

uint32_t next(uint32_t n) {
auto zone = (n << __builtin_clz(n)) - 1;
while (true) {
if (index_ >= block_.size()) {
block_ = block();
index_ = 0;
}
uint64_t raw = block_[index_++];
auto mul = raw * n;
if ((mul & 0xffffffff) <= zone) {
return mul >> 32;
}
}
}

std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX *)> ctx_;
Block block_;
size_t index_;
};
} // namespace kagome::crypto
178 changes: 178 additions & 0 deletions core/parachain/backing/grid.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <numeric>
#include <unordered_set>
#include <vector>

#include "crypto/hasher/hasher_impl.hpp"
#include "parachain/backing/chacha.hpp"

namespace kagome::parachain::grid {
/**
* Numbers arranged into rectangular grid.
* https://github.com/paritytech/polkadot-sdk/blob/2aaa9af3746b0cf671de9dc98fe2465c7ef59be2/polkadot/node/network/protocol/src/grid_topology.rs#L69-L149
*/
struct Grid {
Grid(size_t count) : count{count}, width(std::sqrt(count)) {}

void cross(size_t center, auto &&f) const {
auto c = _split(center);
_vertical(center, c.second, f);
_horizontal(center, c.first, f);
}

void project(size_t center, size_t other, auto &&f) const {
[[unlikely]] if (center == other) { return; }
auto c = _split(center), o = _split(other);
[[unlikely]] if (c.first == o.first || c.second == o.second) {
f(other);
return;
}
if (auto i = c.first + o.second; i < count) {
f(i);
}
if (auto i = o.first + c.second; i < count) {
f(i);
}
}

bool orthogonal(size_t center, size_t other, auto &&f) const {
[[unlikely]] if (center == other) { return false; }
auto c = _split(center), o = _split(other);
if (c.first == o.first) {
_vertical(center, c.second, f);
return true;
}
if (c.second == o.second) {
_horizontal(center, c.first, f);
return true;
}
return false;
}

std::pair<size_t, size_t> _split(size_t i) const {
[[unlikely]] if (i > count) { throw std::range_error{"Grid"}; }
auto x = i % width;
return {i - x, x};
}

void _vertical(size_t center, size_t x, auto &&f) const {
for (size_t i = x; i < count; i += width) {
if (i != center) {
f(i);
}
}
}

void _horizontal(size_t center, size_t min_x, auto &&f) const {
auto max_x = std::min(min_x + width, count);
for (size_t i = min_x; i != max_x; ++i) {
if (i != center) {
f(i);
}
}
}

size_t count, width;
};

using ValidatorIndex = uint32_t;

/// View for one group
struct View {
std::unordered_set<ValidatorIndex> receiving, sending;

bool canReceive(bool full, ValidatorIndex from) const {
return (full ? receiving : sending).contains(from);
}

void sendTo(bool full, auto &&f) const {
if (full) {
for (auto &i : sending) {
if (not receiving.contains(i)) {
f(i);
}
}
} else {
for (auto &i : receiving) {
f(i);
}
}
}
};

/// View for each group
using Views = std::vector<View>;

/// Make view for each group
inline Views makeViews(const std::vector<std::vector<ValidatorIndex>> &groups,
const std::vector<ValidatorIndex> &validators,
ValidatorIndex center) {
Views views;
views.reserve(groups.size());
std::vector<size_t> index;
index.resize(validators.size());
for (size_t i = 0; auto &v : validators) {
index[v] = i++;
}
auto as_value = [&](auto &&f) {
return [&, f{std::forward<decltype(f)>(f)}](size_t i) mutable {
f(validators[i]);
};
};
Grid grid{validators.size()};
for (auto &group : groups) {
auto in_group = [&](ValidatorIndex v) {
return std::find(group.begin(), group.end(), v) != group.end();
};
auto &view = views.emplace_back();
if (in_group(center)) {
grid.cross(index[center], as_value([&](ValidatorIndex v) {
if (not in_group(v)) {
view.sending.emplace(v);
}
}));
continue;
}
for (auto &v : group) {
grid.project(index[center], index[v], as_value([&](ValidatorIndex v) {
view.receiving.emplace(v);
}));
grid.orthogonal(
index[center], index[v], as_value([&](ValidatorIndex v) {
if (not in_group(v)) {
view.sending.emplace(v);
}
}));
}
}
return views;
}

inline std::vector<ValidatorIndex> shuffle(
const std::vector<std::vector<ValidatorIndex>> &groups,
std::span<const uint8_t, 32> babe_randomness) {
size_t n = 0;
for (auto &group : groups) {
n += group.size();
}
std::vector<ValidatorIndex> validators;
validators.resize(n);
std::iota(validators.begin(), validators.end(), 0);
std::array<uint8_t, 8 + 32> subject;
memcpy(subject.data(), "gossipsu", 8);
memcpy(subject.data() + 8, babe_randomness.data(), 32);
auto seed = crypto::HasherImpl{}.blake2b_256(subject);
crypto::RandChaCha20{seed}.shuffle(validators);
return validators;
}
} // namespace kagome::parachain::grid

0 comments on commit 6290884

Please sign in to comment.