Files
chia-blockchain/lib/chiapos/tests/test.cpp
T
Mariano SorgenteandGitHub 55192f257b Fix pos (#45)
* Attempt to fix PoS issue, changing to uint32
* Change more variables to 32 bits
* Script to check plots
2019-12-10 08:10:43 +09:00

636 lines
21 KiB
C++

// Copyright 2018 Chia Network Inc
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <set>
#include <stdio.h>
#include "../lib/include/catch.hpp"
#include "../lib/include/picosha2.hpp"
#include "calculate_bucket.hpp"
#include "plotter_disk.hpp"
#include "sort_on_disk.hpp"
#include "prover_disk.hpp"
#include "verifier.hpp"
#include "encoding.hpp"
using namespace std;
uint8_t plot_id_1[] = {35, 2, 52, 4, 51, 55, 23, 84, 91, 10, 111, 12, 13,
222, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10,
11, 129, 139, 171, 15, 23};
uint8_t plot_id_3[] = {5, 104, 52, 4, 51, 55, 23, 84, 91, 10, 111, 12, 13,
222, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10,
11, 129, 139, 171, 15, 23};
vector<unsigned char> intToBytes(uint32_t paramInt, uint32_t numBytes) {
vector<unsigned char> arrayOfByte(numBytes, 0);
for (uint32_t i = 0; paramInt > 0; i++) {
arrayOfByte[numBytes - i - 1] = paramInt & 0xff;
paramInt >>= 8;
}
return arrayOfByte;
}
TEST_CASE("Util") {
SECTION("Increment and decrement") {
uint8_t bytes[3] = {45, 172, 225};
REQUIRE(Util::SliceInt64FromBytes(bytes, 3, 2, 19) == 374172);
uint8_t bytes2[1] = {213};
REQUIRE(Util::SliceInt64FromBytes(bytes2, 1, 1, 5) == 21);
}
}
TEST_CASE("Bits") {
SECTION("Increment and decrement") {
Bits a = Bits(5, 3);
Bits b = Bits(2, 10);
cout << "A is: " << a << endl;
cout << "B is: " << b << endl;
++b;
++b;
++b;
++b;
cout << "B is: " << b << endl;
++a;
cout << "A is: " << a << endl;
++a;
cout << "A is: " << a << endl;
cout << a + b + a << endl;
--a;
cout << "A is: " << a << endl;
Bits c = a++;
cout << "C is: " << c << endl;
cout << "A is: " << a << endl;
Bits d = a--;
cout << "D is: " << d << endl;
cout << "A is: " << a << endl;
Bits e;
Bits f = Bits(3, 5);
cout << e + f + e + d << endl;
}
SECTION("Slicing and manipulating") {
Bits g = Bits(13271, 15);
cout << "G: " << g << endl;
cout << "G Slice: " << g.Slice(4, 9) << endl;
cout << "G Slice: " << g.Slice(0, 9) << endl;
cout << "G Slice: " << g.Slice(9, 10) << endl;
cout << "G Slice: " << g.Slice(9, 15) << endl;
cout << "G Slice: " << g.Slice(9, 9) << endl;
REQUIRE(g.Slice(9, 9) == Bits());
uint8_t bytes[2];
g.ToBytes(bytes);
cout << "bytes: " << static_cast<int>(bytes[0]) << " " << static_cast<int>(bytes[1]) << endl;
cout << "Back to Bits: " << Bits(bytes, 2, 16) << endl;
Bits(256, 9).ToBytes(bytes);
cout << "bytes: " << static_cast<int>(bytes[0]) << " " << static_cast<int>(bytes[1]) << endl;
cout << "Back to Bits: " << Bits(bytes, 2, 16) << endl;
cout << Bits(640, 11) << endl;
Bits(640, 11).ToBytes(bytes);
cout << "bytes: " << static_cast<int>(bytes[0]) << " " << static_cast<int>(bytes[1]) << endl;
Bits h = Bits(bytes, 2, 16);
Bits i = Bits(bytes, 2, 17);
cout << "H: " << h << endl;
cout << "I: " << i << endl;
Bits j = Bits(11, 5);
Bits k1 = Bits(7, 5);
cout << "j" << j << endl;
cout << "k" << k1 << endl;
cout << "Xor:" << (j ^ k1) << endl;
cout << "G: " << g << endl;
cout << "size: " << g.GetSize() << endl;
Bits shifted = (g << 150);
REQUIRE(shifted.GetSize() == 15);
REQUIRE(shifted.ToString() == "000000000000000");
Bits large = Bits(13271, 200);
REQUIRE(large == ((large << 160)) >> 160);
REQUIRE((large << 160).GetSize() == 200);
Bits l = Bits(123287490, 20);
l = l + Bits(0, 5);
Bits m = Bits(5, 3);
uint8_t buf[1];
m.ToBytes(buf);
REQUIRE(buf[0] == (5 << 5));
}
SECTION("Park Bits") {
uint32_t num_bytes = 16000;
uint8_t* buf = new uint8_t[num_bytes];
uint8_t* buf_2 = new uint8_t[num_bytes];
Util::GetRandomBytes(buf, num_bytes);
ParkBits my_bits = ParkBits(buf, num_bytes, num_bytes*8);
my_bits.ToBytes(buf_2);
for (uint32_t i = 0; i < num_bytes; i++) {
REQUIRE(buf[i] == buf_2[i]);
}
delete[] buf;
delete[] buf_2;
}
SECTION("Large Bits") {
uint32_t num_bytes = 200000;
uint8_t* buf = new uint8_t[num_bytes];
uint8_t* buf_2 = new uint8_t[num_bytes];
Util::GetRandomBytes(buf, num_bytes);
LargeBits my_bits = LargeBits(buf, num_bytes, num_bytes*8);
my_bits.ToBytes(buf_2);
for (uint32_t i = 0; i < num_bytes; i++) {
REQUIRE(buf[i] == buf_2[i]);
}
delete[] buf;
delete[] buf_2;
}
}
class FakeDisk : public Disk {
public:
explicit FakeDisk(uint32_t size) : s(size, 'a') {
f_ = std::stringstream(s, std::ios_base::in | std::ios_base::out);
}
void Read(uint64_t begin, uint8_t* memcache, uint32_t length) {
f_.seekg(begin);
f_.read(reinterpret_cast<char*>(memcache), length);
}
void Write(uint64_t begin, uint8_t* memcache, uint32_t length) {
f_.seekp(begin);
f_.write(reinterpret_cast<char*>(memcache), length);
}
std::iostream* ReadHandle(uint64_t begin) {
f_.seekg(begin);
return &f_;
}
std::iostream* WriteHandle(uint64_t begin) {
f_.seekp(begin);
return &f_;
}
private:
std::string s;
std::stringstream f_;
};
bool CheckMatch(int64_t yl, int64_t yr) {
int64_t bl = yl / kBC;
int64_t br = yr / kBC;
if (bl + 1 != br) return false; // Buckets don't match
for (int64_t m = 0; m < kExtraBitsPow; m++) {
if ((((yr % kBC) / kC - ((yl % kBC) / kC)) - m) % kB == 0) {
if ((((yr % kBC) % kC - ((yl % kBC) % kC)) - (int64_t)(pow((double)(2*m + (bl%2)), (double)2))) % kC == 0) {
return true;
}
}
}
return false;
}
TEST_CASE("F functions") {
SECTION("F1") {
uint8_t test_k = 35;
uint8_t test_key[] = {0, 2, 3, 4, 5, 5, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 1, 2, 3, 41, 5, 6, 7, 8, 9, 10,
11, 12, 13, 11, 15, 16};
F1Calculator f1(test_k, test_key);
Bits L = Bits(525, test_k);
pair<Bits, Bits> result1 = f1.CalculateBucket(L);
Bits L2 = Bits(526, test_k);
pair<Bits, Bits> result2 = f1.CalculateBucket(L2);
Bits L3 = Bits(625, test_k);
pair<Bits, Bits> result3 = f1.CalculateBucket(L3);
vector<pair<Bits, Bits>> results = f1.CalculateBuckets(L, 101);
REQUIRE(result1 == results[0]);
REQUIRE(result2 == results[1]);
REQUIRE(result3 == results[100]);
test_k = 32;
F1Calculator f1_2(test_k, test_key);
L = Bits(192837491, test_k);
result1 = f1_2.CalculateBucket(L);
L2 = Bits(192837491 + 1, test_k);
result2 = f1_2.CalculateBucket(L2);
L3 = Bits(192837491 + 2, test_k);
result3 = f1_2.CalculateBucket(L3);
Bits L4 = Bits(192837491 + 490, test_k);
pair<Bits, Bits> result4 = f1_2.CalculateBucket(L4);
results = f1_2.CalculateBuckets(L, 491);
REQUIRE(result1 == results[0]);
REQUIRE(result2 == results[1]);
REQUIRE(result3 == results[2]);
REQUIRE(result4 == results[490]);
}
SECTION("F2") {
uint8_t test_key_2[] = {20, 2, 5, 4, 51, 52, 23, 84, 91, 10, 111, 12, 13,
24, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10,
11, 129, 139, 171, 15, 18};
map<uint64_t, vector<pair<Bits, Bits>>> buckets;
uint8_t k = 12;
uint64_t num_buckets = pow(2, k + kExtraBits) / kBC + 1;
Bits x = Bits(0, k);
F1Calculator f1(k, test_key_2);
for (uint32_t j=0; j < pow(2, k-4) + 1; j++) {
for (auto pair : f1.CalculateBuckets(x, pow(2, 4))) {
uint64_t bucket = std::get<0>(pair).GetValue() / kBC;
if (buckets.find(bucket) == buckets.end()) {
buckets[bucket] = vector<std::pair<Bits, Bits>>();
}
buckets[bucket].push_back(pair);
if (x.GetValue() + 1 > pow(2, k) - 1) {
break;
}
++x;
}
if (x.GetValue() + 1 > pow(2, k) - 1) {
break;
}
}
FxCalculator f2(k, 2, test_key_2);
int total_matches = 0;
for (auto kv : buckets) {
if (kv.first == num_buckets- 1) {
continue;
}
auto bucket_elements_2 = buckets[kv.first + 1];
vector<PlotEntry> left_bucket;
vector<PlotEntry> right_bucket;
for (auto yx1 : kv.second) {
PlotEntry e;
e.y = get<0>(yx1).GetValue();
left_bucket.push_back(e);
}
for (auto yx2 : buckets[kv.first + 1]) {
PlotEntry e;
e.y = get<0>(yx2).GetValue();
right_bucket.push_back(e);
}
sort(left_bucket.begin(), left_bucket.end(), [](const PlotEntry & a, const PlotEntry & b) -> bool {
return a.y > b.y;
});
sort(right_bucket.begin(), right_bucket.end(), [](const PlotEntry & a, const PlotEntry & b) -> bool {
return a.y > b.y;
});
vector<pair<uint16_t, uint16_t> > matches = f2.FindMatches(left_bucket, right_bucket);
for (auto match : matches) {
REQUIRE(CheckMatch(left_bucket[match.first].y, right_bucket[match.second].y));
}
total_matches += matches.size();
}
REQUIRE(total_matches == 3066);
}
}
void HexToBytes(const string& hex, uint8_t* result) {
for (unsigned int i = 0; i < hex.length(); i += 2) {
string byteString = hex.substr(i, 2);
uint8_t byte = (uint8_t) strtol(byteString.c_str(), NULL, 16);
result[i/2] = byte;
}
}
void TestProofOfSpace(std::string filename, uint32_t iterations, uint8_t k, uint8_t* plot_id,
uint32_t expected_success) {
DiskProver prover(filename);
uint8_t* proof_data = new uint8_t[8 * k];
uint32_t success = 0;
for (uint32_t i = 0; i < iterations; i++) {
vector<unsigned char> hash_input = intToBytes(i, 4);
vector<unsigned char> hash(picosha2::k_digest_size);
picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end());
vector<LargeBits> qualities = prover.GetQualitiesForChallenge(hash.data());
Verifier verifier = Verifier();
for (uint32_t index = 0; index < qualities.size(); index++) {
LargeBits proof = prover.GetFullProof(hash.data(), index);
proof.ToBytes(proof_data);
LargeBits quality = verifier.ValidateProof(plot_id, k, hash.data(), proof_data, k*8);
REQUIRE(quality.GetSize() == 2 * k);
REQUIRE(quality == qualities[index]);
success += 1;
// Tests invalid proof
proof_data[0] = (proof_data[0] + 1) % 256;
LargeBits quality_2 = verifier.ValidateProof(plot_id, k, hash.data(), proof_data, k*8);
REQUIRE(quality_2.GetSize() == 0);
}
}
std::cout << "Success: " << success << "/" << iterations << " " << (100* ((double)success/(double)iterations))
<< "%" << std::endl;
REQUIRE(success == expected_success);
delete[] proof_data;
}
void PlotAndTestProofOfSpace(std::string filename, uint32_t iterations, uint8_t k, uint8_t* plot_id,
uint32_t expected_success) {
DiskPlotter plotter = DiskPlotter();
uint8_t memo[5] = {1, 2, 3, 4, 5};
plotter.CreatePlotDisk(filename, k, memo, 5, plot_id, 32);
TestProofOfSpace(filename, iterations, k, plot_id, expected_success);
REQUIRE(remove(filename.c_str()) == 0);
}
TEST_CASE("Plotting") {
SECTION("Disk plot 1") {
PlotAndTestProofOfSpace("cpp-test-plot.dat", 100, 16, plot_id_1, 42);
}
SECTION("Disk plot 2") {
PlotAndTestProofOfSpace("cpp-test-plot.dat", 500, 17, plot_id_3, 273);
}
SECTION("Disk plot 3") {
PlotAndTestProofOfSpace("cpp-test-plot.dat", 5000, 21, plot_id_3, 4647);
}
}
TEST_CASE("Invalid plot") {
SECTION("File gets deleted") {
DiskPlotter plotter = DiskPlotter();
uint8_t memo[5] = {1, 2, 3, 4, 5};
string filename = "invalid-plot.dat";
uint8_t k = 22;
plotter.CreatePlotDisk(filename, k, memo, 5, plot_id_1, 32);
DiskProver prover(filename);
uint8_t* proof_data = new uint8_t[8 * k];
uint8_t challenge[32];
memset(challenge, 155, 32);
vector<LargeBits> qualities = prover.GetQualitiesForChallenge(challenge);
Verifier verifier = Verifier();
REQUIRE(qualities.size() > 0);
for (uint32_t index = 0; index < qualities.size(); index++) {
LargeBits proof = prover.GetFullProof(challenge, index);
proof.ToBytes(proof_data);
LargeBits quality = verifier.ValidateProof(plot_id_1, k, challenge, proof_data, k*8);
REQUIRE(quality == qualities[index]);
}
REQUIRE(remove(filename.c_str()) == 0);
REQUIRE_THROWS_WITH([&](){
DiskProver p(filename);
}(), "Invalid file " + filename);
delete[] proof_data;
}
}
TEST_CASE("Sort on disk") {
SECTION("ExtractNum") {
for (int i=0; i < 15*8 - 5; i++) {
uint8_t buf[15];
Bits((uint128_t)27 << i, 15*8).ToBytes(buf);
REQUIRE(SortOnDiskUtils::ExtractNum(buf, 15, 15*8 - 4 - i, 3) == 5);
}
uint8_t buf[16];
Bits((uint128_t)27 << 5, 128).ToBytes(buf);
REQUIRE(SortOnDiskUtils::ExtractNum(buf, 16, 100, 200) == 864);
}
SECTION("MemCmpBits") {
uint8_t left[3];
left[0] = 12;
left[1] = 10;
left[2] = 100;
uint8_t right[3];
right[0] = 12;
right[1] = 10;
right[2] = 100;
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) == 0);
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 10) == 0);
right[1] = 11;
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) < 0);
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 16) == 0);
right[1] = 9;
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) > 0);
right[1] = 10;
// Last bit differs
right[2] = 101;
REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) < 0);
}
SECTION("Quicksort") {
uint32_t iters = 100;
vector<string> hashes;
uint8_t* hashes_bytes = new uint8_t[iters * 16];
memset(hashes_bytes, 0, iters * 16);
uint32_t random_state = 0;
for (uint32_t i = 0; i < iters; i++) {
string to_insert = std::to_string(rand_r(&random_state));
while (to_insert.length() < 16) {
to_insert += "0";
}
hashes.push_back(to_insert);
memcpy(hashes_bytes + i * 16, to_insert.data(), to_insert.length());
}
sort(hashes.begin(), hashes.end());
Sorting::QuickSort(hashes_bytes, 16, iters, 0);
for (uint32_t i = 0; i < iters; i++) {
std::string str(reinterpret_cast<char*>(hashes_bytes) + i * 16, 16);
REQUIRE(str.compare(hashes[i]) == 0);
}
delete[] hashes_bytes;
}
SECTION("Fake disk") {
FakeDisk d = FakeDisk(10000);
uint8_t buf[5] = {1, 2, 3, 5, 7};
d.Write(250, buf, 5);
uint8_t read_buf[5];
d.Read(250, read_buf, 5);
REQUIRE(memcmp(buf, read_buf, 5) == 0);
}
SECTION("File disk") {
FileDisk d = FileDisk("test_file.bin");
uint8_t buf[5] = {1, 2, 3, 5, 7};
d.Write(250, buf, 5);
uint8_t read_buf[5];
d.Read(250, read_buf, 5);
REQUIRE(memcmp(buf, read_buf, 5) == 0);
remove("test_file.bin");
}
SECTION("Bucket store") {
uint32_t iters = 10000;
uint32_t size = 16;
vector<Bits> input;
for (uint32_t i = 0; i < iters; i++) {
uint8_t rand_arr[size];
for (uint32_t i = 0; i < size; i++) {
rand_arr[i] = rand() % 256;
}
input.push_back(Bits(rand_arr, size, size*8));
}
set<Bits> iset(input.begin(), input.end());
uint32_t input_index = 0;
vector<Bits> output;
uint8_t* mem = new uint8_t[iters];
BucketStore bs = BucketStore(mem, iters, 16, 0, 4, 5);
bs.Audit();
uint8_t buf[size];
while (true) {
while (!bs.IsFull() && input_index != input.size()) {
input[input_index].ToBytes(buf);
bs.Store(buf, size);
bs.Audit();
input_index += 1;
}
uint32_t m = bs.MaxBucket();
uint64_t final_size;
uint128_t* bucket_handle = bs.BucketHandle(m, 1000000, final_size);
uint32_t entry_size = size / 16;
uint8_t last_size = (size * 8) % 128;
if (last_size == 0)
last_size = 128;
for (uint32_t i = 0; i < final_size; i += entry_size) {
Sorting::EntryToBytes(bucket_handle, i, i + entry_size, last_size, buf);
Bits x(buf, size, size*8);
REQUIRE(iset.find(x) != iset.end());
REQUIRE(SortOnDiskUtils::ExtractNum((uint8_t*)buf, size, 0, 4) == m);
output.push_back(x);
}
if (bs.IsEmpty()) {
delete[] bucket_handle;
break;
}
delete[] bucket_handle;
}
sort(input.begin(), input.end());
sort(output.begin(), output.end());
set<Bits> output_set(output.begin(), output.end());
REQUIRE(output_set.size() == output.size());
for (uint32_t i = 0; i < output.size(); i++) {
REQUIRE(input[i] == output[i]);
}
delete[] mem;
}
SECTION("Sort on disk") {
uint32_t iters = 100000;
uint32_t size = 32;
vector<Bits> input;
uint32_t begin = 1000;
FakeDisk disk = FakeDisk(5000000);
for (uint32_t i = 0; i < iters; i ++) {
vector<unsigned char> hash_input = intToBytes(i, 4);
vector<unsigned char> hash(picosha2::k_digest_size);
picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end());
disk.Write(begin + i * size, hash.data(), size);
input.emplace_back(Bits(hash.data(), size, size*8));
}
vector<uint64_t> bucket_sizes(16, 0);
uint8_t buf[size];
for (Bits& x : input) {
x.ToBytes(buf);
bucket_sizes[SortOnDiskUtils::ExtractNum(buf, size, 0, 4)] += 1;
}
const uint32_t memory_len = 100000;
uint8_t* memory = new uint8_t[memory_len];
Sorting::SortOnDisk(disk, begin, 3600000, size, 0, bucket_sizes, memory, memory_len);
sort(input.begin(), input.end());
uint8_t buf2[size];
for (uint32_t i = 0; i < iters; i++) {
disk.Read(begin + i * size, buf2, size);
input[i].ToBytes(buf);
REQUIRE(memcmp(buf, buf2, size) == 0);
}
delete[] memory;
}
SECTION("Sort in Memory") {
uint32_t iters = 100000;
uint32_t size = 32;
vector<Bits> input;
uint32_t begin = 1000;
FakeDisk disk = FakeDisk(5000000);
for (uint32_t i = 0; i < iters; i ++) {
vector<unsigned char> hash_input = intToBytes(i, 4);
vector<unsigned char> hash(picosha2::k_digest_size);
picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end());
hash[0] = hash[1] = 0;
disk.Write(begin + i * size, hash.data(), size);
input.emplace_back(Bits(hash.data(), size, size*8));
}
const uint32_t memory_len = SortOnDiskUtils::RoundSize(iters) * 30;
uint8_t* memory = new uint8_t[memory_len];
Sorting::SortInMemory(disk, begin, memory, size, iters, 16);
sort(input.begin(), input.end());
uint8_t buf[size];
uint8_t buf2[size];
for (uint32_t i = 0; i < iters; i++) {
disk.Read(begin + i * size, buf2, size);
input[i].ToBytes(buf);
REQUIRE(memcmp(buf, buf2, size) == 0);
}
delete[] memory;
}
}