Bitcoin Core 22.99.0
P2P Digital Currency
asmap.cpp
Go to the documentation of this file.
1// Copyright (c) 2019-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <util/asmap.h>
6
7#include <clientversion.h>
8#include <crypto/common.h>
9#include <logging.h>
10#include <streams.h>
11
12#include <cassert>
13#include <map>
14#include <vector>
15
16namespace {
17
18constexpr uint32_t INVALID = 0xFFFFFFFF;
19
20uint32_t DecodeBits(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos, uint8_t minval, const std::vector<uint8_t> &bit_sizes)
21{
22 uint32_t val = minval;
23 bool bit;
24 for (std::vector<uint8_t>::const_iterator bit_sizes_it = bit_sizes.begin();
25 bit_sizes_it != bit_sizes.end(); ++bit_sizes_it) {
26 if (bit_sizes_it + 1 != bit_sizes.end()) {
27 if (bitpos == endpos) break;
28 bit = *bitpos;
29 bitpos++;
30 } else {
31 bit = 0;
32 }
33 if (bit) {
34 val += (1 << *bit_sizes_it);
35 } else {
36 for (int b = 0; b < *bit_sizes_it; b++) {
37 if (bitpos == endpos) return INVALID; // Reached EOF in mantissa
38 bit = *bitpos;
39 bitpos++;
40 val += bit << (*bit_sizes_it - 1 - b);
41 }
42 return val;
43 }
44 }
45 return INVALID; // Reached EOF in exponent
46}
47
48enum class Instruction : uint32_t
49{
50 RETURN = 0,
51 JUMP = 1,
52 MATCH = 2,
53 DEFAULT = 3,
54};
55
56const std::vector<uint8_t> TYPE_BIT_SIZES{0, 0, 1};
57Instruction DecodeType(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
58{
59 return Instruction(DecodeBits(bitpos, endpos, 0, TYPE_BIT_SIZES));
60}
61
62const std::vector<uint8_t> ASN_BIT_SIZES{15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
63uint32_t DecodeASN(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
64{
65 return DecodeBits(bitpos, endpos, 1, ASN_BIT_SIZES);
66}
67
68
69const std::vector<uint8_t> MATCH_BIT_SIZES{1, 2, 3, 4, 5, 6, 7, 8};
70uint32_t DecodeMatch(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
71{
72 return DecodeBits(bitpos, endpos, 2, MATCH_BIT_SIZES);
73}
74
75
76const std::vector<uint8_t> JUMP_BIT_SIZES{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30};
77uint32_t DecodeJump(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
78{
79 return DecodeBits(bitpos, endpos, 17, JUMP_BIT_SIZES);
80}
81
82}
83
84uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip)
85{
86 std::vector<bool>::const_iterator pos = asmap.begin();
87 const std::vector<bool>::const_iterator endpos = asmap.end();
88 uint8_t bits = ip.size();
89 uint32_t default_asn = 0;
90 uint32_t jump, match, matchlen;
91 Instruction opcode;
92 while (pos != endpos) {
93 opcode = DecodeType(pos, endpos);
94 if (opcode == Instruction::RETURN) {
95 default_asn = DecodeASN(pos, endpos);
96 if (default_asn == INVALID) break; // ASN straddles EOF
97 return default_asn;
98 } else if (opcode == Instruction::JUMP) {
99 jump = DecodeJump(pos, endpos);
100 if (jump == INVALID) break; // Jump offset straddles EOF
101 if (bits == 0) break; // No input bits left
102 if (int64_t{jump} >= int64_t{endpos - pos}) break; // Jumping past EOF
103 if (ip[ip.size() - bits]) {
104 pos += jump;
105 }
106 bits--;
107 } else if (opcode == Instruction::MATCH) {
108 match = DecodeMatch(pos, endpos);
109 if (match == INVALID) break; // Match bits straddle EOF
110 matchlen = CountBits(match) - 1;
111 if (bits < matchlen) break; // Not enough input bits
112 for (uint32_t bit = 0; bit < matchlen; bit++) {
113 if ((ip[ip.size() - bits]) != ((match >> (matchlen - 1 - bit)) & 1)) {
114 return default_asn;
115 }
116 bits--;
117 }
118 } else if (opcode == Instruction::DEFAULT) {
119 default_asn = DecodeASN(pos, endpos);
120 if (default_asn == INVALID) break; // ASN straddles EOF
121 } else {
122 break; // Instruction straddles EOF
123 }
124 }
125 assert(false); // Reached EOF without RETURN, or aborted (see any of the breaks above) - should have been caught by SanityCheckASMap below
126 return 0; // 0 is not a valid ASN
127}
128
129bool SanityCheckASMap(const std::vector<bool>& asmap, int bits)
130{
131 const std::vector<bool>::const_iterator begin = asmap.begin(), endpos = asmap.end();
132 std::vector<bool>::const_iterator pos = begin;
133 std::vector<std::pair<uint32_t, int>> jumps; // All future positions we may jump to (bit offset in asmap -> bits to consume left)
134 jumps.reserve(bits);
135 Instruction prevopcode = Instruction::JUMP;
136 bool had_incomplete_match = false;
137 while (pos != endpos) {
138 uint32_t offset = pos - begin;
139 if (!jumps.empty() && offset >= jumps.back().first) return false; // There was a jump into the middle of the previous instruction
140 Instruction opcode = DecodeType(pos, endpos);
141 if (opcode == Instruction::RETURN) {
142 if (prevopcode == Instruction::DEFAULT) return false; // There should not be any RETURN immediately after a DEFAULT (could be combined into just RETURN)
143 uint32_t asn = DecodeASN(pos, endpos);
144 if (asn == INVALID) return false; // ASN straddles EOF
145 if (jumps.empty()) {
146 // Nothing to execute anymore
147 if (endpos - pos > 7) return false; // Excessive padding
148 while (pos != endpos) {
149 if (*pos) return false; // Nonzero padding bit
150 ++pos;
151 }
152 return true; // Sanely reached EOF
153 } else {
154 // Continue by pretending we jumped to the next instruction
155 offset = pos - begin;
156 if (offset != jumps.back().first) return false; // Unreachable code
157 bits = jumps.back().second; // Restore the number of bits we would have had left after this jump
158 jumps.pop_back();
159 prevopcode = Instruction::JUMP;
160 }
161 } else if (opcode == Instruction::JUMP) {
162 uint32_t jump = DecodeJump(pos, endpos);
163 if (jump == INVALID) return false; // Jump offset straddles EOF
164 if (int64_t{jump} > int64_t{endpos - pos}) return false; // Jump out of range
165 if (bits == 0) return false; // Consuming bits past the end of the input
166 --bits;
167 uint32_t jump_offset = pos - begin + jump;
168 if (!jumps.empty() && jump_offset >= jumps.back().first) return false; // Intersecting jumps
169 jumps.emplace_back(jump_offset, bits);
170 prevopcode = Instruction::JUMP;
171 } else if (opcode == Instruction::MATCH) {
172 uint32_t match = DecodeMatch(pos, endpos);
173 if (match == INVALID) return false; // Match bits straddle EOF
174 int matchlen = CountBits(match) - 1;
175 if (prevopcode != Instruction::MATCH) had_incomplete_match = false;
176 if (matchlen < 8 && had_incomplete_match) return false; // Within a sequence of matches only at most one should be incomplete
177 had_incomplete_match = (matchlen < 8);
178 if (bits < matchlen) return false; // Consuming bits past the end of the input
179 bits -= matchlen;
180 prevopcode = Instruction::MATCH;
181 } else if (opcode == Instruction::DEFAULT) {
182 if (prevopcode == Instruction::DEFAULT) return false; // There should not be two successive DEFAULTs (they could be combined into one)
183 uint32_t asn = DecodeASN(pos, endpos);
184 if (asn == INVALID) return false; // ASN straddles EOF
185 prevopcode = Instruction::DEFAULT;
186 } else {
187 return false; // Instruction straddles EOF
188 }
189 }
190 return false; // Reached EOF without RETURN instruction
191}
192
193std::vector<bool> DecodeAsmap(fs::path path)
194{
195 std::vector<bool> bits;
196 FILE *filestr = fsbridge::fopen(path, "rb");
197 CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
198 if (file.IsNull()) {
199 LogPrintf("Failed to open asmap file from disk\n");
200 return bits;
201 }
202 fseek(filestr, 0, SEEK_END);
203 int length = ftell(filestr);
204 LogPrintf("Opened asmap file %s (%d bytes) from disk\n", fs::quoted(fs::PathToString(path)), length);
205 fseek(filestr, 0, SEEK_SET);
206 uint8_t cur_byte;
207 for (int i = 0; i < length; ++i) {
208 file >> cur_byte;
209 for (int bit = 0; bit < 8; ++bit) {
210 bits.push_back((cur_byte >> bit) & 1);
211 }
212 }
213 if (!SanityCheckASMap(bits, 128)) {
214 LogPrintf("Sanity check of asmap file %s failed\n", fs::quoted(fs::PathToString(path)));
215 return {};
216 }
217 return bits;
218}
219
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:565
bool IsNull() const
Return true if the wrapped FILE* is nullptr, false otherwise.
Definition: streams.h:609
Path class wrapper to prepare application code for transition from boost::filesystem library to std::...
Definition: fs.h:34
static const int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:33
static uint64_t CountBits(uint64_t x)
Return the smallest number n such that (x >> n) == 0 (or 64 if the highest bit in x is set.
Definition: common.h:90
static CService ip(uint32_t i)
#define LogPrintf(...)
Definition: logging.h:187
@ INVALID
Failed decoding.
static auto quoted(const std::string &s)
Definition: fs.h:83
static std::string PathToString(const path &path)
Convert path object to byte string.
Definition: fs.h:120
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:25
@ SER_DISK
Definition: serialize.h:139
uint32_t Interpret(const std::vector< bool > &asmap, const std::vector< bool > &ip)
Definition: asmap.cpp:84
std::vector< bool > DecodeAsmap(fs::path path)
Read asmap from provided binary file.
Definition: asmap.cpp:193
bool SanityCheckASMap(const std::vector< bool > &asmap, int bits)
Definition: asmap.cpp:129
assert(!tx.IsCoinBase())