1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

946
crypto/vm/arithops.cpp Normal file
View file

@ -0,0 +1,946 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <functional>
#include "vm/arithops.h"
#include "vm/log.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
#include "common/bigint.hpp"
#include "common/refint.h"
namespace vm {
int exec_push_tinyint4(VmState* st, unsigned args) {
int x = (int)((args + 5) & 15) - 5;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHINT " << x;
stack.push_smallint(x);
return 0;
}
std::string dump_push_tinyint4(CellSlice&, unsigned args) {
int x = (int)((args + 5) & 15) - 5;
std::ostringstream os{"PUSHINT "};
os << x;
return os.str();
}
int exec_push_tinyint8(VmState* st, unsigned args) {
int x = (signed char)args;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHINT " << x;
stack.push_smallint(x);
return 0;
}
std::string dump_op_tinyint8(const char* op_prefix, CellSlice&, unsigned args) {
int x = (signed char)args;
std::ostringstream os{op_prefix};
os << x;
return os.str();
}
int exec_push_smallint(VmState* st, unsigned args) {
int x = (short)args;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHINT " << x;
stack.push_smallint(x);
return 0;
}
std::string dump_push_smallint(CellSlice&, unsigned args) {
int x = (short)args;
std::ostringstream os{"PUSHINT "};
os << x;
return os.str();
}
int exec_push_int(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
int l = (int)(args & 31) + 2;
if (!cs.have(pfx_bits + 3 + l * 8)) {
throw VmError{Excno::inv_opcode, "not enough bits for integer constant in PUSHINT"};
}
cs.advance(pfx_bits);
td::RefInt256 x = cs.fetch_int256(3 + l * 8);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHINT " << x;
stack.push_int(std::move(x));
return 0;
}
std::string dump_push_int(CellSlice& cs, unsigned args, int pfx_bits) {
int l = (int)(args & 31) + 2;
if (!cs.have(pfx_bits + 3 + l * 8)) {
return "";
}
cs.advance(pfx_bits);
td::RefInt256 x = cs.fetch_int256(3 + l * 8);
std::ostringstream os{"PUSHINT "};
os << x;
return os.str();
}
int compute_len_push_int(const CellSlice& cs, unsigned args, int pfx_bits) {
int l = (int)(args & 31) + 2;
if (!cs.have(pfx_bits + 3 + l * 8)) {
return 0;
} else {
return pfx_bits + 3 + l * 8;
}
}
int exec_push_pow2(VmState* st, unsigned args) {
int x = (args & 255) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHPOW2 " << x;
td::RefInt256 r{true};
r.unique_write().set_pow2(x);
stack.push(std::move(r));
return 0;
}
int exec_push_nan(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHNAN";
td::RefInt256 r{true};
r.unique_write().invalidate();
stack.push(std::move(r));
return 0;
}
int exec_push_pow2dec(VmState* st, unsigned args) {
int x = (args & 255) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHPOW2DEC " << x;
td::RefInt256 r{true};
r.unique_write().set_pow2(x).add_tiny(-1).normalize();
stack.push(std::move(r));
return 0;
}
int exec_push_negpow2(VmState* st, unsigned args) {
int x = (args & 255) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSHNEGPOW2 " << x;
td::RefInt256 r{true};
r.unique_write().set_pow2(x).negate().normalize();
stack.push(std::move(r));
return 0;
}
void register_int_const_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mkfixed(0x7, 4, 4, dump_push_tinyint4, exec_push_tinyint4))
.insert(OpcodeInstr::mkfixed(0x80, 8, 8, std::bind(dump_op_tinyint8, "PUSHINT ", _1, _2), exec_push_tinyint8))
.insert(OpcodeInstr::mkfixed(0x81, 8, 16, dump_push_smallint, exec_push_smallint))
.insert(OpcodeInstr::mkextrange(0x82 << 5, (0x82 << 5) + 31, 13, 5, dump_push_int, exec_push_int,
compute_len_push_int))
.insert(OpcodeInstr::mkfixedrange(0x8300, 0x83ff, 16, 8, instr::dump_1c_l_add(1, "PUSHPOW2 "), exec_push_pow2))
.insert(OpcodeInstr::mksimple(0x83ff, 16, "PUSHNAN", exec_push_nan))
.insert(OpcodeInstr::mkfixed(0x84, 8, 8, instr::dump_1c_l_add(1, "PUSHPOW2DEC "), exec_push_pow2dec))
.insert(OpcodeInstr::mkfixed(0x85, 8, 8, instr::dump_1c_l_add(1, "PUSHNEGPOW2 "), exec_push_negpow2));
}
int exec_add(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ADD";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() + std::move(y), quiet);
return 0;
}
int exec_sub(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SUB";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() - std::move(y), quiet);
return 0;
}
int exec_subr(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SUBR";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(std::move(y) - stack.pop_int(), quiet);
return 0;
}
int exec_negate(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute NEGATE";
stack.check_underflow(1);
stack.push_int_quiet(-stack.pop_int(), quiet);
return 0;
}
int exec_inc(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute INC";
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() + 1, quiet);
return 0;
}
int exec_dec(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DEC";
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() - 1, quiet);
return 0;
}
int exec_add_tinyint8(VmState* st, unsigned args, bool quiet) {
int x = (signed char)args;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ADDINT " << x;
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() + x, quiet);
return 0;
}
int exec_mul_tinyint8(VmState* st, unsigned args, bool quiet) {
int x = (signed char)args;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute MULINT " << x;
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() * x, quiet);
return 0;
}
int exec_mul(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute MUL";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() * std::move(y), quiet);
return 0;
}
void register_add_mul_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xa0, 8, "ADD", std::bind(exec_add, _1, false)))
.insert(OpcodeInstr::mksimple(0xa1, 8, "SUB", std::bind(exec_sub, _1, false)))
.insert(OpcodeInstr::mksimple(0xa2, 8, "SUBR", std::bind(exec_subr, _1, false)))
.insert(OpcodeInstr::mksimple(0xa3, 8, "NEGATE", std::bind(exec_negate, _1, false)))
.insert(OpcodeInstr::mksimple(0xa4, 8, "INC", std::bind(exec_inc, _1, false)))
.insert(OpcodeInstr::mksimple(0xa5, 8, "DEC", std::bind(exec_dec, _1, false)))
.insert(OpcodeInstr::mkfixed(0xa6, 8, 8, std::bind(dump_op_tinyint8, "ADDINT ", _1, _2),
std::bind(exec_add_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mkfixed(0xa7, 8, 8, std::bind(dump_op_tinyint8, "MULINT ", _1, _2),
std::bind(exec_mul_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mksimple(0xa8, 8, "MUL", std::bind(exec_mul, _1, false)));
cp0.insert(OpcodeInstr::mksimple(0xb7a0, 16, "QADD", std::bind(exec_add, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7a1, 16, "QSUB", std::bind(exec_sub, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7a2, 16, "QSUBR", std::bind(exec_subr, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7a3, 16, "QNEGATE", std::bind(exec_negate, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7a4, 16, "QINC", std::bind(exec_inc, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7a5, 16, "QDEC", std::bind(exec_dec, _1, true)))
.insert(OpcodeInstr::mkfixed(0xb7a6, 16, 8, std::bind(dump_op_tinyint8, "QADDINT ", _1, _2),
std::bind(exec_add_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mkfixed(0xb7a7, 16, 8, std::bind(dump_op_tinyint8, "QMULINT ", _1, _2),
std::bind(exec_mul_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mksimple(0xb7a8, 16, "QMUL", std::bind(exec_mul, _1, true)));
}
int exec_divmod(VmState* st, unsigned args, int quiet) {
int round_mode = (int)(args & 3) - 1;
if (!(args & 12) || round_mode == 2) {
throw VmError{Excno::inv_opcode};
}
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DIV/MOD " << (args & 15);
stack.check_underflow(2);
auto y = stack.pop_int();
auto x = stack.pop_int();
switch ((args >> 2) & 3) {
case 1:
stack.push_int_quiet(td::div(std::move(x), std::move(y), round_mode), quiet);
break;
case 2:
stack.push_int_quiet(td::mod(std::move(x), std::move(y), round_mode), quiet);
break;
case 3: {
auto dm = td::divmod(std::move(x), std::move(y), round_mode);
stack.push_int_quiet(std::move(dm.first), quiet);
stack.push_int_quiet(std::move(dm.second), quiet);
break;
}
}
return 0;
}
std::string dump_divmod(CellSlice&, unsigned args, bool quiet) {
int round_mode = (int)(args & 3);
if (!(args & 12) || round_mode == 3) {
return "";
}
std::string s = (args & 4) ? "DIV" : "";
if (args & 8) {
s += "MOD";
}
if (quiet) {
s = "Q" + s;
}
return s + "FRC"[round_mode];
}
int exec_shrmod(VmState* st, unsigned args, int mode) {
int y = -1;
if (mode & 2) {
y = (args & 0xff) + 1;
args >>= 8;
}
int round_mode = (int)(args & 3) - 1;
if (!(args & 12) || round_mode == 2) {
throw VmError{Excno::inv_opcode};
}
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SHR/MOD " << (args & 15) << ',' << y;
if (!(mode & 2)) {
stack.check_underflow(2);
y = stack.pop_smallint_range(256);
} else {
stack.check_underflow(1);
}
if (!y) {
round_mode = -1;
}
auto x = stack.pop_int();
switch ((args >> 2) & 3) {
case 1:
stack.push_int_quiet(td::rshift(std::move(x), y, round_mode), mode & 1);
break;
case 3:
stack.push_int_quiet(td::rshift(x, y, round_mode), mode & 1);
// fallthrough
case 2:
x.write().mod_pow2(y, round_mode).normalize();
stack.push_int_quiet(std::move(x), mode & 1);
break;
}
return 0;
}
std::string dump_shrmod(CellSlice&, unsigned args, int mode) {
int y = -1;
if (mode & 2) {
y = (args & 0xff) + 1;
args >>= 8;
}
int round_mode = (int)(args & 3);
if (!(args & 12) || round_mode == 3) {
return "";
}
std::string s;
switch (args & 12) {
case 4:
s = "RSHIFT";
break;
case 8:
s = "MODPOW2";
break;
case 12:
s = "RSHIFTMOD";
break;
}
if (mode & 1) {
s = "Q" + s;
}
s += "FRC"[round_mode];
if (mode & 2) {
char buff[8];
sprintf(buff, " %d", y);
s += buff;
}
return s;
}
int exec_muldivmod(VmState* st, unsigned args, int quiet) {
int round_mode = (int)(args & 3) - 1;
if (!(args & 12) || round_mode == 2) {
throw VmError{Excno::inv_opcode};
}
Stack& stack = st->get_stack();
VM_LOG(st) << "execute MULDIV/MOD " << (args & 15);
stack.check_underflow(3);
auto z = stack.pop_int();
auto y = stack.pop_int();
auto x = stack.pop_int();
typename td::BigInt256::DoubleInt tmp{0};
tmp.add_mul(*x, *y);
auto q = td::RefInt256{true};
tmp.mod_div(*z, q.unique_write(), round_mode);
switch ((args >> 2) & 3) {
case 1:
q.unique_write().normalize();
stack.push_int_quiet(std::move(q), quiet);
break;
case 3:
q.unique_write().normalize();
stack.push_int_quiet(std::move(q), quiet);
// fallthrough
case 2:
stack.push_int_quiet(td::RefInt256{true, tmp}, quiet);
break;
}
return 0;
}
std::string dump_muldivmod(CellSlice&, unsigned args, bool quiet) {
int round_mode = (int)(args & 3);
if (!(args & 12) || round_mode == 3) {
return "";
}
std::string s = (args & 4) ? "MULDIV" : "MUL";
if (args & 8) {
s += "MOD";
}
if (quiet) {
s = "Q" + s;
}
return s + "FRC"[round_mode];
}
int exec_mulshrmod(VmState* st, unsigned args, int mode) {
int z = -1;
if (mode & 2) {
z = (args & 0xff) + 1;
args >>= 8;
}
int round_mode = (int)(args & 3) - 1;
if (!(args & 12) || round_mode == 2) {
throw VmError{Excno::inv_opcode};
}
Stack& stack = st->get_stack();
VM_LOG(st) << "execute MULSHR/MOD " << (args & 15) << ',' << z;
if (!(mode & 2)) {
stack.check_underflow(3);
z = stack.pop_smallint_range(256);
} else {
stack.check_underflow(2);
}
if (!z) {
round_mode = -1;
}
auto y = stack.pop_int();
auto x = stack.pop_int();
typename td::BigInt256::DoubleInt tmp{0};
tmp.add_mul(*x, *y);
switch ((args >> 2) & 3) {
case 1:
tmp.rshift(z, round_mode).normalize();
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
break;
case 3: {
typename td::BigInt256::DoubleInt tmp2{tmp};
tmp2.rshift(z, round_mode).normalize();
stack.push_int_quiet(td::RefInt256{true, tmp2}, mode & 1);
}
// fallthrough
case 2:
tmp.mod_pow2(z, round_mode).normalize();
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
break;
}
return 0;
}
std::string dump_mulshrmod(CellSlice&, unsigned args, int mode) {
int y = -1;
if (mode & 2) {
y = (args & 0xff) + 1;
args >>= 8;
}
int round_mode = (int)(args & 3);
if (!(args & 12) || round_mode == 3) {
return "";
}
std::string s;
switch (args & 12) {
case 4:
s = "MULRSHIFT";
break;
case 8:
s = "MULMODPOW2";
break;
case 12:
s = "MULRSHIFTMOD";
break;
}
if (mode & 1) {
s = "Q" + s;
}
s += "FRC"[round_mode];
if (mode & 2) {
char buff[8];
sprintf(buff, " %d", y);
s += buff;
}
return s;
}
int exec_shldivmod(VmState* st, unsigned args, int mode) {
int y = -1;
if (mode & 2) {
y = (args & 0xff) + 1;
args >>= 8;
}
int round_mode = (int)(args & 3) - 1;
if (!(args & 12) || round_mode == 2) {
throw VmError{Excno::inv_opcode};
}
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SHLDIV/MOD " << (args & 15) << ',' << y;
if (!(mode & 2)) {
stack.check_underflow(3);
y = stack.pop_smallint_range(256);
} else {
stack.check_underflow(2);
}
auto z = stack.pop_int();
auto x = stack.pop_int();
typename td::BigInt256::DoubleInt tmp{*x};
tmp <<= y;
switch ((args >> 2) & 3) {
case 1: {
auto q = td::RefInt256{true};
tmp.mod_div(*z, q.unique_write(), round_mode);
q.unique_write().normalize();
stack.push_int_quiet(std::move(q), mode & 1);
break;
}
case 3: {
auto q = td::RefInt256{true};
tmp.mod_div(*z, q.unique_write(), round_mode);
q.unique_write().normalize();
stack.push_int_quiet(std::move(q), mode & 1);
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
break;
}
case 2: {
typename td::BigInt256::DoubleInt tmp2;
tmp.mod_div(*z, tmp2, round_mode);
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
break;
}
}
return 0;
}
std::string dump_shldivmod(CellSlice&, unsigned args, bool quiet) {
int round_mode = (int)(args & 3);
if (!(args & 12) || round_mode == 3) {
return "";
}
std::string s = (args & 4) ? "LSHIFTDIV" : "LSHIFT";
if (args & 8) {
s += "MOD";
}
if (quiet) {
s = "Q" + s;
}
return s + "FRC"[round_mode];
}
void register_div_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mkfixed(0xa90, 12, 4, std::bind(dump_divmod, _1, _2, false),
std::bind(exec_divmod, _1, _2, false)))
.insert(OpcodeInstr::mkfixed(0xa92, 12, 4, std::bind(dump_shrmod, _1, _2, 0), std::bind(exec_shrmod, _1, _2, 0)))
.insert(OpcodeInstr::mkfixed(0xa93, 12, 12, std::bind(dump_shrmod, _1, _2, 2), std::bind(exec_shrmod, _1, _2, 2)))
.insert(OpcodeInstr::mkfixed(0xa98, 12, 4, std::bind(dump_muldivmod, _1, _2, false),
std::bind(exec_muldivmod, _1, _2, false)))
.insert(OpcodeInstr::mkfixed(0xa9a, 12, 4, std::bind(dump_mulshrmod, _1, _2, 0),
std::bind(exec_mulshrmod, _1, _2, 0)))
.insert(OpcodeInstr::mkfixed(0xa9b, 12, 12, std::bind(dump_mulshrmod, _1, _2, 2),
std::bind(exec_mulshrmod, _1, _2, 2)))
.insert(OpcodeInstr::mkfixed(0xa9c, 12, 4, std::bind(dump_shldivmod, _1, _2, 0),
std::bind(exec_shldivmod, _1, _2, 0)))
.insert(OpcodeInstr::mkfixed(0xa9d, 12, 12, std::bind(dump_shldivmod, _1, _2, 2),
std::bind(exec_shldivmod, _1, _2, 2)));
cp0.insert(OpcodeInstr::mkfixed(0xb7a90, 20, 4, std::bind(dump_divmod, _1, _2, true),
std::bind(exec_divmod, _1, _2, true)))
.insert(
OpcodeInstr::mkfixed(0xb7a92, 20, 4, std::bind(dump_shrmod, _1, _2, 1), std::bind(exec_shrmod, _1, _2, 1)))
// .insert(OpcodeInstr::mkfixed(0xb7a93, 20, 12, std::bind(dump_shrmod, _1, _2, 3), std::bind(exec_shrmod, _1, _2, 3)))
.insert(OpcodeInstr::mkfixed(0xb7a98, 20, 4, std::bind(dump_muldivmod, _1, _2, true),
std::bind(exec_muldivmod, _1, _2, true)))
.insert(OpcodeInstr::mkfixed(0xb7a9a, 20, 4, std::bind(dump_mulshrmod, _1, _2, 1),
std::bind(exec_mulshrmod, _1, _2, 1)))
// .insert(OpcodeInstr::mkfixed(0xb7a9b, 20, 12, std::bind(dump_mulshrmod, _1, _2, 3), std::bind(exec_mulshrmod, _1, _2, 3)))
.insert(OpcodeInstr::mkfixed(0xb7a9c, 20, 4, std::bind(dump_shldivmod, _1, _2, 1),
std::bind(exec_shldivmod, _1, _2, 1)))
// .insert(OpcodeInstr::mkfixed(0xb7a9d, 20, 12, std::bind(dump_shldivmod, _1, _2, 3), std::bind(exec_shldivmod, _1, _2, 3)))
;
}
int exec_lshift_tinyint8(VmState* st, unsigned args, bool quiet) {
int x = (args & 0xff) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute LSHIFT " << x;
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() << x, quiet);
return 0;
}
int exec_rshift_tinyint8(VmState* st, unsigned args, bool quiet) {
int x = (args & 0xff) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute RSHIFT " << x;
stack.check_underflow(1);
stack.push_int_quiet(stack.pop_int() >> x, quiet);
return 0;
}
int exec_lshift(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute LSHIFT";
stack.check_underflow(2);
int x = stack.pop_smallint_range(1023);
stack.push_int_quiet(stack.pop_int() << x, quiet);
return 0;
}
int exec_rshift(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute RSHIFT";
stack.check_underflow(2);
int x = stack.pop_smallint_range(1023);
stack.push_int_quiet(stack.pop_int() >> x, quiet);
return 0;
}
int exec_pow2(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute POW2";
stack.check_underflow(1);
int x = stack.pop_smallint_range(1023);
td::RefInt256 r{true};
r.unique_write().set_pow2(x);
stack.push_int_quiet(std::move(r), quiet);
return 0;
}
int exec_and(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute AND";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() & std::move(y), quiet);
return 0;
}
int exec_or(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute OR";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() | std::move(y), quiet);
return 0;
}
int exec_xor(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XOR";
stack.check_underflow(2);
auto y = stack.pop_int();
stack.push_int_quiet(stack.pop_int() ^ std::move(y), quiet);
return 0;
}
int exec_not(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute NOT";
stack.check_underflow(1);
stack.push_int_quiet(~stack.pop_int(), quiet);
return 0;
}
int exec_fits_tinyint8(VmState* st, unsigned args, bool quiet) {
int y = (args & 0xff) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute FITS " << y;
stack.check_underflow(1);
auto x = stack.pop_int();
if (!x->signed_fits_bits(y)) {
x.write().invalidate();
}
stack.push_int_quiet(std::move(x), quiet);
return 0;
}
int exec_ufits_tinyint8(VmState* st, unsigned args, bool quiet) {
int y = (args & 0xff) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute UFITS " << y;
stack.check_underflow(1);
auto x = stack.pop_int();
if (!x->unsigned_fits_bits(y)) {
x.write().invalidate();
}
stack.push_int_quiet(std::move(x), quiet);
return 0;
}
int exec_fits(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute FITSX";
stack.check_underflow(2);
int y = stack.pop_smallint_range(1023);
auto x = stack.pop_int();
if (!x->signed_fits_bits(y)) {
x.write().invalidate();
}
stack.push_int_quiet(std::move(x), quiet);
return 0;
}
int exec_ufits(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute UFITSX";
stack.check_underflow(2);
int y = stack.pop_smallint_range(1023);
auto x = stack.pop_int();
if (!x->unsigned_fits_bits(y)) {
x.write().invalidate();
}
stack.push_int_quiet(std::move(x), quiet);
return 0;
}
int exec_bitsize(VmState* st, bool sgnd, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << (sgnd ? "" : "U") << "BITSIZE";
stack.check_underflow(1);
auto x = stack.pop_int();
int y = x->bit_size(sgnd);
if (y < 0x7fffffff) {
stack.push_smallint(y);
} else if (!quiet) {
throw VmError{Excno::range_chk, "CHKSIZE for negative integer"};
} else {
stack.push_int_quiet(td::RefInt256{true}, quiet);
}
return 0;
}
void register_shift_logic_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mkfixed(0xaa, 8, 8, instr::dump_1c_l_add(1, "LSHIFT "),
std::bind(exec_lshift_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mkfixed(0xab, 8, 8, instr::dump_1c_l_add(1, "RSHIFT "),
std::bind(exec_rshift_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mksimple(0xac, 8, "LSHIFT", std::bind(exec_lshift, _1, false)))
.insert(OpcodeInstr::mksimple(0xad, 8, "RSHIFT", std::bind(exec_rshift, _1, false)))
.insert(OpcodeInstr::mksimple(0xae, 8, "POW2", std::bind(exec_pow2, _1, false)))
.insert(OpcodeInstr::mksimple(0xb0, 8, "AND", std::bind(exec_and, _1, false)))
.insert(OpcodeInstr::mksimple(0xb1, 8, "OR", std::bind(exec_or, _1, false)))
.insert(OpcodeInstr::mksimple(0xb2, 8, "XOR", std::bind(exec_xor, _1, false)))
.insert(OpcodeInstr::mksimple(0xb3, 8, "NOT", std::bind(exec_not, _1, false)))
.insert(OpcodeInstr::mkfixed(0xb4, 8, 8, instr::dump_1c_l_add(1, "FITS "),
std::bind(exec_fits_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mkfixed(0xb5, 8, 8, instr::dump_1c_l_add(1, "UFITS "),
std::bind(exec_ufits_tinyint8, _1, _2, false)))
.insert(OpcodeInstr::mksimple(0xb600, 16, "FITSX", std::bind(exec_fits, _1, false)))
.insert(OpcodeInstr::mksimple(0xb601, 16, "UFITSX", std::bind(exec_ufits, _1, false)))
.insert(OpcodeInstr::mksimple(0xb602, 16, "BITSIZE", std::bind(exec_bitsize, _1, true, false)))
.insert(OpcodeInstr::mksimple(0xb603, 16, "UBITSIZE", std::bind(exec_bitsize, _1, false, false)));
cp0.insert(OpcodeInstr::mkfixed(0xb7aa, 16, 8, instr::dump_1c_l_add(1, "QLSHIFT "),
std::bind(exec_lshift_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mkfixed(0xb7ab, 16, 8, instr::dump_1c_l_add(1, "QRSHIFT "),
std::bind(exec_rshift_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mksimple(0xb7ac, 16, "QLSHIFT", std::bind(exec_lshift, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7ad, 16, "QRSHIFT", std::bind(exec_rshift, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7ae, 16, "QPOW2", std::bind(exec_pow2, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b0, 16, "QAND", std::bind(exec_and, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b1, 16, "QOR", std::bind(exec_or, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b2, 16, "QXOR", std::bind(exec_xor, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b3, 16, "QNOT", std::bind(exec_not, _1, true)))
.insert(OpcodeInstr::mkfixed(0xb7b4, 16, 8, instr::dump_1c_l_add(1, "QFITS "),
std::bind(exec_fits_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mkfixed(0xb7b5, 16, 8, instr::dump_1c_l_add(1, "QUFITS "),
std::bind(exec_ufits_tinyint8, _1, _2, true)))
.insert(OpcodeInstr::mksimple(0xb7b600, 24, "QFITSX", std::bind(exec_fits, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b601, 24, "QUFITSX", std::bind(exec_ufits, _1, true)))
.insert(OpcodeInstr::mksimple(0xb7b602, 24, "QBITSIZE", std::bind(exec_bitsize, _1, true, true)))
.insert(OpcodeInstr::mksimple(0xb7b603, 24, "QUBITSIZE", std::bind(exec_bitsize, _1, false, true)));
}
int exec_minmax(VmState* st, int mode) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute MINMAXOP " << mode;
stack.check_underflow(2);
auto x = stack.pop_int();
auto y = stack.pop_int();
if (!x->is_valid()) {
y = x;
} else if (!y->is_valid()) {
x = y;
} else if (cmp(x, y) > 0) {
swap(x, y);
}
if (mode & 2) {
stack.push_int_quiet(std::move(x), mode & 1);
}
if (mode & 4) {
stack.push_int_quiet(std::move(y), mode & 1);
}
return 0;
}
int exec_abs(VmState* st, bool quiet) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ABS";
stack.check_underflow(1);
auto x = stack.pop_int();
if (x->is_valid() && x->sgn() < 0) {
stack.push_int_quiet(-std::move(x), quiet);
} else {
stack.push_int_quiet(std::move(x), quiet);
}
return 0;
}
void register_other_arith_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xb608, 16, "MIN", std::bind(exec_minmax, _1, 2)))
.insert(OpcodeInstr::mksimple(0xb609, 16, "MAX", std::bind(exec_minmax, _1, 4)))
.insert(OpcodeInstr::mksimple(0xb60a, 16, "MINMAX", std::bind(exec_minmax, _1, 6)))
.insert(OpcodeInstr::mksimple(0xb60b, 16, "ABS", std::bind(exec_abs, _1, false)));
cp0.insert(OpcodeInstr::mksimple(0xb7b608, 24, "QMIN", std::bind(exec_minmax, _1, 3)))
.insert(OpcodeInstr::mksimple(0xb7b609, 24, "QMAX", std::bind(exec_minmax, _1, 5)))
.insert(OpcodeInstr::mksimple(0xb7b60a, 24, "QMINMAX", std::bind(exec_minmax, _1, 7)))
.insert(OpcodeInstr::mksimple(0xb7b60b, 24, "QABS", std::bind(exec_abs, _1, true)));
}
int exec_sgn(VmState* st, int mode, bool quiet, const char* name) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << name;
stack.check_underflow(1);
auto x = stack.pop_int();
if (!x->is_valid()) {
stack.push_int_quiet(std::move(x), quiet);
} else {
int y = td::sgn(std::move(x));
stack.push_smallint(((mode >> (4 + y * 4)) & 15) - 8);
}
return 0;
}
int exec_cmp(VmState* st, int mode, bool quiet, const char* name) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << name;
stack.check_underflow(2);
auto y = stack.pop_int();
auto x = stack.pop_int();
if (!x->is_valid() || !y->is_valid()) {
stack.push_int_quiet(std::move(x), quiet);
} else {
int z = td::cmp(std::move(x), std::move(y));
stack.push_smallint(((mode >> (4 + z * 4)) & 15) - 8);
}
return 0;
}
int exec_cmp_int(VmState* st, unsigned args, int mode, bool quiet, const char* name) {
int y = (signed char)args;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << name << "INT " << y;
stack.check_underflow(1);
auto x = stack.pop_int();
if (!x->is_valid()) {
stack.push_int_quiet(std::move(x), quiet);
} else {
int z = td::cmp(std::move(x), y);
stack.push_smallint(((mode >> (4 + z * 4)) & 15) - 8);
}
return 0;
}
int exec_is_nan(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ISNAN";
stack.check_underflow(1);
auto x = stack.pop_int();
stack.push_smallint(x->is_valid() ? 0 : -1);
return 0;
}
int exec_chk_nan(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute CHKNAN";
stack.check_underflow(1);
auto x = stack.pop_int();
stack.push_int(std::move(x));
return 0;
}
void register_int_cmp_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xb8, 8, "SGN", std::bind(exec_sgn, _1, 0x987, false, "SGN")))
.insert(OpcodeInstr::mksimple(0xb9, 8, "LESS", std::bind(exec_cmp, _1, 0x887, false, "LESS")))
.insert(OpcodeInstr::mksimple(0xba, 8, "EQUAL", std::bind(exec_cmp, _1, 0x878, false, "EQUAL")))
.insert(OpcodeInstr::mksimple(0xbb, 8, "LEQ", std::bind(exec_cmp, _1, 0x877, false, "LEQ")))
.insert(OpcodeInstr::mksimple(0xbc, 8, "GREATER", std::bind(exec_cmp, _1, 0x788, false, "GREATER")))
.insert(OpcodeInstr::mksimple(0xbd, 8, "NEQ", std::bind(exec_cmp, _1, 0x787, false, "NEQ")))
.insert(OpcodeInstr::mksimple(0xbe, 8, "GEQ", std::bind(exec_cmp, _1, 0x778, false, "GEQ")))
.insert(OpcodeInstr::mksimple(0xbf, 8, "CMP", std::bind(exec_cmp, _1, 0x987, false, "CMP")))
.insert(OpcodeInstr::mkfixed(0xc0, 8, 8, std::bind(dump_op_tinyint8, "EQINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x878, false, "EQ")))
.insert(OpcodeInstr::mkfixed(0xc1, 8, 8, std::bind(dump_op_tinyint8, "LESSINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x887, false, "LESS")))
.insert(OpcodeInstr::mkfixed(0xc2, 8, 8, std::bind(dump_op_tinyint8, "GTINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x788, false, "GT")))
.insert(OpcodeInstr::mkfixed(0xc3, 8, 8, std::bind(dump_op_tinyint8, "NEQINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x787, false, "NEQ")))
.insert(OpcodeInstr::mksimple(0xc4, 8, "ISNAN", exec_is_nan))
.insert(OpcodeInstr::mksimple(0xc5, 8, "CHKNAN", exec_chk_nan));
cp0.insert(OpcodeInstr::mksimple(0xb7b8, 16, "QSGN", std::bind(exec_sgn, _1, 0x987, true, "QSGN")))
.insert(OpcodeInstr::mksimple(0xb7b9, 16, "QLESS", std::bind(exec_cmp, _1, 0x887, true, "QLESS")))
.insert(OpcodeInstr::mksimple(0xb7ba, 16, "QEQUAL", std::bind(exec_cmp, _1, 0x878, true, "QEQUAL")))
.insert(OpcodeInstr::mksimple(0xb7bb, 16, "QLEQ", std::bind(exec_cmp, _1, 0x877, true, "QLEQ")))
.insert(OpcodeInstr::mksimple(0xb7bc, 16, "QGREATER", std::bind(exec_cmp, _1, 0x788, true, "QGREATER")))
.insert(OpcodeInstr::mksimple(0xb7bd, 16, "QNEQ", std::bind(exec_cmp, _1, 0x787, true, "QNEQ")))
.insert(OpcodeInstr::mksimple(0xb7be, 16, "QGEQ", std::bind(exec_cmp, _1, 0x778, true, "QGEQ")))
.insert(OpcodeInstr::mksimple(0xb7bf, 16, "QCMP", std::bind(exec_cmp, _1, 0x987, true, "QCMP")))
.insert(OpcodeInstr::mkfixed(0xb7c0, 16, 8, std::bind(dump_op_tinyint8, "QEQINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x878, true, "QEQ")))
.insert(OpcodeInstr::mkfixed(0xb7c1, 16, 8, std::bind(dump_op_tinyint8, "QLESSINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x887, true, "QLESS")))
.insert(OpcodeInstr::mkfixed(0xb7c2, 16, 8, std::bind(dump_op_tinyint8, "QGTINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x788, true, "QGT")))
.insert(OpcodeInstr::mkfixed(0xb7c3, 16, 8, std::bind(dump_op_tinyint8, "QNEQINT ", _1, _2),
std::bind(exec_cmp_int, _1, _2, 0x787, true, "QNEQ")));
}
void register_arith_ops(OpcodeTable& cp0) {
register_int_const_ops(cp0);
register_add_mul_ops(cp0);
register_div_ops(cp0);
register_shift_logic_ops(cp0);
register_other_arith_ops(cp0);
register_int_cmp_ops(cp0);
}
} // namespace vm

27
crypto/vm/arithops.h Normal file
View file

@ -0,0 +1,27 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_arith_ops(OpcodeTable& cp0);
} // namespace vm

97
crypto/vm/atom.cpp Normal file
View file

@ -0,0 +1,97 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "atom.h"
namespace vm {
using td::Ref;
std::atomic<Atom*> Atom::hashtable[hashtable_size] = {};
std::atomic<int> Atom::atoms_defined{0};
std::atomic<int> Atom::anon_atoms{0};
void Atom::print_to(std::ostream& os) const {
if (name_.empty()) {
os << "atom#" << index_;
} else {
os << name_;
}
}
std::string Atom::make_name() const {
char buffer[16];
sprintf(buffer, "atom#%d", index_);
return buffer;
}
std::ostream& operator<<(std::ostream& os, const Atom& atom) {
atom.print_to(os);
return os;
}
std::ostream& operator<<(std::ostream& os, Ref<Atom> atom_ref) {
atom_ref->print_to(os);
return os;
}
std::pair<unsigned, unsigned> Atom::compute_hash(td::Slice name) {
unsigned h1 = 1, h2 = 1;
for (std::size_t i = 0; i < name.size(); i++) {
h1 = (239 * h1 + (unsigned char)name[i]) % hashtable_size;
h2 = (17 * h2 + (unsigned char)name[i]) % (hashtable_size - 1);
}
return std::make_pair(h1, h2 + 1);
}
Ref<Atom> Atom::find(td::Slice name, bool create) {
auto hash = compute_hash(name);
while (true) {
auto& pos = hashtable[hash.first];
Atom* ptr = pos.load(std::memory_order_acquire);
if (ptr) {
if (ptr->name_as_slice() == name) {
return Ref<Atom>(ptr);
}
} else if (!create) {
return {};
} else {
Atom* p2 = new Atom(name.str(), hash.first);
Atom* p1 = nullptr;
if (pos.compare_exchange_strong(p1, p2)) {
atoms_defined.fetch_add(1, std::memory_order_relaxed);
return Ref<Atom>(p2);
}
delete p2;
CHECK(p1);
if (p1->name_as_slice() == name) {
return Ref<Atom>(p1);
}
}
hash.first += hash.second;
if (hash.first >= hashtable_size) {
hash.first -= hashtable_size;
}
}
}
Ref<Atom> Atom::anon() {
int c = anon_atoms.fetch_add(1, std::memory_order_relaxed);
return Ref<Atom>{true, "", ~c};
}
} // namespace vm

70
crypto/vm/atom.h Normal file
View file

@ -0,0 +1,70 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <atomic>
#include "vm/stack.hpp"
#include "td/utils/Slice.h"
namespace vm {
using td::Ref;
class Atom : public td::CntObject {
std::string name_;
int index_;
static constexpr unsigned hashtable_size = 170239;
static std::atomic<Atom*> hashtable[hashtable_size];
static std::atomic<int> atoms_defined;
static std::atomic<int> anon_atoms;
static std::pair<unsigned, unsigned> compute_hash(td::Slice name);
public:
Atom(const Atom&) = delete;
Atom &operator=(const Atom&) = delete;
Atom(Atom&&) = delete;
Atom &operator=(Atom&&) = delete;
~Atom() override = default;
Atom(std::string name, int index) : name_(name), index_(index) {
}
static Ref<Atom> anon();
static Ref<Atom> find(td::Slice name, bool create = false);
static Ref<Atom> create(td::Slice name) {
return find(std::move(name), true);
}
std::string name() const {
return name_;
}
std::string name_ext() const {
return name_.empty() ? make_name() : name_;
}
td::Slice name_as_slice() const {
return td::Slice{name_};
}
int index() const {
return index_;
}
void print_to(std::ostream& os) const;
private:
std::string make_name() const;
};
std::ostream& operator<<(std::ostream& os, const Atom& atom);
std::ostream& operator<<(std::ostream& os, Ref<Atom> atom_ref);
} // namespace vm

1133
crypto/vm/boc.cpp Normal file

File diff suppressed because it is too large Load diff

314
crypto/vm/boc.h Normal file
View file

@ -0,0 +1,314 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <set>
#include "vm/cells.h"
#include "td/utils/Status.h"
#include "td/utils/buffer.h"
#include "td/utils/HashMap.h"
namespace vm {
using td::Ref;
td::Result<Ref<Cell>> std_boc_deserialize(td::Slice data, bool can_be_empty = false);
td::Result<td::BufferSlice> std_boc_serialize(Ref<Cell> root, int mode = 0);
td::Result<std::vector<Ref<Cell>>> std_boc_deserialize_multi(td::Slice data);
td::Result<td::BufferSlice> std_boc_serialize_multi(std::vector<Ref<Cell>> root, int mode = 0);
class NewCellStorageStat {
public:
NewCellStorageStat() {
}
struct Stat {
Stat() {
}
Stat(td::uint64 cells_, td::uint64 bits_, td::uint64 internal_refs_ = 0, td::uint64 external_refs_ = 0)
: cells(cells_), bits(bits_), internal_refs(internal_refs_), external_refs(external_refs_) {
}
td::uint64 cells{0};
td::uint64 bits{0};
td::uint64 internal_refs{0};
td::uint64 external_refs{0};
auto key() const {
return std::make_tuple(cells, bits, internal_refs, external_refs);
}
bool operator==(const Stat& other) const {
return key() == other.key();
}
Stat& operator=(const Stat& other) = default;
Stat& operator+=(const Stat& other) {
cells += other.cells;
bits += other.bits;
internal_refs += other.internal_refs;
external_refs += other.external_refs;
return *this;
}
Stat operator+(const Stat& other) const {
return Stat{cells + other.cells, bits + other.bits, internal_refs + other.internal_refs,
external_refs + other.external_refs};
}
bool fits_uint32() const {
return !((cells | bits | internal_refs | external_refs) >> 32);
}
void set_zero() {
cells = bits = internal_refs = external_refs = 0;
}
};
Stat get_stat() const {
return stat_;
}
Stat get_proof_stat() const {
return proof_stat_;
}
Stat get_total_stat() const {
return stat_ + proof_stat_;
}
void add_cell(Ref<Cell> cell);
void add_proof(Ref<Cell> cell, const CellUsageTree* usage_tree);
void add_cell_and_proof(Ref<Cell> cell, const CellUsageTree* usage_tree);
Stat tentative_add_cell(Ref<Cell> cell) const;
Stat tentative_add_proof(Ref<Cell> cell, const CellUsageTree* usage_tree) const;
void set_zero() {
stat_.set_zero();
proof_stat_.set_zero();
}
private:
const CellUsageTree* usage_tree_;
std::set<vm::Cell::Hash> seen_;
Stat stat_;
std::set<vm::Cell::Hash> proof_seen_;
Stat proof_stat_;
const NewCellStorageStat* parent_{nullptr};
void dfs(Ref<Cell> cell, bool need_stat, bool need_proof_stat);
};
struct CellStorageStat {
unsigned long long cells;
unsigned long long bits;
unsigned long long public_cells;
std::set<vm::Cell::Hash> seen;
CellStorageStat() : cells(0), bits(0), public_cells(0) {
}
bool clear_seen() {
seen.clear();
return true;
}
void clear() {
cells = bits = public_cells = 0;
clear_seen();
}
bool compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
bool compute_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
bool add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
bool add_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
bool add_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
bool add_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
};
struct CellSerializationInfo {
bool special;
Cell::LevelMask level_mask;
bool with_hashes;
size_t hashes_offset;
size_t depth_offset;
size_t data_offset;
size_t data_len;
bool data_with_bits;
size_t refs_offset;
int refs_cnt;
size_t end_offset;
td::Status init(td::Slice data, int ref_byte_size);
td::Status init(td::uint8 d1, td::uint8 d2, int ref_byte_size);
td::Result<int> get_bits(td::Slice cell) const;
td::Result<Ref<DataCell>> create_data_cell(td::Slice data, td::Span<Ref<Cell>> refs) const;
};
class BagOfCells {
public:
enum { hash_bytes = vm::Cell::hash_bytes };
enum Mode { WithIndex = 1, WithCRC32C = 2, WithTopHash = 4, WithIntHashes = 8, WithCacheBits = 16, max = 31 };
enum { max_cell_whs = 64 };
using Hash = Cell::Hash;
struct Info {
enum : td::uint32 { boc_idx = 0x68ff65f3, boc_idx_crc32c = 0xacc3a728, boc_generic = 0xb5ee9c72 };
unsigned magic;
int root_count;
int cell_count;
int absent_count;
int ref_byte_size;
int offset_byte_size;
bool valid;
bool has_index;
bool has_roots{false};
bool has_crc32c;
bool has_cache_bits;
unsigned long long roots_offset, index_offset, data_offset, data_size, total_size;
Info() : magic(0), valid(false) {
}
void invalidate() {
valid = false;
}
long long parse_serialized_header(const td::Slice& slice);
unsigned long long read_int(const unsigned char* ptr, unsigned bytes);
unsigned long long read_ref(const unsigned char* ptr) {
return read_int(ptr, ref_byte_size);
}
unsigned long long read_offset(const unsigned char* ptr) {
return read_int(ptr, offset_byte_size);
}
void write_int(unsigned char* ptr, unsigned long long value, int bytes);
void write_ref(unsigned char* ptr, unsigned long long value) {
write_int(ptr, value, ref_byte_size);
}
void write_offset(unsigned char* ptr, unsigned long long value) {
write_int(ptr, value, offset_byte_size);
}
};
private:
int cell_count{0}, root_count{0}, dangle_count{0}, int_refs{0};
int int_hashes{0}, top_hashes{0};
int max_depth{1024};
Info info;
unsigned long long data_bytes{0};
unsigned char* store_ptr{nullptr};
unsigned char* store_end{nullptr};
td::HashMap<Hash, int> cells;
struct CellInfo {
Ref<DataCell> dc_ref;
std::array<int, 4> ref_idx;
unsigned char ref_num;
unsigned char wt;
unsigned char hcnt;
int new_idx;
bool should_cache{false};
bool is_root_cell{false};
CellInfo() : ref_num(0) {
}
CellInfo(Ref<DataCell> _dc) : dc_ref(std::move(_dc)), ref_num(0) {
}
CellInfo(Ref<DataCell> _dc, int _refs, const std::array<int, 4>& _ref_list)
: dc_ref(std::move(_dc)), ref_idx(_ref_list), ref_num(static_cast<unsigned char>(_refs)) {
}
bool is_special() const {
return !wt;
}
};
std::vector<CellInfo> cell_list_;
struct RootInfo {
RootInfo() = default;
RootInfo(Ref<Cell> cell, int idx) : cell(std::move(cell)), idx(idx) {
}
Ref<Cell> cell;
int idx{-1};
};
std::vector<CellInfo> cell_list_tmp;
std::vector<RootInfo> roots;
std::vector<unsigned char> serialized;
const unsigned char* index_ptr{nullptr};
const unsigned char* data_ptr{nullptr};
std::vector<unsigned long long> custom_index;
public:
void clear();
int set_roots(const std::vector<td::Ref<vm::Cell>>& new_roots);
int set_root(td::Ref<vm::Cell> new_root);
int add_roots(const std::vector<td::Ref<vm::Cell>>& add_roots);
int add_root(td::Ref<vm::Cell> add_root);
td::Status import_cells() TD_WARN_UNUSED_RESULT;
BagOfCells() = default;
std::size_t estimate_serialized_size(int mode = 0);
BagOfCells& serialize(int mode = 0);
std::string serialize_to_string(int mode = 0);
td::Result<td::BufferSlice> serialize_to_slice(int mode = 0);
std::size_t serialize_to(unsigned char* buffer, std::size_t buff_size, int mode = 0);
std::string extract_string() const;
td::Result<long long> deserialize(const td::Slice& data);
td::Result<long long> deserialize(const unsigned char* buffer, std::size_t buff_size) {
return deserialize(td::Slice{buffer, buff_size});
}
int get_root_count() const {
return root_count;
}
Ref<Cell> get_root_cell(int idx = 0) const {
return (idx >= 0 && idx < root_count) ? roots.at(idx).cell : Ref<Cell>{};
}
static int precompute_cell_serialization_size(const unsigned char* cell, std::size_t len, int ref_size,
int* refs_num_ptr = nullptr);
private:
int rv_idx;
td::Result<int> import_cell(td::Ref<vm::Cell> cell, int depth);
void cells_clear() {
cell_count = 0;
int_refs = 0;
data_bytes = 0;
cells.clear();
cell_list_.clear();
}
td::uint64 compute_sizes(int mode, int& r_size, int& o_size);
void init_store(unsigned char* from, unsigned char* to) {
store_ptr = from;
store_end = to;
}
void store_chk() const {
DCHECK(store_ptr <= store_end);
}
bool store_empty() const {
return store_ptr == store_end;
}
void store_uint(unsigned long long value, unsigned bytes);
void store_ref(unsigned long long value) {
store_uint(value, info.ref_byte_size);
}
void store_offset(unsigned long long value) {
store_uint(value, info.offset_byte_size);
}
void reorder_cells();
int revisit(int cell_idx, int force = 0);
unsigned long long get_idx_entry_raw(int index);
unsigned long long get_idx_entry(int index);
bool get_cache_entry(int index);
td::Result<td::Slice> get_cell_slice(int index, td::Slice data);
td::Result<td::Ref<vm::DataCell>> deserialize_cell(int index, td::Slice data, td::Span<td::Ref<DataCell>> cells,
std::vector<td::uint8>* cell_should_cache);
};
} // namespace vm

63
crypto/vm/box.hpp Normal file
View file

@ -0,0 +1,63 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/stack.hpp"
namespace vm {
using td::Ref;
class Box : public td::CntObject {
mutable StackEntry data_;
public:
Box() = default;
Box(const Box&) = default;
Box(Box&&) = default;
template <typename... Args>
Box(Args... args) : data_{std::move(args...)} {
}
~Box() override = default;
Box(const StackEntry& data) : data_(data) {
}
Box(StackEntry&& data) : data_(std::move(data)) {
}
void operator=(const StackEntry& data) const {
data_ = data;
}
void operator=(StackEntry&& data) const {
data_ = data;
}
void set(const StackEntry& data) const {
data_ = data;
}
void set(StackEntry&& data) const {
data_ = data;
}
const StackEntry& get() const {
return data_;
}
void clear() const {
data_.clear();
}
bool empty() const {
return data_.empty();
}
};
} // namespace vm

1420
crypto/vm/cellops.cpp Normal file

File diff suppressed because it is too large Load diff

31
crypto/vm/cellops.h Normal file
View file

@ -0,0 +1,31 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cellslice.h"
namespace vm {
class OpcodeTable;
void register_cell_ops(OpcodeTable &cp0);
std::string dump_push_ref(CellSlice &cs, unsigned args, int pfx_bits, std::string name);
int compute_len_push_ref(const CellSlice &cs, unsigned args, int pfx_bits);
} // namespace vm

105
crypto/vm/cellparse.hpp Normal file
View file

@ -0,0 +1,105 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells.h"
#include "vm/cellslice.h"
namespace vm {
class CellRefParser {
Ref<CellSlice> cs;
bool state;
public:
CellRefParser(Ref<CellSlice> _cs) : cs(std::move(_cs)), state(cs.not_null()) {
}
bool ok() const {
return state;
}
operator bool() const {
return state;
}
template <class T>
CellRefParser& deserialize(T& val) {
state = (state && val.deserialize(cs.write()));
return *this;
}
//template <class T>
//CellRefParser& deserialize(T& val) {
// state = val.deserialize_ext(cs.write(), state);
// return *this;
//}
template <class T>
CellRefParser& deserialize(const T& val) {
state = (state && val.deserialize(cs.write()));
return *this;
}
//template <class T>
//CellRefParser& deserialize(const T& val) {
// state = val.deserialize_ext(cs.write(), state);
// return *this;
//}
};
class CellParser {
CellSlice& cs;
bool state;
public:
CellParser(CellSlice& _cs) : cs(_cs), state(true) {
}
bool ok() const {
return state;
}
operator bool() const {
return state;
}
template <class T>
CellParser& deserialize(T& val) {
state = (state && val.deserialize(cs));
return *this;
}
// template <class T>
// CellParser& deserialize(T& val) {
// state = val.deserialize_ext(cs, state);
// return *this;
// }
template <class T>
CellParser& deserialize(const T& val) {
state = (state && val.deserialize(cs));
return *this;
}
// template <class T>
// CellParser& deserialize(const T& val) {
// state = val.deserialize_ext(cs, state);
// return *this;
// }
};
template <class P, class T>
P& operator>>(P& cp, T& val) {
return cp.deserialize(val);
}
template <class P, class T>
P& operator>>(P& cp, const T& val) {
return cp.deserialize(val);
}
} // namespace vm

73
crypto/vm/cells.h Normal file
View file

@ -0,0 +1,73 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <ostream>
#include "common/refcnt.hpp"
#include "common/bitstring.h"
#include "common/bigint.hpp"
#include "common/refint.h"
#include "vm/cells/Cell.h"
#include "vm/cells/CellBuilder.h"
#include "vm/cells/DataCell.h"
#include "vm/cells/UsageCell.h"
#include "vm/cells/VirtualCell.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
#include "openssl/digest.h"
// H_i(cell) = H(cell_i)
// cell.hash = sha256(
// d1, d2,
// (level == 0 || special_type == PrunnedBranch) ? data : cell_(level - 1).hash,
// for child in refs:
// child.depth, child.hash
// )
// lower hashes of Prunned branch are calculated from its data
// cell_i.ref[j] = (special_type == MerkleProof || special_type == MerkleUpdate) ? cell.ref[j]_(i+1) : cell.ref[j]_i
//
// Ordinary cell:
// cell_i.data = cell.data
// cell_i.level_mask = cell.level_mask & ((1<<i) - 1)
// (cell_i.level_mask = == BinaryOr(cell_i.ref[j].level_mask))
// cell_i.level = 32 - count_leading_zeroes32(cell_i.level_mask)
// cell_i.depth = if cell_i.has_ref then max(cell_i.ref[j].depth) + 1 else 0
// cell_i.ref[j] = cell.ref[j]
//
//
// Prunned branch
// cell.level_mask = prunned_cell.level_mask + (1 << (cell.level + 1))
// cell.level = <default> == cell.level + 1
// cell_i = if i < cell.level then prunned_cell_i
// prunned_cell.data = EXCEPTION
// prunned_cell_i.hash = <from cell.data>
// prunned_cell.level_mask = cell.level_mask ^ (1 << cell.level)
// prunned_cell.level = <default>
// prunned_cell_i.depth = <from cell.data>
//
// Merkle proof
// cell.level_mask = proof_cell.level_mask >> 1
// cell.level = <default> == max(0, proof.cell.level - 1)
// cell_i.data = <default>
// cell_i.level_mask = <default>
// cell_i.ref[j] = cell.ref[j]_(i+1)
// cell_i.depth = max_j(1 + cell_i.ref[j].depth)

59
crypto/vm/cells/Cell.cpp Normal file
View file

@ -0,0 +1,59 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/Cell.h"
#include "vm/cells/VirtualCell.h"
#include "vm/cells/DataCell.h"
#include <iostream>
namespace vm {
td::Status Cell::check_equals_unloaded(const Ref<Cell>& other) const {
auto level_mask = get_level_mask();
if (level_mask != other->get_level_mask()) {
return td::Status::Error("level mismatch");
}
auto level = level_mask.get_level();
for (unsigned i = 0; i <= level; i++) {
if (!get_level_mask().is_significant(i)) {
continue;
}
if (get_hash(i) != other->get_hash(i)) {
return td::Status::Error("hash mismatch");
}
}
for (unsigned i = 0; i <= level; i++) {
if (!get_level_mask().is_significant(i)) {
continue;
}
if (get_depth(i) != other->get_depth(i)) {
return td::Status::Error("depth mismatch");
}
}
return td::Status::OK();
}
Ref<Cell> Cell::virtualize(VirtualizationParameters virt) const {
return VirtualCell::create(virt, Ref<Cell>(this));
}
std::ostream& operator<<(std::ostream& os, const Cell& c) {
return os << c.get_hash().to_hex();
}
} // namespace vm

89
crypto/vm/cells/Cell.h Normal file
View file

@ -0,0 +1,89 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refcnt.hpp"
#include "common/bitstring.h"
#include "vm/cells/CellHash.h"
#include "vm/cells/CellTraits.h"
#include "vm/cells/CellUsageTree.h"
#include "vm/cells/LevelMask.h"
#include "vm/cells/VirtualizationParameters.h"
#include "td/utils/Status.h"
#include <iostream>
namespace vm {
using td::Ref;
class DataCell;
class Cell : public CellTraits {
public:
using LevelMask = detail::LevelMask;
using VirtualizationParameters = detail::VirtualizationParameters;
struct LoadedCell {
Ref<DataCell> data_cell;
VirtualizationParameters virt;
CellUsageTree::NodePtr tree_node; // TODO: inline_vector?
};
using Hash = CellHash;
static_assert(std::is_standard_layout<Hash>::value, "Cell::Hash is not a standard layout type");
static_assert(sizeof(Hash) == hash_bytes, "Cell::Hash size is not equal to hash_bytes");
//typedef td::BitArray<hash_bits> hash_t;
Cell* make_copy() const final {
throw WriteError();
}
// load interface
virtual td::Result<LoadedCell> load_cell() const = 0;
virtual Ref<Cell> virtualize(VirtualizationParameters virt) const;
virtual td::uint32 get_virtualization() const = 0;
virtual CellUsageTree::NodePtr get_tree_node() const = 0;
virtual bool is_loaded() const = 0;
// hash and level
virtual LevelMask get_level_mask() const = 0;
// level helper function
td::uint32 get_level() const {
return get_level_mask().get_level();
}
// hash helper functions
const Hash get_hash(int level = max_level) const {
return do_get_hash(level);
}
// depth helper function
td::uint16 get_depth(int level = max_level) const {
return do_get_depth(level);
}
td::Status check_equals_unloaded(const Ref<Cell>& other) const;
private:
virtual td::uint16 do_get_depth(td::uint32 level) const = 0;
virtual const Hash do_get_hash(td::uint32 level) const = 0;
};
std::ostream& operator<<(std::ostream& os, const Cell& c);
} // namespace vm

View file

@ -0,0 +1,593 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/CellBuilder.h"
#include "vm/cells/CellSlice.h"
#include "vm/cells/DataCell.h"
#include "td/utils/misc.h"
#include "td/utils/format.h"
#include "openssl/digest.h"
namespace vm {
using td::Ref;
using td::RefAny;
/*
*
* CELL BUILDERS
*
*/
CellBuilder::~CellBuilder() {
get_thread_safe_counter().add(-1);
}
CellBuilder::CellBuilder() : bits(0), refs_cnt(0) {
get_thread_safe_counter().add(+1);
}
Ref<DataCell> CellBuilder::finalize_copy(bool special) const {
auto* vm_state_interface = VmStateInterface::get();
if (vm_state_interface) {
vm_state_interface->register_cell_create();
}
auto res = DataCell::create(data, size(), td::span(refs.data(), size_refs()), special);
if (res.is_error()) {
LOG(ERROR) << res.error();
throw CellWriteError{};
}
CHECK(res.ok().not_null());
return res.move_as_ok();
}
Ref<DataCell> CellBuilder::finalize_novm(bool special) {
auto res = DataCell::create(data, size(), td::mutable_span(refs.data(), size_refs()), special);
bits = refs_cnt = 0;
if (res.is_error()) {
LOG(ERROR) << res.error();
throw CellWriteError{};
}
CHECK(res.ok().not_null());
return res.move_as_ok();
}
Ref<DataCell> CellBuilder::finalize(bool special) {
auto* vm_state_interface = VmStateInterface::get();
if (vm_state_interface) {
vm_state_interface->register_cell_create();
}
return finalize_novm(special);
}
Ref<Cell> CellBuilder::create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level) {
if (cell->is_loaded() && cell->get_level() <= virt_level && cell->get_virtualization() == 0) {
CellSlice cs(NoVm{}, cell);
if (cs.size_refs() == 0) {
return cell;
}
}
return do_create_pruned_branch(std::move(cell), new_level, virt_level);
}
Ref<DataCell> CellBuilder::do_create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level) {
auto level_mask = cell->get_level_mask().apply(virt_level);
auto level = level_mask.get_level();
if (new_level < level + 1) {
throw CellWriteError();
}
CellBuilder cb;
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::PrunnedBranch), 8);
cb.store_long(level_mask.apply_or(Cell::LevelMask::one_level(new_level)).get_mask(), 8);
for (td::uint32 i = 0; i <= level; i++) {
if (level_mask.is_significant(i)) {
cb.store_bytes(cell->get_hash(i).as_slice());
}
}
for (td::uint32 i = 0; i <= level; i++) {
if (level_mask.is_significant(i)) {
cb.store_long(cell->get_depth(i), 16);
}
}
return cb.finalize(true);
}
Ref<DataCell> CellBuilder::create_merkle_proof(Ref<Cell> cell_proof) {
CellBuilder cb;
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::MerkleProof), 8);
cb.store_bytes(cell_proof->get_hash(0).as_slice());
cb.store_long(cell_proof->get_depth(0), Cell::depth_bytes * 8);
cb.store_ref(cell_proof);
return cb.finalize(true);
}
Ref<DataCell> CellBuilder::create_merkle_update(Ref<Cell> from_proof, Ref<Cell> to_proof) {
CellBuilder cb;
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::MerkleUpdate), 8);
cb.store_bytes(from_proof->get_hash(0).as_slice());
cb.store_bytes(to_proof->get_hash(0).as_slice());
cb.store_long(from_proof->get_depth(0), Cell::depth_bytes * 8);
cb.store_long(to_proof->get_depth(0), Cell::depth_bytes * 8);
cb.store_ref(from_proof);
cb.store_ref(to_proof);
return cb.finalize(true);
}
void CellBuilder::reset(void) {
while (refs_cnt > 0) {
refs[--refs_cnt].clear();
}
bits = 0;
}
CellBuilder& CellBuilder::operator=(const CellBuilder& other) {
bits = other.bits;
refs_cnt = other.refs_cnt;
refs = other.refs;
std::memcpy(data, other.data, (bits + 7) >> 3);
return *this;
}
CellBuilder& CellBuilder::operator=(CellBuilder&& other) {
bits = other.bits;
refs_cnt = other.refs_cnt;
refs = std::move(other.refs);
other.refs_cnt = 0;
std::memcpy(data, other.data, (bits + 7) >> 3);
return *this;
}
bool CellBuilder::can_extend_by(std::size_t new_bits, unsigned new_refs) const {
return (new_bits <= Cell::max_bits - bits && new_refs <= (unsigned)Cell::max_refs - refs_cnt);
}
bool CellBuilder::can_extend_by(std::size_t new_bits) const {
return new_bits <= Cell::max_bits - bits;
}
CellBuilder& CellBuilder::store_bytes(const unsigned char* str, std::size_t len) {
ensure_throw(len <= Cell::max_bytes);
return store_bits(str, len * 8);
}
CellBuilder& CellBuilder::store_bytes(const unsigned char* str, const unsigned char* end) {
ensure_throw(end >= str && end <= str + Cell::max_bytes);
return store_bits(str, (end - str) * 8);
}
CellBuilder& CellBuilder::store_bytes(const char* str, std::size_t len) {
return store_bytes((const unsigned char*)(str), len);
}
CellBuilder& CellBuilder::store_bytes(const char* str, const char* end) {
return store_bytes((const unsigned char*)(str), (const unsigned char*)(end));
}
CellBuilder& CellBuilder::store_bytes(td::Slice s) {
return store_bytes((const unsigned char*)(s.data()), (const unsigned char*)(s.data() + s.size()));
}
bool CellBuilder::store_bytes_bool(const unsigned char* str, std::size_t len) {
return len <= Cell::max_bytes && store_bits_bool(str, len * 8);
}
bool CellBuilder::store_bytes_bool(const char* str, std::size_t len) {
return len <= Cell::max_bytes && store_bits_bool((const unsigned char*)str, len * 8);
}
bool CellBuilder::store_bytes_bool(td::Slice s) {
return store_bytes_bool((const unsigned char*)s.data(), s.size());
}
bool CellBuilder::store_bits_bool(const unsigned char* str, std::size_t bit_count, int bit_offset) {
unsigned pos = bits;
if (!prepare_reserve(bit_count)) {
return false;
}
td::bitstring::bits_memcpy(data, pos, str, bit_offset, bit_count);
return true;
}
CellBuilder& CellBuilder::store_bits(const unsigned char* str, std::size_t bit_count, int bit_offset) {
unsigned pos = bits;
if (prepare_reserve(bit_count)) {
td::bitstring::bits_memcpy(data, pos, str, bit_offset, bit_count);
}
return *this;
}
CellBuilder& CellBuilder::store_bits(const td::BitSlice& bs) {
return store_bits(bs.get_ptr(), bs.size(), bs.get_offs());
}
CellBuilder& CellBuilder::store_bits(const char* str, std::size_t bit_count, int bit_offset) {
return store_bits((const unsigned char*)str, bit_count, bit_offset);
}
CellBuilder& CellBuilder::store_bits(td::ConstBitPtr bs, std::size_t bit_count) {
return store_bits(bs.ptr, bit_count, bs.offs);
}
bool CellBuilder::store_bits_bool(td::ConstBitPtr bs, std::size_t bit_count) {
return store_bits_bool(bs.ptr, bit_count, bs.offs);
}
CellBuilder& CellBuilder::store_bits_same(std::size_t bit_count, bool val) {
unsigned pos = bits;
if (prepare_reserve(bit_count)) {
td::bitstring::bits_memset(data, pos, val, bit_count);
}
return *this;
}
bool CellBuilder::store_bits_same_bool(std::size_t bit_count, bool val) {
unsigned pos = bits;
if (!prepare_reserve(bit_count)) {
return false;
}
td::bitstring::bits_memset(data, pos, val, bit_count);
return true;
}
inline bool CellBuilder::prepare_reserve(std::size_t bit_count) {
if (!can_extend_by(bit_count)) {
return false;
} else {
bits += (unsigned)bit_count;
return true;
}
}
td::BitSliceWrite CellBuilder::reserve_slice(std::size_t bit_count) {
unsigned offs = bits;
if (prepare_reserve(bit_count)) {
return td::BitSliceWrite{Ref<CellBuilder>{this}, data, offs, (unsigned)bit_count};
} else {
return td::BitSliceWrite{};
}
}
CellBuilder& CellBuilder::reserve_slice(std::size_t bit_count, td::BitSliceWrite& bsw) {
unsigned offs = bits;
if (prepare_reserve(bit_count)) {
bsw.assign(Ref<CellBuilder>{this}, data, offs, (unsigned)bit_count);
} else {
bsw.forget();
}
return *this;
}
bool CellBuilder::store_bool_bool(bool val) {
if (can_extend_by_fast(1)) {
store_long(val, 1);
return true;
} else {
return false;
}
}
bool CellBuilder::store_long_bool(long long val, unsigned val_bits) {
if (val_bits > 64 || !can_extend_by(val_bits)) {
return false;
}
store_long(val, val_bits);
return true;
}
bool CellBuilder::store_long_rchk_bool(long long val, unsigned val_bits) {
if (val_bits > 64 || !can_extend_by(val_bits)) {
return false;
}
if (val_bits < 64 && (val < static_cast<long long>(std::numeric_limits<td::uint64>::max() << (val_bits - 1)) ||
val >= (1LL << (val_bits - 1)))) {
return false;
}
store_long(val, val_bits);
return true;
}
bool CellBuilder::store_ulong_rchk_bool(unsigned long long val, unsigned val_bits) {
if (val_bits > 64 || !can_extend_by(val_bits)) {
return false;
}
if (val_bits < 64 && val >= (1ULL << val_bits)) {
return false;
}
store_long(val, val_bits);
return true;
}
CellBuilder& CellBuilder::store_long(long long val, unsigned val_bits) {
return store_long_top(val << (64 - val_bits), val_bits);
}
CellBuilder& CellBuilder::store_long_top(unsigned long long val, unsigned top_bits) {
unsigned pos = bits;
auto reserve_ok = prepare_reserve(top_bits);
ensure_throw(reserve_ok);
td::bitstring::bits_store_long_top(data, pos, val, top_bits);
return *this;
}
bool CellBuilder::store_uint_less(unsigned upper_bound, unsigned long long val) {
return val < upper_bound && store_long_bool(val, 32 - td::count_leading_zeroes32(upper_bound - 1));
}
bool CellBuilder::store_uint_leq(unsigned upper_bound, unsigned long long val) {
return val <= upper_bound && store_long_bool(val, 32 - td::count_leading_zeroes32(upper_bound));
}
bool CellBuilder::store_int256_bool(const td::BigInt256& val, unsigned val_bits, bool sgnd) {
unsigned pos = bits;
if (!prepare_reserve(val_bits)) {
return false;
}
if (val.export_bits(data, pos, val_bits, sgnd)) {
return true;
} else {
bits = pos;
return false;
}
}
CellBuilder& CellBuilder::store_int256(const td::BigInt256& val, unsigned val_bits, bool sgnd) {
return ensure_pass(store_int256_bool(val, val_bits, sgnd));
}
bool CellBuilder::store_int256_bool(td::RefInt256 val, unsigned val_bits, bool sgnd) {
return val.not_null() && store_int256_bool(*val, val_bits, sgnd);
}
bool CellBuilder::store_builder_ref_bool(vm::CellBuilder&& cb) {
return store_ref_bool(cb.finalize());
}
bool CellBuilder::store_ref_bool(Ref<Cell> ref) {
if (refs_cnt < Cell::max_refs && ref.not_null()) {
refs[refs_cnt++] = std::move(ref);
return true;
} else {
return false;
}
}
CellBuilder& CellBuilder::store_ref(Ref<Cell> ref) {
return ensure_pass(store_ref_bool(std::move(ref)));
}
bool CellBuilder::append_data_cell_bool(const DataCell& cell) {
unsigned len = cell.size();
if (can_extend_by(len, cell.size_refs())) {
unsigned pos = bits;
ensure_throw(prepare_reserve(len));
td::bitstring::bits_memcpy(data, pos, cell.get_data(), 0, len);
for (unsigned i = 0; i < cell.size_refs(); i++) {
refs[refs_cnt++] = cell.get_ref(i);
}
return true;
} else {
return false;
}
}
CellBuilder& CellBuilder::append_data_cell(const DataCell& cell) {
return ensure_pass(append_data_cell_bool(cell));
}
bool CellBuilder::append_data_cell_bool(Ref<DataCell> cell_ref) {
return append_data_cell_bool(*cell_ref);
}
CellBuilder& CellBuilder::append_data_cell(Ref<DataCell> cell_ref) {
return ensure_pass(append_data_cell_bool(std::move(cell_ref)));
}
bool CellBuilder::append_builder_bool(const CellBuilder& cb) {
unsigned len = cb.size();
if (can_extend_by(len, cb.size_refs())) {
unsigned pos = bits;
ensure_throw(prepare_reserve(len));
td::bitstring::bits_memcpy(data, pos, cb.get_data(), 0, len);
for (unsigned i = 0; i < cb.size_refs(); i++) {
refs[refs_cnt++] = cb.get_ref(i);
}
return true;
} else {
return false;
}
}
CellBuilder& CellBuilder::append_builder(const CellBuilder& cb) {
return ensure_pass(append_builder_bool(cb));
}
bool CellBuilder::append_builder_bool(Ref<CellBuilder> cb_ref) {
return append_builder_bool(*cb_ref);
}
CellBuilder& CellBuilder::append_builder(Ref<CellBuilder> cb_ref) {
return ensure_pass(append_builder_bool(std::move(cb_ref)));
}
bool CellBuilder::append_cellslice_bool(const CellSlice& cs) {
unsigned len = cs.size();
if (can_extend_by(len, cs.size_refs())) {
int pos = bits;
ensure_throw(prepare_reserve(len));
td::bitstring::bits_memcpy(td::BitPtr{data, pos}, cs.data_bits(), len);
for (unsigned i = 0; i < cs.size_refs(); i++) {
refs[refs_cnt++] = cs.prefetch_ref(i);
}
return true;
} else {
return false;
}
}
CellBuilder& CellBuilder::append_cellslice(const CellSlice& cs) {
return ensure_pass(append_cellslice_bool(cs));
}
bool CellBuilder::append_cellslice_bool(Ref<CellSlice> cs_ref) {
return cs_ref.not_null() && append_cellslice_bool(*cs_ref);
}
CellBuilder& CellBuilder::append_cellslice(Ref<CellSlice> cs) {
return ensure_pass(append_cellslice_bool(cs));
}
bool CellBuilder::append_cellslice_chk(const CellSlice& cs, unsigned size_ext) {
return cs.size_ext() == size_ext && append_cellslice_bool(cs);
}
bool CellBuilder::append_cellslice_chk(Ref<CellSlice> cs_ref, unsigned size_ext) {
return cs_ref.not_null() && append_cellslice_chk(*cs_ref, size_ext);
}
bool CellBuilder::append_bitstring(const td::BitString& bs) {
return store_bits_bool(bs.cbits(), bs.size());
}
bool CellBuilder::append_bitstring(Ref<td::BitString> bs_ref) {
return bs_ref.not_null() && append_bitstring(*bs_ref);
}
bool CellBuilder::append_bitstring_chk(const td::BitString& bs, unsigned size) {
return bs.size() == size && store_bits_bool(bs.cbits(), size);
}
bool CellBuilder::append_bitstring_chk(Ref<td::BitString> bs_ref, unsigned size) {
return bs_ref.not_null() && append_bitstring_chk(*bs_ref, size);
}
bool CellBuilder::append_bitslice(const td::BitSlice& bs) {
return store_bits_bool(bs.bits(), bs.size());
}
bool CellBuilder::store_maybe_ref(Ref<Cell> cell) {
if (cell.is_null()) {
return store_long_bool(0, 1);
} else {
return store_long_bool(1, 1) && store_ref_bool(std::move(cell));
}
}
void CellBuilder::flush(unsigned char d[2]) const {
assert(refs_cnt <= Cell::max_refs && bits <= Cell::max_bits);
unsigned l = (bits >> 3);
if (bits & 7) {
int m = (0x80 >> (bits & 7));
data[l] = static_cast<unsigned char>((data[l] & -m) | m);
d[1] = static_cast<unsigned char>(2 * l + 1);
} else {
d[1] = static_cast<unsigned char>(2 * l);
}
d[0] = static_cast<unsigned char>(refs_cnt);
}
const unsigned char* CellBuilder::compute_hash(unsigned char buffer[Cell::hash_bytes]) const {
unsigned char tmp[2];
flush(tmp);
digest::SHA256 hasher(tmp, 2);
hasher.feed(data, (bits + 7) >> 3);
for (unsigned i = 0; i < refs_cnt; i++) {
hasher.feed(refs[i]->get_hash().as_slice().data(), Cell::hash_bytes);
}
auto extracted_size = hasher.extract(buffer);
DCHECK(extracted_size == Cell::hash_bytes);
return buffer;
}
int CellBuilder::serialize(unsigned char* buff, int buff_size) const {
int len = get_serialized_size();
if (len > buff_size) {
return 0;
}
flush(buff);
std::memcpy(buff + 2, data, len - 2);
return len;
}
CellBuilder* CellBuilder::make_copy() const {
CellBuilder* c = new CellBuilder();
if (!c) {
throw CellWriteError();
}
c->bits = bits;
std::memcpy(c->data, data, (bits + 7) >> 3);
c->refs_cnt = refs_cnt;
for (unsigned i = 0; i < refs_cnt; i++) {
c->refs[i] = refs[i];
}
return c;
}
CellSlice CellBuilder::as_cellslice() const& {
return CellSlice{finalize_copy()};
}
Ref<CellSlice> CellBuilder::as_cellslice_ref() const& {
return Ref<CellSlice>{true, finalize_copy()};
}
CellSlice CellBuilder::as_cellslice() && {
return CellSlice{finalize()};
}
Ref<CellSlice> CellBuilder::as_cellslice_ref() && {
return Ref<CellSlice>{true, finalize()};
}
bool CellBuilder::contents_equal(const CellSlice& cs) const {
if (size() != cs.size() || size_refs() != cs.size_refs()) {
return false;
}
if (td::bitstring::bits_memcmp(data_bits(), cs.data_bits(), size())) {
return false;
}
for (unsigned i = 0; i < size_refs(); i++) {
if (refs[i]->get_hash() != cs.prefetch_ref(i)->get_hash()) {
return false;
}
}
return true;
}
std::string CellBuilder::serialize() const {
unsigned char buff[Cell::max_serialized_bytes];
int len = serialize(buff, sizeof(buff));
return std::string(buff, buff + len);
}
std::string CellBuilder::to_hex() const {
unsigned char buff[Cell::max_serialized_bytes];
int len = serialize(buff, sizeof(buff));
char hex_buff[Cell::max_serialized_bytes * 2 + 1];
for (int i = 0; i < len; i++) {
sprintf(hex_buff + 2 * i, "%02x", buff[i]);
}
return hex_buff;
}
std::ostream& operator<<(std::ostream& os, const CellBuilder& cb) {
return os << cb.to_hex();
}
} // namespace vm

View file

@ -0,0 +1,238 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/DataCell.h"
#include "vm/cells/VirtualCell.h"
#include "vm/vmstate.h"
#include "common/refint.h"
#include "td/utils/ThreadSafeCounter.h"
namespace vm {
class CellSlice;
class DataCell;
class CellBuilder : public td::CntObject {
public:
struct CellWriteError {};
struct CellCreateError {};
private:
unsigned bits;
unsigned refs_cnt;
std::array<Ref<Cell>, Cell::max_refs> refs;
mutable unsigned char data[Cell::max_bytes];
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("CellBuilder");
return res;
}
public:
CellBuilder();
virtual ~CellBuilder() override;
static Ref<Cell> create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level = Cell::max_level);
static Ref<DataCell> do_create_pruned_branch(Ref<Cell> cell, td::uint32 new_level,
td::uint32 virt_level = Cell::max_level);
static Ref<DataCell> create_merkle_proof(Ref<Cell> cell_proof);
static Ref<DataCell> create_merkle_update(Ref<Cell> from_proof, Ref<Cell> to_proof);
unsigned get_refs_cnt() const {
return refs_cnt;
}
unsigned get_bits() const {
return bits;
}
unsigned size_refs() const {
return refs_cnt;
}
unsigned size() const {
return bits;
}
unsigned size_ext() const {
return (refs_cnt << 16) + bits;
}
unsigned remaining_bits() const {
return Cell::max_bits - bits;
}
unsigned remaining_refs() const {
return Cell::max_refs - refs_cnt;
}
const unsigned char* get_data() const {
return data;
}
td::ConstBitPtr data_bits() const {
return data;
}
Ref<Cell> get_ref(unsigned idx) const {
return idx < refs_cnt ? refs[idx] : Ref<Cell>{};
}
void reset();
CellBuilder& operator=(const CellBuilder&);
CellBuilder& operator=(CellBuilder&&);
CellBuilder& store_bytes(const char* str, std::size_t len);
CellBuilder& store_bytes(const char* str, const char* end);
CellBuilder& store_bytes(const unsigned char* str, std::size_t len);
CellBuilder& store_bytes(const unsigned char* str, const unsigned char* end);
CellBuilder& store_bytes(td::Slice s);
bool store_bytes_bool(const unsigned char* str, std::size_t len);
bool store_bytes_bool(const char* str, std::size_t len);
bool store_bytes_bool(td::Slice s);
CellBuilder& store_bits(const unsigned char* str, std::size_t bit_count, int bit_offset = 0);
CellBuilder& store_bits(const char* str, std::size_t bit_count, int bit_offset = 0);
CellBuilder& store_bits(td::ConstBitPtr bs, std::size_t bit_count);
CellBuilder& store_bits(const td::BitSlice& bs);
CellBuilder& store_bits_same(std::size_t bit_count, bool val);
bool store_bits_bool(const unsigned char* str, std::size_t bit_count, int bit_offset = 0);
bool store_bits_bool(td::ConstBitPtr bs, std::size_t bit_count);
template <unsigned n>
bool store_bits_bool(const td::BitArray<n>& ba) {
return store_bits_bool(ba.cbits(), n);
}
bool store_bits_same_bool(std::size_t bit_count, bool val);
CellBuilder& store_zeroes(std::size_t bit_count) {
return store_bits_same(bit_count, false);
}
CellBuilder& store_ones(std::size_t bit_count) {
return store_bits_same(bit_count, true);
}
bool store_zeroes_bool(std::size_t bit_count) {
return store_bits_same_bool(bit_count, false);
}
bool store_ones_bool(std::size_t bit_count) {
return store_bits_same_bool(bit_count, true);
}
td::BitSliceWrite reserve_slice(std::size_t bit_count);
CellBuilder& reserve_slice(std::size_t bit_count, td::BitSliceWrite& bsw);
bool store_long_bool(long long val, unsigned val_bits = 64);
bool store_long_rchk_bool(long long val, unsigned val_bits = 64);
bool store_ulong_rchk_bool(unsigned long long val, unsigned val_bits = 64);
bool store_uint_less(unsigned upper_bound, unsigned long long val);
bool store_uint_leq(unsigned upper_bound, unsigned long long val);
CellBuilder& store_long(long long val, unsigned val_bits = 64);
// bool store_long_top_bool(unsigned long long val, unsigned top_bits);
CellBuilder& store_long_top(unsigned long long val, unsigned top_bits);
bool store_bool_bool(bool val);
bool store_int256_bool(const td::BigInt256& val, unsigned val_bits, bool sgnd = true);
bool store_int256_bool(td::RefInt256 val, unsigned val_bits, bool sgnd = true);
bool store_uint256_bool(const td::BigInt256& val, unsigned val_bits) {
return store_int256_bool(val, val_bits, false);
}
bool store_uint256_bool(td::RefInt256 val, unsigned val_bits) {
return store_int256_bool(std::move(val), val_bits, false);
}
CellBuilder& store_int256(const td::BigInt256& val, unsigned val_bits, bool sgnd = true);
CellBuilder& store_uint256(const td::BigInt256& val, unsigned val_bits) {
return store_int256(val, val_bits, false);
}
bool store_builder_ref_bool(vm::CellBuilder&& cb);
bool store_ref_bool(Ref<Cell> r);
CellBuilder& store_ref(Ref<Cell> r);
bool append_data_cell_bool(const DataCell& cell);
CellBuilder& append_data_cell(const DataCell& cell);
bool append_data_cell_bool(Ref<DataCell> cell_ref);
CellBuilder& append_data_cell(Ref<DataCell> cell_ref);
bool append_builder_bool(const CellBuilder& cb);
CellBuilder& append_builder(const CellBuilder& cb);
bool append_builder_bool(Ref<CellBuilder> cb_ref);
CellBuilder& append_builder(Ref<CellBuilder> cb_ref);
bool append_cellslice_bool(const CellSlice& cs);
CellBuilder& append_cellslice(const CellSlice& cs);
bool append_cellslice_bool(Ref<CellSlice> cs_ref);
CellBuilder& append_cellslice(Ref<CellSlice> cs_ref);
bool append_cellslice_chk(const CellSlice& cs, unsigned size_ext);
bool append_cellslice_chk(Ref<CellSlice> cs, unsigned size_ext);
bool append_bitstring(const td::BitString& bs);
bool append_bitstring(Ref<td::BitString> bs_ref);
bool append_bitstring_chk(const td::BitString& bs, unsigned size);
bool append_bitstring_chk(Ref<td::BitString> bs, unsigned size);
bool append_bitslice(const td::BitSlice& bs);
bool store_maybe_ref(Ref<Cell> cell);
bool contents_equal(const CellSlice& cs) const;
CellBuilder* make_copy() const override;
bool can_extend_by(std::size_t bits) const;
bool can_extend_by(std::size_t bits, unsigned refs) const;
Ref<DataCell> finalize_copy(bool special = false) const;
Ref<DataCell> finalize(bool special = false);
Ref<DataCell> finalize_novm(bool special = false);
bool finalize_to(Ref<Cell>& res, bool special = false) {
return (res = finalize(special)).not_null();
}
CellSlice as_cellslice() const &;
CellSlice as_cellslice() &&;
Ref<CellSlice> as_cellslice_ref() const &;
Ref<CellSlice> as_cellslice_ref() &&;
static td::int64 get_total_cell_builders() {
return get_thread_safe_counter().sum();
}
int get_serialized_size() const {
return ((bits + 23) >> 3);
}
int serialize(unsigned char* buff, int buff_size) const;
std::string serialize() const;
std::string to_hex() const;
const unsigned char* compute_hash(unsigned char buffer[Cell::hash_bytes]) const;
const CellBuilder& ensure_pass(bool cond) const {
ensure_throw(cond);
return *this;
}
CellBuilder& ensure_pass(bool cond) {
ensure_throw(cond);
return *this;
}
void ensure_throw(bool cond) const {
if (!cond) {
throw CellCreateError{};
}
}
private:
void flush(unsigned char d[2]) const;
bool prepare_reserve(std::size_t bit_count);
bool can_extend_by_fast(unsigned bits_req) const {
return (int)bits <= (int)(Cell::max_bits - bits_req);
}
bool can_extend_by_fast(unsigned bits_req, unsigned refs_req) const {
return (int)bits <= (int)(Cell::max_bits - bits_req) && (int)refs_cnt <= (int)(Cell::max_refs - refs_req);
}
bool can_extend_by_fast2(unsigned bits_req) const {
return bits + bits_req <= Cell::max_bits;
}
bool can_extend_by_fast2(unsigned bits_req, unsigned refs_req) const {
return bits + bits_req <= Cell::max_bits && refs_cnt + refs_req <= Cell::max_refs;
}
};
std::ostream& operator<<(std::ostream& os, const CellBuilder& cb);
template <class T>
CellBuilder& operator<<(CellBuilder& cb, const T& val) {
return cb.ensure_pass(val.serialize(cb));
}
template <class T>
Ref<CellBuilder>& operator<<(Ref<CellBuilder>& cb_ref, const T& val) {
bool res = val.serialize(cb_ref.write());
cb_ref->ensure_throw(res);
return cb_ref;
}
} // namespace vm

View file

@ -0,0 +1,28 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/CellHash.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Slice.h"
namespace vm {
td::StringBuilder &operator<<(td::StringBuilder &sb, const CellHash &hash) {
return sb << hash.to_hex();
}
} // namespace vm

View file

@ -0,0 +1,92 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/CellTraits.h"
#include "common/bitstring.h"
#include "td/utils/as.h"
#include <array>
namespace td {
class StringBuilder;
}
namespace vm {
struct CellHash {
public:
td::Slice as_slice() const {
return td::Slice(hash_.data(), hash_.size());
}
td::MutableSlice as_slice() {
return td::MutableSlice(hash_.data(), hash_.size());
}
bool operator==(const CellHash& other) const {
return hash_ == other.hash_;
}
bool operator<(const CellHash& other) const {
return hash_ < other.hash_;
}
bool operator!=(const CellHash& other) const {
return hash_ != other.hash_;
}
std::string to_hex() const {
return td::ConstBitPtr{hash_.data()}.to_hex(hash_.size() * 8);
}
friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CellHash& hash);
td::ConstBitPtr bits() const {
return td::ConstBitPtr{hash_.data()};
}
td::BitPtr bits() {
return td::BitPtr{hash_.data()};
}
td::BitSlice as_bitslice() const {
return td::BitSlice{hash_.data(), (unsigned int)hash_.size() * 8};
}
const std::array<td::uint8, CellTraits::hash_bytes>& as_array() const {
return hash_;
}
static CellHash from_slice(td::Slice slice) {
CellHash res;
CHECK(slice.size() == res.hash_.size());
td::MutableSlice(res.hash_.data(), res.hash_.size()).copy_from(slice);
return res;
}
private:
std::array<td::uint8, CellTraits::hash_bytes> hash_;
};
} // namespace vm
namespace std {
template <>
struct hash<vm::CellHash> {
typedef vm::CellHash argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& s) const noexcept {
return td::as<size_t>(s.as_slice().ubegin());
}
};
} // namespace std
namespace vm {
template <class H>
H AbslHashValue(H h, const CellHash& cell_hash) {
return H::combine(std::move(h), std::hash<vm::CellHash>()(cell_hash));
}
} // namespace vm

File diff suppressed because it is too large Load diff

328
crypto/vm/cells/CellSlice.h Normal file
View file

@ -0,0 +1,328 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refcnt.hpp"
#include "common/refint.h"
#include "vm/cells.h"
namespace td {
class StringBuilder;
}
namespace vm {
struct NoVm {};
struct NoVmOrd {};
struct NoVmSpec {};
class CellSlice : public td::CntObject {
Cell::VirtualizationParameters virt;
Ref<DataCell> cell;
CellUsageTree::NodePtr tree_node;
unsigned bits_st, refs_st;
unsigned bits_en, refs_en;
mutable const unsigned char* ptr{nullptr};
mutable unsigned long long z;
mutable unsigned zd;
public:
static constexpr long long fetch_long_eof = (static_cast<unsigned long long>(-1LL) << 63);
static constexpr unsigned long long fetch_ulong_eof = (unsigned long long)-1LL;
struct CellReadError {};
CellSlice(NoVm, Ref<Cell> cell_ref);
CellSlice(NoVmOrd, Ref<Cell> cell_ref);
CellSlice(NoVmSpec, Ref<Cell> cell_ref);
CellSlice(Ref<DataCell> dc_ref);
CellSlice(VirtualCell::LoadedCell loaded_cell);
/*
CellSlice(Ref<DataCell> dc_ref, unsigned _bits_en, unsigned _refs_en, unsigned _bits_st = 0, unsigned _refs_st = 0);*/
CellSlice(const CellSlice& cs, unsigned _bits_en, unsigned _refs_en);
CellSlice(const CellSlice& cs, unsigned _bits_en, unsigned _refs_en, unsigned _bits_st, unsigned _refs_st);
CellSlice(const CellSlice&);
CellSlice& operator=(const CellSlice& other) = default;
CellSlice();
Cell::LoadedCell move_as_loaded_cell();
td::CntObject* make_copy() const override {
return new CellSlice{*this};
}
void clear();
bool load(VirtualCell::LoadedCell loaded_cell);
bool load(NoVm, Ref<Cell> cell_ref);
bool load(NoVmOrd, Ref<Cell> cell_ref);
bool load(NoVmSpec, Ref<Cell> cell_ref);
bool load(Ref<DataCell> dc_ref);
bool load(Ref<Cell> cell);
bool load_ord(Ref<Cell> cell);
unsigned size() const {
return bits_en - bits_st;
}
bool is_special() const {
return cell->is_special();
}
bool is_valid() const {
return cell.not_null();
}
Cell::SpecialType special_type() const {
return cell->special_type();
}
int child_merkle_depth(int merkle_depth) const {
if (merkle_depth == Cell::VirtualizationParameters::max_level()) {
return merkle_depth;
}
if (cell->special_type() == Cell::SpecialType::MerkleProof ||
cell->special_type() == Cell::SpecialType::MerkleUpdate) {
merkle_depth++;
}
return merkle_depth;
}
unsigned size_refs() const {
return refs_en - refs_st;
}
unsigned size_ext() const {
return size() + (size_refs() << 16);
}
bool have(unsigned bits) const {
return bits <= size();
}
bool have(unsigned bits, unsigned refs) const {
return bits <= size() && refs <= size_refs();
}
bool have_ext(unsigned ext_size) const {
return have(ext_size & 0xffff, ext_size >> 16);
}
bool empty() const {
return !size();
}
bool empty_ext() const {
return !size() && !size_refs();
}
bool have_refs(unsigned refs = 1) const {
return refs <= size_refs();
}
bool advance(unsigned bits);
bool advance_refs(unsigned refs);
bool advance_ext(unsigned bits_refs);
bool advance_ext(unsigned bits, unsigned refs);
unsigned cur_pos() const {
return bits_st;
}
unsigned cur_ref() const {
return refs_st;
}
const unsigned char* data() const {
return cell->get_data();
}
td::ConstBitPtr data_bits() const {
return td::ConstBitPtr{data(), (int)cur_pos()};
}
int subtract_base_ext(const CellSlice& base) {
return (bits_st - base.bits_st) | ((refs_st - base.refs_st) << 16);
}
unsigned get_cell_level() const;
unsigned get_level() const;
int fetch_octet();
int prefetch_octet() const;
unsigned long long prefetch_ulong_top(unsigned& bits) const;
unsigned long long fetch_ulong(unsigned bits);
unsigned long long prefetch_ulong(unsigned bits) const;
long long fetch_long(unsigned bits);
long long prefetch_long(unsigned bits) const;
bool fetch_long_bool(unsigned bits, long long& res);
bool prefetch_long_bool(unsigned bits, long long& res) const;
bool fetch_ulong_bool(unsigned bits, unsigned long long& res);
bool prefetch_ulong_bool(unsigned bits, unsigned long long& res) const;
bool fetch_bool_to(bool& res);
bool fetch_bool_to(int& res);
bool fetch_bool_to(int& res, int mask);
bool fetch_uint_to(unsigned bits, unsigned long long& res);
bool fetch_uint_to(unsigned bits, long long& res);
bool fetch_uint_to(unsigned bits, unsigned long& res);
bool fetch_uint_to(unsigned bits, long& res);
bool fetch_uint_to(unsigned bits, unsigned& res);
bool fetch_uint_to(unsigned bits, int& res);
bool fetch_uint_less(unsigned upper_bound, int& res);
bool fetch_uint_less(unsigned upper_bound, unsigned& res);
bool fetch_uint_leq(unsigned upper_bound, int& res);
bool fetch_uint_leq(unsigned upper_bound, unsigned& res);
bool fetch_int_to(unsigned bits, long long& res);
bool fetch_int_to(unsigned bits, int& res);
int bselect(unsigned bits, unsigned long long mask) const;
int bselect_ext(unsigned bits, unsigned long long mask) const;
int bit_at(unsigned i) const {
return have(i) ? data_bits()[i] : -1;
}
td::RefInt256 fetch_int256(unsigned bits, bool sgnd = true);
td::RefInt256 prefetch_int256(unsigned bits, bool sgnd = true) const;
td::RefInt256 prefetch_int256_zeroext(unsigned bits, bool sgnd = true) const;
bool fetch_int256_to(unsigned bits, td::RefInt256& res, bool sgnd = true) {
return (res = fetch_int256(bits, sgnd)).not_null();
}
bool fetch_uint256_to(unsigned bits, td::RefInt256& res) {
return (res = fetch_int256(bits, false)).not_null();
}
Ref<Cell> prefetch_ref(unsigned offset = 0) const;
Ref<Cell> fetch_ref();
bool fetch_ref_to(Ref<Cell>& ref) {
return (ref = fetch_ref()).not_null();
}
bool prefetch_ref_to(Ref<Cell>& ref, unsigned offset = 0) const {
return (ref = prefetch_ref(offset)).not_null();
}
td::BitSlice fetch_bits(unsigned bits);
td::BitSlice prefetch_bits(unsigned bits) const;
td::Ref<CellSlice> fetch_subslice(unsigned bits, unsigned refs = 0);
td::Ref<CellSlice> prefetch_subslice(unsigned bits, unsigned refs = 0) const;
td::Ref<CellSlice> fetch_subslice_ext(unsigned size);
td::Ref<CellSlice> prefetch_subslice_ext(unsigned size) const;
td::Ref<td::BitString> fetch_bitstring(unsigned size);
td::Ref<td::BitString> prefetch_bitstring(unsigned size) const;
bool fetch_subslice_to(unsigned bits, td::Ref<CellSlice>& res) {
return (res = fetch_subslice(bits)).not_null();
}
bool fetch_subslice_ext_to(unsigned bits, td::Ref<CellSlice>& res) {
return (res = fetch_subslice_ext(bits)).not_null();
}
bool fetch_bitstring_to(unsigned bits, td::Ref<td::BitString>& res) {
return (res = fetch_bitstring(bits)).not_null();
}
bool fetch_bits_to(td::BitPtr buffer, unsigned bits);
bool prefetch_bits_to(td::BitPtr buffer, unsigned bits) const;
template <unsigned n>
bool fetch_bits_to(td::BitArray<n>& buffer) {
return fetch_bits_to(buffer.bits(), n);
}
template <unsigned n>
bool prefetch_bits_to(td::BitArray<n>& buffer) const {
return prefetch_bits_to(buffer.bits(), n);
}
bool fetch_bytes(unsigned char* buffer, unsigned bytes);
bool prefetch_bytes(unsigned char* buffer, unsigned bytes) const;
td::BitSlice as_bitslice() const {
return prefetch_bits(size());
}
bool begins_with(unsigned bits, unsigned long long value) const;
bool begins_with(unsigned long long value) const;
bool begins_with_skip(unsigned bits, unsigned long long value) {
return begins_with(bits, value) && advance(bits);
}
bool begins_with_skip(unsigned long long value);
bool only_first(unsigned bits, unsigned refs = 0);
bool only_ext(unsigned size);
bool skip_first(unsigned bits, unsigned refs = 0);
bool skip_ext(unsigned size);
bool only_last(unsigned bits, unsigned refs = 0);
bool skip_last(unsigned bits, unsigned refs = 0);
bool cut_tail(const CellSlice& tail_cs);
int remove_trailing();
int count_leading(bool bit) const;
int count_trailing(bool bit) const;
int lex_cmp(const CellSlice& cs2) const;
int common_prefix_len(const CellSlice& cs2) const;
bool is_prefix_of(const CellSlice& cs2) const;
bool is_prefix_of(td::ConstBitPtr bs, unsigned len) const;
bool is_suffix_of(const CellSlice& cs2) const;
bool has_prefix(const CellSlice& cs2) const;
bool has_prefix(td::ConstBitPtr bs, unsigned len) const;
bool has_suffix(const CellSlice& cs2) const;
bool is_proper_prefix_of(const CellSlice& cs2) const;
bool is_proper_suffix_of(const CellSlice& cs2) const;
// int common_prefix_len(const td::BitSlice& bs, unsigned offs = 0, unsigned max_len = 0xffffffffU) const;
int common_prefix_len(td::ConstBitPtr bs, unsigned len) const;
// bool is_prefix_of(const td::BitSlice& bs, unsigned offs = 0, unsigned max_len = 0xffffffffU) const;
bool contents_equal(const CellSlice& cs2) const;
void dump(std::ostream& os, int level = 0, bool endl = true) const;
void dump_hex(std::ostream& os, int mode = 0, bool endl = false) const;
void print_rec(std::ostream& os, int indent = 0) const;
void error() const {
throw CellReadError{};
}
bool chk(bool cond) const {
if (!cond) {
error();
}
return cond;
}
bool have_chk(unsigned bits) const {
return chk(have(bits));
}
bool have_chk(unsigned bits, unsigned refs) const {
return chk(have(bits, refs));
}
bool have_refs_chk(unsigned refs = 1) const {
return chk(have_refs(refs));
}
CellSlice operator+(unsigned offs) const {
offs = std::min(offs, size());
return CellSlice{*this, size() - offs, size_refs(), offs, 0};
}
private:
void init_bits_refs();
void init_preload() const;
void preload_at_least(unsigned req_bits) const;
Cell::VirtualizationParameters child_virt() const {
return Cell::VirtualizationParameters(static_cast<td::uint8>(child_merkle_depth(virt.get_level())),
virt.get_virtualization());
}
};
td::StringBuilder& operator<<(td::StringBuilder& sb, const CellSlice& cs);
bool cell_builder_add_slice_bool(CellBuilder& cb, const CellSlice& cs);
CellBuilder& cell_builder_add_slice(CellBuilder& cb, const CellSlice& cs);
std::ostream& operator<<(std::ostream& os, CellSlice cs);
std::ostream& operator<<(std::ostream& os, Ref<CellSlice> cs_ref);
template <class T>
CellSlice& operator>>(CellSlice& cs, T& val) {
cs.chk(val.deserialize(cs));
return cs;
}
template <class T>
Ref<CellSlice>& operator>>(Ref<CellSlice>& cs_ref, T& val) {
bool res = val.deserialize(cs_ref.write());
cs_ref->chk(res);
return cs_ref;
}
template <class T>
CellSlice& operator>>(CellSlice& cs, const T& val) {
cs.chk(val.deserialize(cs));
return cs;
}
template <class T>
Ref<CellSlice>& operator>>(Ref<CellSlice>& cs_ref, const T& val) {
bool res = val.deserialize(cs_ref.write());
cs_ref->chk(res);
return cs_ref;
}
// If can_be_special is not null, then it is allowed to load special cell
// Flag whether loaded cell is actually special will be stored into can_be_special
CellSlice load_cell_slice(const Ref<Cell>& cell);
Ref<CellSlice> load_cell_slice_ref(const Ref<Cell>& cell);
CellSlice load_cell_slice_special(const Ref<Cell>& cell, bool& is_special);
Ref<CellSlice> load_cell_slice_ref_special(const Ref<Cell>& cell, bool& is_special);
void print_load_cell(std::ostream& os, Ref<Cell> cell, int indent = 0);
} // namespace vm

View file

@ -0,0 +1,45 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/CellTraits.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Slice.h"
namespace vm {
td::StringBuilder& operator<<(td::StringBuilder& sb, CellTraits::SpecialType special_type) {
switch (special_type) {
case CellTraits::SpecialType::Ordinary:
sb << "Ordinary";
break;
case CellTraits::SpecialType::MerkleProof:
sb << "MerkleProof";
break;
case CellTraits::SpecialType::MerkleUpdate:
sb << "MerkleUpdate";
break;
case CellTraits::SpecialType::PrunnedBranch:
sb << "PrunnedBranch";
break;
case CellTraits::SpecialType::Library:
sb << "Library";
break;
}
return sb;
}
} // namespace vm

View file

@ -0,0 +1,52 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/int_types.h"
#include "common/refcnt.hpp"
namespace td {
class StringBuilder;
}
namespace vm {
class CellTraits : public td::CntObject {
public:
enum class SpecialType : td::uint8 {
Ordinary = 0,
PrunnedBranch = 1,
Library = 2,
MerkleProof = 3,
MerkleUpdate = 4
};
enum {
max_refs = 4,
max_bytes = 128,
max_bits = 1023,
hash_bytes = 32,
hash_bits = hash_bytes * 8,
depth_bytes = 2,
depth_bits = depth_bytes * 8,
max_level = 3,
max_depth = 1024,
max_virtualization = 7,
max_serialized_bytes = 2 + max_bytes + (max_level + 1) * (hash_bytes + depth_bytes)
};
};
td::StringBuilder& operator<<(td::StringBuilder& sb, CellTraits::SpecialType special_type);
} // namespace vm

View file

@ -0,0 +1,136 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/CellUsageTree.h"
namespace vm {
//
// CellUsageTree::NodePtr
//
bool CellUsageTree::NodePtr::on_load() const {
auto tree = tree_weak_.lock();
if (!tree) {
return false;
}
tree->on_load(node_id_);
return true;
}
CellUsageTree::NodePtr CellUsageTree::NodePtr::create_child(unsigned ref_id) const {
auto tree = tree_weak_.lock();
if (!tree) {
return {};
}
return {tree_weak_, tree->create_child(node_id_, ref_id)};
}
bool CellUsageTree::NodePtr::is_from_tree(const CellUsageTree* master_tree) const {
DCHECK(master_tree);
auto tree = tree_weak_.lock();
if (tree.get() != master_tree) {
return false;
}
return true;
}
bool CellUsageTree::NodePtr::mark_path(CellUsageTree* master_tree) const {
DCHECK(master_tree);
auto tree = tree_weak_.lock();
if (tree.get() != master_tree) {
return false;
}
master_tree->mark_path(node_id_);
return true;
}
//
// CellUsageTree
//
CellUsageTree::NodePtr CellUsageTree::root_ptr() {
return {shared_from_this(), 1};
}
CellUsageTree::NodeId CellUsageTree::root_id() const {
return 1;
};
bool CellUsageTree::is_loaded(NodeId node_id) const {
if (use_mark_) {
return nodes_[node_id].has_mark;
}
return nodes_[node_id].is_loaded;
}
bool CellUsageTree::has_mark(NodeId node_id) const {
return nodes_[node_id].has_mark;
}
void CellUsageTree::set_mark(NodeId node_id, bool mark) {
if (node_id == 0) {
return;
}
nodes_[node_id].has_mark = mark;
}
void CellUsageTree::mark_path(NodeId node_id) {
auto cur_node_id = get_parent(node_id);
while (cur_node_id != 0) {
if (has_mark(cur_node_id)) {
break;
}
set_mark(cur_node_id);
cur_node_id = get_parent(cur_node_id);
}
}
CellUsageTree::NodeId CellUsageTree::get_parent(NodeId node_id) {
return nodes_[node_id].parent;
}
CellUsageTree::NodeId CellUsageTree::get_child(NodeId node_id, unsigned ref_id) {
DCHECK(ref_id < CellTraits::max_refs);
return nodes_[node_id].children[ref_id];
}
void CellUsageTree::set_use_mark_for_is_loaded(bool use_mark) {
use_mark_ = use_mark;
}
void CellUsageTree::on_load(NodeId node_id) {
nodes_[node_id].is_loaded = true;
}
CellUsageTree::NodeId CellUsageTree::create_child(NodeId node_id, unsigned ref_id) {
DCHECK(ref_id < CellTraits::max_refs);
NodeId res = nodes_[node_id].children[ref_id];
if (res) {
return res;
}
res = create_node(node_id);
nodes_[node_id].children[ref_id] = res;
return res;
}
CellUsageTree::NodeId CellUsageTree::create_node(NodeId parent) {
NodeId res = static_cast<NodeId>(nodes_.size());
nodes_.emplace_back();
nodes_.back().parent = parent;
return res;
}
} // namespace vm

View file

@ -0,0 +1,75 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/CellTraits.h"
#include "td/utils/int_types.h"
#include "td/utils/logging.h"
namespace vm {
class CellUsageTree : public std::enable_shared_from_this<CellUsageTree> {
public:
using NodeId = td::uint32;
struct NodePtr {
public:
NodePtr() = default;
NodePtr(std::weak_ptr<CellUsageTree> tree_weak, NodeId node_id)
: tree_weak_(std::move(tree_weak)), node_id_(node_id) {
}
bool empty() const {
return node_id_ == 0 || tree_weak_.expired();
}
bool on_load() const;
NodePtr create_child(unsigned ref_id) const;
bool mark_path(CellUsageTree* master_tree) const;
bool is_from_tree(const CellUsageTree* master_tree) const;
private:
std::weak_ptr<CellUsageTree> tree_weak_;
NodeId node_id_{0};
};
NodePtr root_ptr();
NodeId root_id() const;
bool is_loaded(NodeId node_id) const;
bool has_mark(NodeId node_id) const;
void set_mark(NodeId node_id, bool mark = true);
void mark_path(NodeId node_id);
NodeId get_parent(NodeId node_id);
NodeId get_child(NodeId node_id, unsigned ref_id);
void set_use_mark_for_is_loaded(bool use_mark = true);
NodeId create_child(NodeId node_id, unsigned ref_id);
private:
struct Node {
bool is_loaded{false};
bool has_mark{false};
NodeId parent{0};
std::array<td::uint32, CellTraits::max_refs> children{};
};
bool use_mark_{false};
std::vector<Node> nodes_{2};
void on_load(NodeId node_id);
NodeId create_node(NodeId parent);
};
} // namespace vm

View file

@ -0,0 +1,92 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
namespace detail {
template <class CellT, size_t Size = 0>
class CellWithArrayStorage : public CellT {
public:
template <class... ArgsT>
CellWithArrayStorage(ArgsT&&... args) : CellT(std::forward<ArgsT>(args)...) {
}
~CellWithArrayStorage() {
CellT::destroy_storage(get_storage());
}
template <class... ArgsT>
static std::unique_ptr<CellT> create(size_t storage_size, ArgsT&&... args) {
static_assert(CellT::max_storage_size <= 40 * 8, "");
//size = 128 + 32 + 8;
auto size = (storage_size + 7) / 8;
#define CASE(size) \
case (size): \
return std::make_unique<CellWithArrayStorage<CellT, (size)*8>>(std::forward<ArgsT>(args)...);
#define CASE2(offset) CASE(offset) CASE(offset + 1)
#define CASE8(offset) CASE2(offset) CASE2(offset + 2) CASE2(offset + 4) CASE2(offset + 6)
#define CASE32(offset) CASE8(offset) CASE8(offset + 8) CASE8(offset + 16) CASE8(offset + 24)
switch (size) { CASE32(0) CASE8(32) }
#undef CASE
#undef CASE2
#undef CASE8
#undef CASE32
LOG(FATAL) << "TOO BIG " << storage_size;
UNREACHABLE();
}
private:
alignas(alignof(void*)) char storage_[Size];
const char* get_storage() const final {
return storage_;
}
char* get_storage() final {
return storage_;
}
};
template <class CellT>
class CellWithUniquePtrStorage : public CellT {
public:
template <class... ArgsT>
CellWithUniquePtrStorage(size_t storage_size, ArgsT&&... args)
: CellT(std::forward<ArgsT>(args)...), storage_(std::make_unique<char[]>(storage_size)) {
}
~CellWithUniquePtrStorage() {
CellT::destroy_storage(get_storage());
}
template <class... ArgsT>
static std::unique_ptr<CellT> create(size_t storage_size, ArgsT&&... args) {
return std::make_unique<CellWithUniquePtrStorage>(storage_size, std::forward<ArgsT>(args)...);
}
private:
std::unique_ptr<char[]> storage_;
const char* get_storage() const final {
CHECK(storage_);
return storage_.get();
}
char* get_storage() final {
CHECK(storage_);
return storage_.get();
}
};
} // namespace detail
} // namespace vm

View file

@ -0,0 +1,368 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/DataCell.h"
#include "openssl/digest.h"
#include "td/utils/ScopeGuard.h"
#include "vm/cells/CellWithStorage.h"
namespace vm {
std::unique_ptr<DataCell> DataCell::create_empty_data_cell(Info info) {
return detail::CellWithUniquePtrStorage<DataCell>::create(info.get_storage_size(), info);
}
DataCell::DataCell(Info info) : info_(std::move(info)) {
get_thread_safe_counter().add(1);
}
DataCell::~DataCell() {
get_thread_safe_counter().add(-1);
}
void DataCell::destroy_storage(char* storage) {
auto* refs = info_.get_refs(storage);
for (size_t i = 0; i < get_refs_cnt(); i++) {
Ref<Cell>(refs[i], Ref<Cell>::acquire_t{}); // call destructor
}
}
td::Result<Ref<DataCell>> DataCell::create(td::ConstBitPtr data, unsigned bits, td::Span<Ref<Cell>> refs,
bool special) {
std::array<Ref<Cell>, max_refs> copied_refs;
CHECK(refs.size() <= copied_refs.size());
for (size_t i = 0; i < refs.size(); i++) {
copied_refs[i] = refs[i];
}
return create(std::move(data), bits, td::MutableSpan<Ref<Cell>>(copied_refs.data(), refs.size()), special);
}
DataCell::SpecialType DataCell::special_type() const {
if (is_special()) {
return static_cast<SpecialType>(td::bitstring::bits_load_ulong(get_data(), 8));
}
return SpecialType::Ordinary;
}
td::Result<Ref<DataCell>> DataCell::create(td::ConstBitPtr data, unsigned bits, td::MutableSpan<Ref<Cell>> refs,
bool special) {
for (auto& ref : refs) {
if (ref.is_null()) {
return td::Status::Error("Has null cell reference");
}
}
SpecialType type = SpecialType::Ordinary;
if (special) {
if (bits < 8) {
return td::Status::Error("Not enough data for a special cell");
}
type = static_cast<SpecialType>(td::bitstring::bits_load_ulong(data, 8));
if (type == SpecialType::Ordinary) {
return td::Status::Error("Special cell has Ordinary type");
}
}
LevelMask level_mask;
td::uint32 virtualization = 0;
switch (type) {
case SpecialType::Ordinary: {
for (auto& ref : refs) {
level_mask = level_mask.apply_or(ref->get_level_mask());
virtualization = td::max(virtualization, ref->get_virtualization());
}
break;
}
case SpecialType::PrunnedBranch: {
if (refs.size() != 0) {
return td::Status::Error("PrunnedBranch special cell has a cell reference");
}
if (bits < 16) {
return td::Status::Error("Not enough data for a PrunnedBranch special cell");
}
level_mask = LevelMask((td::bitstring::bits_load_ulong(data + 8, 8)) & 0xff);
auto level = level_mask.get_level();
if (level > max_level || level == 0) {
return td::Status::Error("Prunned Branch has an invalid level");
}
if (bits != (2 + level_mask.apply(level - 1).get_hashes_count() * (hash_bytes + depth_bytes)) * 8) {
return td::Status::Error("Not enouch data for a PrunnedBranch special cell");
}
// depth will be checked later!
break;
}
case SpecialType::Library: {
if (bits != 8 + hash_bytes * 8) {
return td::Status::Error("Not enouch data for a Library special cell");
}
break;
}
case SpecialType::MerkleProof: {
if (bits != 8 + (hash_bytes + depth_bytes) * 8) {
return td::Status::Error("Not enouch data for a MerkleProof special cell");
}
if (refs.size() != 1) {
return td::Status::Error("Wrong references count for a MerkleProof special cell");
}
if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
return td::Status::Error("Hash mismatch in a MerkleProof special cell");
}
if (td::bitstring::bits_load_ulong(data + 8 + hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
return td::Status::Error("Depth mismatch in a MerkleProof special cell");
}
level_mask = refs[0]->get_level_mask().shift_right();
virtualization = refs[0]->get_virtualization();
break;
}
case SpecialType::MerkleUpdate: {
if (bits != 8 + (hash_bytes + depth_bytes) * 8 * 2) {
return td::Status::Error("Not enouch data for a MerkleUpdate special cell");
}
if (refs.size() != 2) {
return td::Status::Error("Wrong references count for a MerkleUpdate special cell");
}
if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
return td::Status::Error("First hash mismatch in a MerkleProof special cell");
}
if (td::bitstring::bits_memcmp(data + 8 + hash_bits, refs[1]->get_hash(0).as_bitslice().get_ptr(), hash_bits) !=
0) {
return td::Status::Error("Second hash mismatch in a MerkleProof special cell");
}
if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
return td::Status::Error("First depth mismatch in a MerkleProof special cell");
}
if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits + depth_bytes * 8, depth_bytes * 8) !=
refs[1]->get_depth(0)) {
return td::Status::Error("Second depth mismatch in a MerkleProof special cell");
}
level_mask = refs[0]->get_level_mask().apply_or(refs[1]->get_level_mask()).shift_right();
virtualization = td::max(refs[0]->get_virtualization(), refs[1]->get_virtualization());
break;
}
default:
return td::Status::Error("Unknown special cell type");
}
Info info;
if (td::unlikely(bits > max_bits)) {
return td::Status::Error("Too many bits");
}
if (td::unlikely(refs.size() > max_refs)) {
return td::Status::Error("Too many cell references");
}
if (td::unlikely(virtualization > max_virtualization)) {
return td::Status::Error("Too big virtualization");
}
CHECK(level_mask.get_level() <= max_level);
auto hash_count = type == SpecialType::PrunnedBranch ? 1 : level_mask.get_hashes_count();
DCHECK(hash_count <= max_level + 1);
info.bits_ = bits;
info.refs_count_ = refs.size() & 7;
info.is_special_ = special;
info.level_mask_ = level_mask.get_mask() & 7;
info.hash_count_ = hash_count & 7;
info.virtualization_ = virtualization & 7;
auto data_cell = create_empty_data_cell(info);
auto* storage = data_cell->get_storage();
// init data
auto* data_ptr = info.get_data(storage);
td::BitPtr{data_ptr}.copy_from(data, bits);
// prepare for serialization
if (bits & 7) {
int m = (0x80 >> (bits & 7));
unsigned l = bits / 8;
data_ptr[l] = static_cast<unsigned char>((data_ptr[l] & -m) | m);
}
// init refs
auto refs_ptr = info.get_refs(storage);
for (size_t i = 0; i < refs.size(); i++) {
refs_ptr[i] = refs[i].release();
}
// init hashes and depth
auto* hashes_ptr = info.get_hashes(storage);
auto* depth_ptr = info.get_depth(storage);
// NB: be careful with special cells
auto total_hash_count = level_mask.get_hashes_count();
auto hash_i_offset = total_hash_count - hash_count;
for (td::uint32 level_i = 0, hash_i = 0, level = level_mask.get_level(); level_i <= level; level_i++) {
if (!level_mask.is_significant(level_i)) {
continue;
}
SCOPE_EXIT {
hash_i++;
};
if (hash_i < hash_i_offset) {
continue;
}
unsigned char tmp[2];
tmp[0] = info.d1(level_mask.apply(level_i));
tmp[1] = info.d2();
static TD_THREAD_LOCAL digest::SHA256* hasher;
td::init_thread_local<digest::SHA256>(hasher);
hasher->reset();
hasher->feed(td::Slice(tmp, 2));
if (hash_i == hash_i_offset) {
DCHECK(level_i == 0 || type == SpecialType::PrunnedBranch);
hasher->feed(td::Slice(data_ptr, (bits + 7) >> 3));
} else {
DCHECK(level_i != 0 && type != SpecialType::PrunnedBranch);
hasher->feed(hashes_ptr[hash_i - hash_i_offset - 1].as_slice());
}
auto dest_i = hash_i - hash_i_offset;
// calc depth
td::uint16 depth = 0;
for (int i = 0; i < info.refs_count_; i++) {
td::uint16 child_depth = 0;
if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
child_depth = refs_ptr[i]->get_depth(level_i + 1);
} else {
child_depth = refs_ptr[i]->get_depth(level_i);
}
// add depth into hash
td::uint8 child_depth_buf[depth_bytes];
store_depth(child_depth_buf, child_depth);
hasher->feed(td::Slice(child_depth_buf, depth_bytes));
depth = std::max(depth, child_depth);
}
if (info.refs_count_ != 0) {
if (depth >= max_depth) {
return td::Status::Error("Depth is too big");
}
depth++;
}
depth_ptr[dest_i] = depth;
// children hash
for (int i = 0; i < info.refs_count_; i++) {
if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
hasher->feed(refs_ptr[i]->get_hash(level_i + 1).as_slice());
} else {
hasher->feed(refs_ptr[i]->get_hash(level_i).as_slice());
}
}
auto extracted_size = hasher->extract(hashes_ptr[dest_i].as_slice());
DCHECK(extracted_size == hash_bytes);
}
return Ref<DataCell>(data_cell.release(), Ref<DataCell>::acquire_t{});
}
const DataCell::Hash DataCell::do_get_hash(td::uint32 level) const {
auto hash_i = get_level_mask().apply(level).get_hash_i();
if (special_type() == SpecialType::PrunnedBranch) {
auto this_hash_i = get_level_mask().get_hash_i();
if (hash_i != this_hash_i) {
return reinterpret_cast<const Hash*>(info_.get_data(get_storage()) + 2)[hash_i];
}
hash_i = 0;
}
return info_.get_hashes(get_storage())[hash_i];
}
td::uint16 DataCell::do_get_depth(td::uint32 level) const {
auto hash_i = get_level_mask().apply(level).get_hash_i();
if (special_type() == SpecialType::PrunnedBranch) {
auto this_hash_i = get_level_mask().get_hash_i();
if (hash_i != this_hash_i) {
return load_depth(info_.get_data(get_storage()) + 2 + hash_bytes * this_hash_i + hash_i * depth_bytes);
}
hash_i = 0;
}
return info_.get_depth(get_storage())[hash_i];
}
int DataCell::serialize(unsigned char* buff, int buff_size, bool with_hashes) const {
int len = get_serialized_size(with_hashes);
if (len > buff_size) {
return 0;
}
buff[0] = static_cast<unsigned char>(info_.d1() | (with_hashes * 16));
buff[1] = info_.d2();
int hs = 0;
if (with_hashes) {
hs = (get_level_mask().get_hashes_count()) * (hash_bytes + depth_bytes);
assert(len >= 2 + hs);
std::memset(buff + 2, 0, hs);
auto dest = td::MutableSlice(buff + 2, hs);
auto level = get_level();
// TODO: optimize for prunned brandh
for (unsigned i = 0; i <= level; i++) {
if (!get_level_mask().is_significant(i)) {
continue;
}
dest.copy_from(get_hash(i).as_slice());
dest.remove_prefix(hash_bytes);
}
for (unsigned i = 0; i <= level; i++) {
if (!get_level_mask().is_significant(i)) {
continue;
}
store_depth(dest.ubegin(), get_depth(i));
dest.remove_prefix(depth_bytes);
}
// buff[2] = 0; // for testing hash verification in deserialization
buff += hs;
len -= hs;
}
std::memcpy(buff + 2, get_data(), len - 2);
return len + hs;
}
std::string DataCell::serialize() const {
unsigned char buff[max_serialized_bytes];
int len = serialize(buff, sizeof(buff));
return std::string(buff, buff + len);
}
std::string DataCell::to_hex() const {
unsigned char buff[max_serialized_bytes];
int len = serialize(buff, sizeof(buff));
char hex_buff[max_serialized_bytes * 2 + 1];
for (int i = 0; i < len; i++) {
sprintf(hex_buff + 2 * i, "%02x", buff[i]);
}
return hex_buff;
}
std::ostream& operator<<(std::ostream& os, const DataCell& c) {
return os << c.to_hex();
}
} // namespace vm

212
crypto/vm/cells/DataCell.h Normal file
View file

@ -0,0 +1,212 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
#include "td/utils/Span.h"
#include "td/utils/ThreadSafeCounter.h"
namespace vm {
class DataCell : public Cell {
public:
DataCell(const DataCell& other) = delete;
~DataCell() override;
static void store_depth(td::uint8* dest, td::uint16 depth) {
td::bitstring::bits_store_long(dest, depth, depth_bits);
}
static td::uint16 load_depth(const td::uint8* src) {
return td::bitstring::bits_load_ulong(src, depth_bits) & 0xff;
}
protected:
struct Info {
unsigned bits_;
// d1
unsigned char refs_count_ : 3;
bool is_special_ : 1;
unsigned char level_mask_ : 3;
unsigned char hash_count_ : 3;
unsigned char virtualization_ : 3;
unsigned char d1() const {
return d1(LevelMask{level_mask_});
}
unsigned char d1(LevelMask level_mask) const {
// d1 = refs_count + 8 * is_special + 32 * level
// + 16 * with_hashes - for seriazlization
// d1 = 7 + 16 + 32 * l - for absent cells
return static_cast<unsigned char>(refs_count_ + 8 * is_special_ + 32 * level_mask.get_mask());
}
unsigned char d2() const {
auto res = static_cast<unsigned char>((bits_ / 8) * 2);
if ((bits_ & 7) != 0) {
return static_cast<unsigned char>(res + 1);
}
return res;
}
size_t get_hashes_offset() const {
return 0;
}
size_t get_refs_offset() const {
return get_hashes_offset() + hash_bytes * hash_count_;
}
size_t get_depth_offset() const {
return get_refs_offset() + refs_count_ * sizeof(Cell*);
}
size_t get_data_offset() const {
return get_depth_offset() + sizeof(td::uint16) * hash_count_;
}
size_t get_storage_size() const {
return get_data_offset() + (bits_ + 7) / 8;
}
const Hash* get_hashes(const char* storage) const {
return reinterpret_cast<const Hash*>(storage + get_hashes_offset());
}
Hash* get_hashes(char* storage) const {
return reinterpret_cast<Hash*>(storage + get_hashes_offset());
}
const td::uint16* get_depth(const char* storage) const {
return reinterpret_cast<const td::uint16*>(storage + get_depth_offset());
}
td::uint16* get_depth(char* storage) const {
return reinterpret_cast<td::uint16*>(storage + get_depth_offset());
}
const unsigned char* get_data(const char* storage) const {
return reinterpret_cast<const unsigned char*>(storage + get_data_offset());
}
unsigned char* get_data(char* storage) const {
return reinterpret_cast<unsigned char*>(storage + get_data_offset());
}
Cell* const* get_refs(const char* storage) const {
return reinterpret_cast<Cell* const*>(storage + get_refs_offset());
}
Cell** get_refs(char* storage) const {
return reinterpret_cast<Cell**>(storage + get_refs_offset());
}
};
Info info_;
virtual char* get_storage() = 0;
virtual const char* get_storage() const = 0;
// TODO: we may also save three different pointers
void destroy_storage(char* storage);
explicit DataCell(Info info);
Cell* get_ref_raw_ptr(unsigned idx) const {
DCHECK(idx < get_refs_cnt());
return info_.get_refs(get_storage())[idx];
}
public:
td::Result<LoadedCell> load_cell() const override {
return LoadedCell{Ref<DataCell>{this}, {}, {}};
}
unsigned get_refs_cnt() const {
return info_.refs_count_;
}
unsigned get_bits() const {
return info_.bits_;
}
unsigned size_refs() const {
return info_.refs_count_;
}
unsigned size() const {
return info_.bits_;
}
const unsigned char* get_data() const {
return info_.get_data(get_storage());
}
Ref<Cell> get_ref(unsigned idx) const {
if (idx >= get_refs_cnt()) {
return Ref<Cell>{};
}
return Ref<Cell>(get_ref_raw_ptr(idx));
}
td::uint32 get_virtualization() const override {
return info_.virtualization_;
}
CellUsageTree::NodePtr get_tree_node() const override {
return {};
}
bool is_loaded() const override {
return true;
}
LevelMask get_level_mask() const override {
return LevelMask{info_.level_mask_};
}
bool is_special() const {
return info_.is_special_;
}
SpecialType special_type() const;
int get_serialized_size(bool with_hashes = false) const {
return ((get_bits() + 23) >> 3) +
(with_hashes ? get_level_mask().get_hashes_count() * (hash_bytes + depth_bytes) : 0);
}
int serialize(unsigned char* buff, int buff_size, bool with_hashes = false) const;
std::string serialize() const;
std::string to_hex() const;
static td::int64 get_total_data_cells() {
return get_thread_safe_counter().sum();
}
template <class StorerT>
void store(StorerT& storer) const {
storer.template store_binary<td::uint8>(info_.d1());
storer.template store_binary<td::uint8>(info_.d2());
storer.store_slice(td::Slice(get_data(), (get_bits() + 7) / 8));
}
protected:
static constexpr auto max_storage_size = max_refs * sizeof(void*) + (max_level + 1) * hash_bytes + max_bytes;
private:
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DataCell");
return res;
}
static std::unique_ptr<DataCell> create_empty_data_cell(Info info);
const Hash do_get_hash(td::uint32 level) const override;
td::uint16 do_get_depth(td::uint32 level) const override;
friend class CellBuilder;
static td::Result<Ref<DataCell>> create(td::ConstBitPtr data, unsigned bits, td::Span<Ref<Cell>> refs, bool special);
static td::Result<Ref<DataCell>> create(td::ConstBitPtr data, unsigned bits, td::MutableSpan<Ref<Cell>> refs,
bool special);
};
std::ostream& operator<<(std::ostream& os, const DataCell& c);
} // namespace vm

140
crypto/vm/cells/ExtCell.h Normal file
View file

@ -0,0 +1,140 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
#include "vm/cells/PrunnedCell.h"
#include "common/AtomicRef.h"
#include <mutex>
#include "td/utils/port/thread_local.h"
#include "td/utils/HazardPointers.h"
#include "td/utils/optional.h"
namespace vm {
template <class ExtraT, class Loader>
class ExtCell : public Cell {
private:
struct PrivateTag {};
public:
static td::Result<Ref<ExtCell<ExtraT, Loader>>> create(const PrunnedCellInfo& prunned_cell_info, ExtraT&& extra) {
TRY_RESULT(prunned_cell, PrunnedCell<ExtraT>::create(prunned_cell_info, std::move(extra)));
return Ref<ExtCell<ExtraT, Loader>>(true, std::move(prunned_cell), PrivateTag{});
}
ExtCell(Ref<PrunnedCell<ExtraT>> prunned_cell, PrivateTag) : prunned_cell_(std::move(prunned_cell)) {
get_thread_safe_counter().add(1);
get_thread_safe_counter_unloaded().add(prunned_cell_.load_unsafe().not_null());
}
~ExtCell() {
get_thread_safe_counter().add(-1);
get_thread_safe_counter_unloaded().add(-static_cast<int>(prunned_cell_.load_unsafe().not_null()));
}
LevelMask get_level_mask() const override {
return CellView(this)->get_level_mask();
}
td::Result<LoadedCell> load_cell() const override {
TRY_RESULT(data_cell, load_data_cell());
return LoadedCell{std::move(data_cell), {}, {}};
}
td::uint32 get_virtualization() const override {
return 0;
}
CellUsageTree::NodePtr get_tree_node() const override {
return {};
}
bool is_loaded() const override {
return CellView(this)->is_loaded();
}
private:
mutable td::AtomicRef<DataCell> data_cell_;
mutable td::AtomicRef<PrunnedCell<ExtraT>> prunned_cell_;
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("ExtCell");
return res;
}
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter_unloaded() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("ExtCell.unloaded");
return res;
}
struct CellView {
CellView(const ExtCell<ExtraT, Loader>* cell) {
cell_ = cell->data_cell_.get_unsafe();
if (cell_) {
return;
}
prunned_cell_ = cell->prunned_cell_.load();
if (!prunned_cell_.is_null()) {
cell_ = &*prunned_cell_;
return;
}
cell_ = cell->data_cell_.get_unsafe();
DCHECK(cell_);
}
const Cell* operator->() const {
return cell_;
}
td::Ref<PrunnedCell<ExtraT>> prunned_cell_;
const Cell* cell_;
};
const Hash do_get_hash(td::uint32 level) const override {
return CellView(this)->get_hash(level);
}
td::uint16 do_get_depth(td::uint32 level) const override {
return CellView(this)->get_depth(level);
}
td::Result<Ref<DataCell>> load_data_cell() const {
auto data_cell = data_cell_.get_unsafe();
if (data_cell) {
return Ref<DataCell>(data_cell);
}
auto prunned_cell = prunned_cell_.load();
if (prunned_cell.is_null()) {
data_cell = data_cell_.get_unsafe();
DCHECK(data_cell);
return Ref<DataCell>(data_cell);
}
TRY_RESULT(new_data_cell, Loader::load_data_cell(*this, prunned_cell->get_extra()));
TRY_STATUS(prunned_cell->check_equals_unloaded(new_data_cell));
if (data_cell_.store_if_empty(new_data_cell)) {
prunned_cell_.store({});
get_thread_safe_counter_unloaded().add(-1);
}
return data_cell_.load_unsafe();
}
};
} // namespace vm

View file

@ -0,0 +1,32 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "LevelMask.h"
namespace vm {
namespace detail {
td::StringBuilder& operator<<(td::StringBuilder& sb, LevelMask level_mask) {
sb << "LevelMask{";
for (int i = 0, level = level_mask.get_level(); i < level; i++) {
sb << "01"[(level_mask.get_mask() >> i) % 2];
}
sb << "}";
return sb;
}
} // namespace detail
} // namespace vm

View file

@ -0,0 +1,84 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/bits.h"
#include "td/utils/int_types.h"
#include "td/utils/StringBuilder.h"
namespace td {
class StringBuilder;
}
namespace vm {
namespace detail {
class LevelMask {
public:
explicit LevelMask(td::uint32 new_mask = 0) : mask_(new_mask) {
}
td::uint32 get_mask() const {
return mask_;
}
td::uint32 get_level() const {
return 32 - td::count_leading_zeroes32(mask_);
}
td::uint32 get_hash_i() const {
return td::count_bits32(mask_);
}
td::uint32 get_hashes_count() const {
return get_hash_i() + 1;
}
LevelMask apply(td::uint32 level) const {
DCHECK(level < 32);
return LevelMask{mask_ & ((1u << level) - 1)};
}
LevelMask apply_or(LevelMask other) const {
return LevelMask{mask_ | other.mask_};
}
LevelMask shift_right() const {
return LevelMask{mask_ >> 1};
}
bool is_significant(td::uint32 level) const {
DCHECK(level < 32);
bool res = level == 0 || ((mask_ >> (level - 1)) % 2 != 0);
CHECK(res == (apply(level).get_level() == level));
return res;
}
bool operator==(const LevelMask& other) const {
return mask_ == other.mask_;
}
bool operator!=(const LevelMask& other) const {
return !(*this == other);
}
static LevelMask one_level(td::uint32 level) {
DCHECK(level < 32);
if (level == 0) {
return LevelMask(0);
}
return LevelMask(1 << (level - 1));
}
private:
td::uint32 mask_;
};
td::StringBuilder& operator<<(td::StringBuilder& sb, LevelMask level_mask);
} // namespace detail
} // namespace vm

View file

@ -0,0 +1,263 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/MerkleProof.h"
#include "vm/cells/CellBuilder.h"
#include "vm/cells/CellSlice.h"
#include "td/utils/HashMap.h"
#include "td/utils/HashSet.h"
namespace vm {
namespace detail {
class MerkleProofImpl {
public:
explicit MerkleProofImpl(MerkleProof::IsPrunnedFunction is_prunned) : is_prunned_(std::move(is_prunned)) {
}
explicit MerkleProofImpl(CellUsageTree *usage_tree) : usage_tree_(usage_tree) {
}
Ref<Cell> create_from(Ref<Cell> cell) {
if (!is_prunned_) {
CHECK(usage_tree_);
dfs_usage_tree(cell, usage_tree_->root_id());
is_prunned_ = [this](const Ref<Cell> &cell) { return visited_cells_.count(cell->get_hash()) == 0; };
}
return dfs(cell, cell->get_level());
}
private:
using Key = std::pair<Cell::Hash, int>;
td::HashMap<Key, Ref<Cell>> cells_;
td::HashSet<Cell::Hash> visited_cells_;
CellUsageTree *usage_tree_{nullptr};
MerkleProof::IsPrunnedFunction is_prunned_;
void dfs_usage_tree(Ref<Cell> cell, CellUsageTree::NodeId node_id) {
if (!usage_tree_->is_loaded(node_id)) {
return;
}
visited_cells_.insert(cell->get_hash());
CellSlice cs(NoVm(), cell);
for (unsigned i = 0; i < cs.size_refs(); i++) {
dfs_usage_tree(cs.prefetch_ref(i), usage_tree_->get_child(node_id, i));
}
}
Ref<Cell> dfs(Ref<Cell> cell, int merkle_depth) {
CHECK(cell.not_null());
Key key{cell->get_hash(), merkle_depth};
{
auto it = cells_.find(key);
if (it != cells_.end()) {
CHECK(it->second.not_null());
return it->second;
}
}
if (is_prunned_(cell)) {
auto res = CellBuilder::create_pruned_branch(cell, merkle_depth + 1);
CHECK(res.not_null());
cells_.emplace(key, res);
return res;
}
CellSlice cs(NoVm(), cell);
int children_merkle_depth = cs.child_merkle_depth(merkle_depth);
CellBuilder cb;
cb.store_bits(cs.fetch_bits(cs.size()));
for (unsigned i = 0; i < cs.size_refs(); i++) {
cb.store_ref(dfs(cs.prefetch_ref(i), children_merkle_depth));
}
auto res = cb.finalize(cs.is_special());
CHECK(res.not_null());
cells_.emplace(key, res);
return res;
}
};
} // namespace detail
Ref<Cell> MerkleProof::generate_raw(Ref<Cell> cell, IsPrunnedFunction is_prunned) {
return detail::MerkleProofImpl(is_prunned).create_from(cell);
}
Ref<Cell> MerkleProof::generate_raw(Ref<Cell> cell, CellUsageTree *usage_tree) {
return detail::MerkleProofImpl(usage_tree).create_from(cell);
}
Ref<Cell> MerkleProof::virtualize_raw(Ref<Cell> cell, Cell::VirtualizationParameters virt) {
return cell->virtualize(virt);
}
Ref<Cell> MerkleProof::generate(Ref<Cell> cell, IsPrunnedFunction is_prunned) {
int cell_level = cell->get_level();
if (cell_level != 0) {
return {};
}
auto raw = generate_raw(std::move(cell), is_prunned);
return CellBuilder::create_merkle_proof(std::move(raw));
}
Ref<Cell> MerkleProof::generate(Ref<Cell> cell, CellUsageTree *usage_tree) {
int cell_level = cell->get_level();
if (cell_level != 0) {
return {};
}
auto raw = generate_raw(std::move(cell), usage_tree);
return CellBuilder::create_merkle_proof(std::move(raw));
}
td::Result<Ref<Cell>> unpack_proof(Ref<Cell> cell) {
CHECK(cell.not_null());
td::uint8 level = static_cast<td::uint8>(cell->get_level());
if (level != 0) {
return td::Status::Error("Level of MerkleProof must be zero");
}
CellSlice cs(NoVm(), std::move(cell));
if (cs.special_type() != Cell::SpecialType::MerkleProof) {
return td::Status::Error("Not a MekleProof cell");
}
return cs.fetch_ref();
}
Ref<Cell> MerkleProof::virtualize(Ref<Cell> cell, int virtualization) {
auto r_raw = unpack_proof(std::move(cell));
if (r_raw.is_error()) {
return {};
}
return virtualize_raw(r_raw.move_as_ok(), {0 /*level*/, static_cast<td::uint8>(virtualization)});
}
class MerkleProofCombine {
public:
MerkleProofCombine(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
}
td::Result<Ref<Cell>> run() {
TRY_RESULT(a, unpack_proof(a_));
TRY_RESULT(b, unpack_proof(b_));
if (a->get_hash(0) != b->get_hash(0)) {
return td::Status::Error("Can't combine MerkleProofs with different roots");
}
dfs(a, 0);
dfs(b, 0);
return CellBuilder::create_merkle_proof(create_A(a, 0, 0));
}
private:
Ref<Cell> a_;
Ref<Cell> b_;
struct Info {
Ref<Cell> cell_;
Ref<Cell> prunned_cells_[Cell::max_level]; // Cache prunned cells with different levels to reuse them
Ref<Cell> get_prunned_cell(int depth) {
if (depth < Cell::max_level) {
return prunned_cells_[depth];
}
return {};
}
Ref<Cell> get_any_cell() const {
if (cell_.not_null()) {
return cell_;
}
for (auto &cell : prunned_cells_) {
if (cell.not_null()) {
return cell;
}
}
UNREACHABLE();
}
};
using Key = std::pair<Cell::Hash, int>;
td::HashMap<Cell::Hash, Info> cells_;
td::HashMap<Key, Ref<Cell>> create_A_res_;
td::HashSet<Key> visited_;
void dfs(Ref<Cell> cell, int merkle_depth) {
if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
return;
}
auto &info = cells_[cell->get_hash(merkle_depth)];
CellSlice cs(NoVm(), cell);
// check if prunned cell is bounded
if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast<int>(cell->get_level()) > merkle_depth) {
info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
return;
}
info.cell_ = std::move(cell);
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
dfs(cs.fetch_ref(), child_merkle_depth);
}
}
Ref<Cell> create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
auto it = create_A_res_.find(key);
if (it != create_A_res_.end()) {
return it->second;
}
auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
create_A_res_.emplace(key, res);
return res;
}
Ref<Cell> do_create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
auto &info = cells_[cell->get_hash(merkle_depth)];
if (info.cell_.is_null()) {
Ref<Cell> res = info.get_prunned_cell(a_merkle_depth);
if (res.is_null()) {
res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
}
return res;
}
CHECK(info.cell_.not_null());
CellSlice cs(NoVm(), info.cell_);
//CHECK(cs.size_refs() != 0);
if (cs.size_refs() == 0) {
return info.cell_;
}
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
CellBuilder cb;
cb.store_bits(cs.fetch_bits(cs.size()));
for (unsigned i = 0; i < cs.size_refs(); i++) {
cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
}
return cb.finalize(cs.is_special());
}
};
Ref<Cell> MerkleProof::combine(Ref<Cell> a, Ref<Cell> b) {
auto res = MerkleProofCombine(std::move(a), std::move(b)).run();
if (res.is_error()) {
return {};
}
return res.move_as_ok();
}
} // namespace vm

View file

@ -0,0 +1,46 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
#include <utility>
#include <functional>
namespace vm {
class MerkleProof {
public:
using IsPrunnedFunction = std::function<bool(const Ref<Cell> &)>;
// works with proofs wrapped in MerkleProof special cell
// cells must have zero level
static Ref<Cell> generate(Ref<Cell> cell, IsPrunnedFunction is_prunned);
static Ref<Cell> generate(Ref<Cell> cell, CellUsageTree *usage_tree);
// cell must have zero level and must be a MerkleProof
static Ref<Cell> virtualize(Ref<Cell> cell, int virtualization);
static Ref<Cell> combine(Ref<Cell> a, Ref<Cell> b);
// works with upwrapped proofs
// works fine with cell of non-zero level, but this is not supported (yet?) in MerkeProof special cell
static Ref<Cell> generate_raw(Ref<Cell> cell, IsPrunnedFunction is_prunned);
static Ref<Cell> generate_raw(Ref<Cell> cell, CellUsageTree *usage_tree);
static Ref<Cell> virtualize_raw(Ref<Cell> cell, Cell::VirtualizationParameters virt);
};
} // namespace vm

View file

@ -0,0 +1,510 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/MerkleUpdate.h"
#include "vm/cells/MerkleProof.h"
#include "td/utils/HashMap.h"
#include "td/utils/HashSet.h"
namespace vm {
namespace detail {
class MerkleUpdateApply {
public:
Ref<Cell> apply(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
td::uint32 to_level) {
if (from_level != from->get_level()) {
return {};
}
dfs_both(from, update_from, from_level);
return dfs(update_to, to_level);
}
private:
using Key = std::pair<Cell::Hash, int>;
td::HashMap<Cell::Hash, Ref<Cell>> known_cells_;
td::HashMap<Key, Ref<Cell>> ready_cells_;
void dfs_both(Ref<Cell> original, Ref<Cell> update_from, int merkle_depth) {
CellSlice cs_update_from(NoVm(), update_from);
known_cells_.emplace(original->get_hash(merkle_depth), original);
if (cs_update_from.special_type() == Cell::SpecialType::PrunnedBranch) {
return;
}
int child_merkle_depth = cs_update_from.child_merkle_depth(merkle_depth);
CellSlice cs_original(NoVm(), original);
for (unsigned i = 0; i < cs_original.size_refs(); i++) {
dfs_both(cs_original.prefetch_ref(i), cs_update_from.prefetch_ref(i), child_merkle_depth);
}
}
Ref<Cell> dfs(Ref<Cell> cell, int merkle_depth) {
CellSlice cs(NoVm(), cell);
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
if ((int)cell->get_level() == merkle_depth + 1) {
auto it = known_cells_.find(cell->get_hash(merkle_depth));
if (it != known_cells_.end()) {
return it->second;
}
return {};
}
return cell;
}
Key key{cell->get_hash(), merkle_depth};
{
auto it = ready_cells_.find(key);
if (it != ready_cells_.end()) {
return it->second;
}
}
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
CellBuilder cb;
cb.store_bits(cs.fetch_bits(cs.size()));
for (unsigned i = 0; i < cs.size_refs(); i++) {
auto ref = dfs(cs.prefetch_ref(i), child_merkle_depth);
if (ref.is_null()) {
return {};
}
cb.store_ref(std::move(ref));
}
auto res = cb.finalize(cs.is_special());
ready_cells_.emplace(key, res);
return res;
}
};
class MerkleUpdateValidator {
public:
td::Status validate(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level, td::uint32 to_level) {
dfs_from(update_from, from_level);
return dfs_to(update_to, to_level);
}
private:
td::HashSet<Cell::Hash> known_cells_;
using Key = std::pair<Cell::Hash, int>;
td::HashSet<Key> visited_from_;
td::HashSet<Key> visited_to_;
void dfs_from(Ref<Cell> cell, int merkle_depth) {
if (!visited_from_.emplace(cell->get_hash(), merkle_depth).second) {
return;
}
CellSlice cs(NoVm(), cell);
known_cells_.insert(cell->get_hash(merkle_depth));
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
return;
}
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
for (unsigned i = 0; i < cs.size_refs(); i++) {
dfs_from(cs.prefetch_ref(i), child_merkle_depth);
}
}
td::Status dfs_to(Ref<Cell> cell, int merkle_depth) {
if (!visited_to_.emplace(cell->get_hash(), merkle_depth).second) {
return td::Status::OK();
}
CellSlice cs(NoVm(), cell);
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
if ((int)cell->get_level() == merkle_depth + 1) {
if (known_cells_.count(cell->get_hash(merkle_depth)) == 0) {
return td::Status::Error(PSLICE()
<< "Unknown prunned cell (validate): " << cell->get_hash(merkle_depth).to_hex());
}
}
return td::Status::OK();
}
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
for (unsigned i = 0; i < cs.size_refs(); i++) {
TRY_STATUS(dfs_to(cs.prefetch_ref(i), child_merkle_depth));
}
return td::Status::OK();
}
};
} // namespace detail
td::Status MerkleUpdate::may_apply(Ref<Cell> from, Ref<Cell> update) {
if (update->get_level() != 0 || from->get_level() != 0) {
return td::Status::Error("Level of update of from is not zero");
}
CellSlice cs(NoVm(), std::move(update));
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
return td::Status::Error("Update cell is not a MerkeUpdate");
}
auto update_from = cs.fetch_ref();
if (from->get_hash(0) != update_from->get_hash(0)) {
return td::Status::Error("Hash mismatch");
}
return td::Status::OK();
}
Ref<Cell> MerkleUpdate::apply(Ref<Cell> from, Ref<Cell> update) {
if (update->get_level() != 0 || from->get_level() != 0) {
return {};
}
CellSlice cs(NoVm(), std::move(update));
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
return {};
}
auto update_from = cs.fetch_ref();
auto update_to = cs.fetch_ref();
return apply_raw(std::move(from), std::move(update_from), std::move(update_to), 0, 0);
}
Ref<Cell> MerkleUpdate::apply_raw(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
td::uint32 to_level) {
if (from->get_hash(from_level) != update_from->get_hash(from_level)) {
LOG(DEBUG) << "invalid Merkle update: expected old value hash = " << update_from->get_hash(from_level).to_hex()
<< ", applied to value with hash = " << from->get_hash(from_level).to_hex();
return {};
}
return detail::MerkleUpdateApply().apply(from, std::move(update_from), std::move(update_to), from_level, to_level);
}
std::pair<Ref<Cell>, Ref<Cell>> MerkleUpdate::generate_raw(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
// create Merkle update cell->new_cell
auto update_to = MerkleProof::generate_raw(to, [tree = usage_tree](const Ref<Cell> &cell) {
auto loaded_cell = cell->load_cell().move_as_ok(); // FIXME
if (loaded_cell.data_cell->size_refs() == 0) {
return false;
}
return !loaded_cell.tree_node.empty() && loaded_cell.tree_node.mark_path(tree);
});
usage_tree->set_use_mark_for_is_loaded(true);
auto update_from = MerkleProof::generate_raw(from, usage_tree);
return {std::move(update_from), std::move(update_to)};
}
td::Status MerkleUpdate::validate_raw(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
td::uint32 to_level) {
return detail::MerkleUpdateValidator().validate(std::move(update_from), std::move(update_to), from_level, to_level);
}
td::Status MerkleUpdate::validate(Ref<Cell> update) {
if (update->get_level() != 0) {
return td::Status::Error("nonzero level");
}
CellSlice cs(NoVm(), std::move(update));
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
return td::Status::Error("not a MerkleUpdate cell");
}
auto update_from = cs.fetch_ref();
auto update_to = cs.fetch_ref();
return validate_raw(std::move(update_from), std::move(update_to), 0, 0);
}
Ref<Cell> MerkleUpdate::generate(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
auto from_level = from->get_level();
auto to_level = to->get_level();
if (from_level != 0 || to_level != 0) {
return {};
}
auto res = generate_raw(std::move(from), std::move(to), usage_tree);
return CellBuilder::create_merkle_update(res.first, res.second);
}
namespace detail {
class MerkleCombine {
public:
MerkleCombine(Ref<Cell> AB, Ref<Cell> CD) : AB_(std::move(AB)), CD_(std::move(CD)) {
}
td::Result<Ref<Cell>> run() {
TRY_RESULT(AB, unpack_update(std::move(AB_)));
TRY_RESULT(CD, unpack_update(std::move(CD_)));
std::tie(A_, B_) = AB;
std::tie(C_, D_) = CD;
if (B_->get_hash(0) != C_->get_hash(0)) {
return td::Status::Error("Impossible to combine merkle updates");
}
auto log = [](td::Slice name, auto cell) {
CellSlice cs(NoVm(), cell);
LOG(ERROR) << name << " " << cell->get_level();
cs.print_rec(std::cerr);
};
if (0) {
log("A", A_);
log("B", B_);
log("C", C_);
log("D", D_);
}
// We have four bags of cells. A, B, C and D.
// X = Virtualize(A), A is subtree (merkle proof) of X
// Y = Virtualize(B) = Virtualize(C), B and C are subrees of Y
// Z = Virtualize(D), D is subtree of Z
//
// Prunned cells bounded by merkle proof P are essentially cells which are impossible to load during traversal of Virtualize(P)
//
// We want to create new_A and new_D
// Virtualize(new_A) = X
// Virtualize(new_D) = Z
// All prunned branches bounded by new_D must be in new_A
// i.e. if we have all cells reachable in Virtualize(new_A) we may construct Z from them (and from new_D)
//
// Main idea is following
// 1. Create maximum subtrees of X and Z with all cells in A, B, C and D
// Max(V) - such maximum subtree
//
// 2. Max(A) and Max(D) should be merkle update already. But win want to minimize it
// So we cut all branches of Max(D) which are in maxA
// When we cut branch q in Max(D) we mark some path to q in Max(A)
// Then we cut all branches of Max(A) which are not marked.
//
// How to create Max(A)?
// We just store all cells reachable from A, B, C and D in big cache.
// It we reach bounded prunned cell during traversion we may continue traversial with a cell from the cache.
//
//
// 1. load_cells(root) - caches all cell reachable in Virtualize(root);
visited_.clear();
load_cells(A_, 0);
visited_.clear();
load_cells(B_, 0);
visited_.clear();
load_cells(C_, 0);
visited_.clear();
load_cells(D_, 0);
// 2. mark_A(A) - Traverse Max(A), but uses all cached cells from step 1. Mark all visited cells
A_usage_tree_ = std::make_shared<CellUsageTree>();
mark_A(A_, 0, A_usage_tree_->root_id());
// 3. create_D(D) - create new_D. Traverse Max(D), and stop at marked cells. Mark path in A to marked cells
auto new_D = create_D(D_, 0, 0);
if (new_D.is_null()) {
return td::Status::Error("Failed to combine updates. One of them is probably an invalid update");
}
// 4. create_A(A) - create new_A. Traverse Max(A), and stop at cells not marked at step 3.
auto new_A = create_A(A_, 0, 0);
if (0) {
log("NewD", new_D);
}
return CellBuilder::create_merkle_update(new_A, new_D);
}
private:
Ref<Cell> AB_, CD_;
Ref<Cell> A_, B_, C_, D_;
std::shared_ptr<CellUsageTree> A_usage_tree_;
struct Info {
Ref<Cell> cell_;
Ref<Cell> prunned_cells_[Cell::max_level]; // Cache prunned cells with different levels to reuse them
CellUsageTree::NodeId A_node_id{0};
Ref<Cell> get_prunned_cell(int depth) {
if (depth < Cell::max_level) {
return prunned_cells_[depth];
}
return {};
}
Ref<Cell> get_any_cell() const {
if (cell_.not_null()) {
return cell_;
}
for (auto &cell : prunned_cells_) {
if (cell.not_null()) {
return cell;
}
}
UNREACHABLE();
}
};
using Key = std::pair<Cell::Hash, int>;
td::HashMap<Cell::Hash, Info> cells_;
td::HashMap<Key, Ref<Cell>> create_A_res_;
td::HashMap<Key, Ref<Cell>> create_D_res_;
td::HashSet<Key> visited_;
void load_cells(Ref<Cell> cell, int merkle_depth) {
if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
return;
}
auto &info = cells_[cell->get_hash(merkle_depth)];
CellSlice cs(NoVm(), cell);
// check if prunned cell is bounded
if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast<int>(cell->get_level()) > merkle_depth) {
info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
return;
}
info.cell_ = std::move(cell);
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
load_cells(cs.fetch_ref(), child_merkle_depth);
}
}
void mark_A(Ref<Cell> cell, int merkle_depth, CellUsageTree::NodeId node_id) {
CHECK(node_id != 0);
// cell in cache may be virtualized with different level
// so we make merkle_depth as small as possible
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
auto &info = cells_[cell->get_hash(merkle_depth)];
if (info.A_node_id != 0) {
return;
}
info.A_node_id = node_id;
if (info.cell_.is_null()) {
return;
}
CellSlice cs(NoVm(), info.cell_);
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
for (int i = 0, size = cs.size_refs(); i < size; i++) {
mark_A(cs.fetch_ref(), child_merkle_depth, A_usage_tree_->create_child(node_id, i));
}
}
Ref<Cell> create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
auto key = Key(cell->get_hash(merkle_depth), d_merkle_depth);
auto it = create_D_res_.find(key);
if (it != create_D_res_.end()) {
return it->second;
}
auto res = do_create_D(std::move(cell), merkle_depth, d_merkle_depth);
if (res.is_null()) {
return {};
}
create_D_res_.emplace(key, res);
return res;
}
Ref<Cell> do_create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
auto &info = cells_[cell->get_hash(merkle_depth)];
if (info.A_node_id != 0) {
A_usage_tree_->mark_path(info.A_node_id);
Ref<Cell> res = info.get_prunned_cell(d_merkle_depth);
if (res.is_null()) {
res = CellBuilder::create_pruned_branch(info.get_any_cell(), d_merkle_depth + 1, merkle_depth);
}
return res;
}
if (info.cell_.is_null()) {
return {};
}
CellSlice cs(NoVm(), info.cell_);
if (cs.size_refs() == 0) {
return info.cell_;
}
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
auto child_d_merkle_depth = cs.child_merkle_depth(d_merkle_depth);
CellBuilder cb;
cb.store_bits(cs.fetch_bits(cs.size()));
for (unsigned i = 0; i < cs.size_refs(); i++) {
auto ref = create_D(cs.prefetch_ref(i), child_merkle_depth, child_d_merkle_depth);
if (ref.is_null()) {
return {};
}
cb.store_ref(std::move(ref));
}
return cb.finalize(cs.is_special());
}
Ref<Cell> create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
auto it = create_A_res_.find(key);
if (it != create_A_res_.end()) {
return it->second;
}
auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
create_A_res_.emplace(key, res);
return res;
}
Ref<Cell> do_create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
auto &info = cells_[cell->get_hash(merkle_depth)];
CHECK(info.A_node_id != 0);
if (!A_usage_tree_->has_mark(info.A_node_id)) {
Ref<Cell> res = info.get_prunned_cell(a_merkle_depth);
if (res.is_null()) {
res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
}
return res;
}
CHECK(info.cell_.not_null());
CellSlice cs(NoVm(), info.cell_);
CHECK(cs.size_refs() != 0);
if (cs.size_refs() == 0) {
return info.cell_;
}
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
CellBuilder cb;
cb.store_bits(cs.fetch_bits(cs.size()));
for (unsigned i = 0; i < cs.size_refs(); i++) {
cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
}
return cb.finalize(cs.is_special());
}
td::Result<std::pair<Ref<Cell>, Ref<Cell>>> unpack_update(Ref<Cell> update) const {
if (update->get_level() != 0) {
return td::Status::Error("level is not zero");
}
CellSlice cs(NoVm(), std::move(update));
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
return td::Status::Error("Not a Merkle Update cell");
}
auto update_from = cs.fetch_ref();
auto update_to = cs.fetch_ref();
return std::make_pair(std::move(update_from), std::move(update_to));
}
};
} // namespace detail
Ref<Cell> MerkleUpdate::combine(Ref<Cell> ab, Ref<Cell> bc) {
detail::MerkleCombine combine(ab, bc);
auto res = combine.run();
if (res.is_error()) {
return {};
}
return res.move_as_ok();
}
} // namespace vm

View file

@ -0,0 +1,50 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
#include "vm/cells/CellSlice.h"
#include "vm/cells/CellBuilder.h"
#include "td/utils/Status.h"
#include <utility>
namespace vm {
class MerkleUpdate {
public:
// from + update == to
static Ref<Cell> generate(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree);
// Returns empty Ref<Cell> if something go wrong. If validate(from).is_ok() and may_apply(from, to).is_ok(), then it
// must not fail.
static Ref<Cell> apply(Ref<Cell> from, Ref<Cell> update);
// check if update is valid
static TD_WARN_UNUSED_RESULT td::Status validate(Ref<Cell> update);
// check that hash in from is same as hash stored in update. Do not validate update
static TD_WARN_UNUSED_RESULT td::Status may_apply(Ref<Cell> from, Ref<Cell> update);
static Ref<Cell> apply_raw(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
td::uint32 to_level);
static std::pair<Ref<Cell>, Ref<Cell>> generate_raw(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree);
static td::Status validate_raw(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
td::uint32 to_level);
static Ref<Cell> combine(Ref<Cell> ab, Ref<Cell> bc);
};
} // namespace vm

View file

@ -0,0 +1,139 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/CellWithStorage.h"
#include "vm/cells/Cell.h"
namespace vm {
struct PrunnedCellInfo {
Cell::LevelMask level_mask;
td::Slice hash;
td::Slice depth;
};
template <class ExtraT>
class PrunnedCell : public Cell {
public:
const ExtraT& get_extra() const {
return extra_;
}
static td::Result<Ref<PrunnedCell<ExtraT>>> create(const PrunnedCellInfo& prunned_cell_info, ExtraT&& extra) {
auto level_mask = prunned_cell_info.level_mask;
if (level_mask.get_level() > max_level) {
return td::Status::Error("Level is too big");
}
Info info(level_mask);
auto prunned_cell =
detail::CellWithUniquePtrStorage<PrunnedCell<ExtraT>>::create(info.get_storage_size(), info, std::move(extra));
TRY_STATUS(prunned_cell->init(prunned_cell_info));
return Ref<PrunnedCell<ExtraT>>(prunned_cell.release(), typename Ref<PrunnedCell<ExtraT>>::acquire_t{});
}
LevelMask get_level_mask() const override {
return LevelMask(info_.level_mask_);
}
protected:
struct Info {
Info(LevelMask level_mask) {
level_mask_ = level_mask.get_mask() & 7;
hash_count_ = level_mask.get_hashes_count() & 7;
}
unsigned char level_mask_ : 3;
unsigned char hash_count_ : 3;
size_t get_hashes_offset() const {
return 0;
}
size_t get_depth_offset() const {
return get_hashes_offset() + hash_bytes * hash_count_;
}
size_t get_storage_size() const {
return get_depth_offset() + sizeof(td::uint16) * hash_count_;
}
const Hash* get_hashes(const char* storage) const {
return reinterpret_cast<const Hash*>(storage + get_hashes_offset());
}
Hash* get_hashes(char* storage) const {
return reinterpret_cast<Hash*>(storage + get_hashes_offset());
}
const td::uint16* get_depth(const char* storage) const {
return reinterpret_cast<const td::uint16*>(storage + get_depth_offset());
}
td::uint16* get_depth(char* storage) const {
return reinterpret_cast<td::uint16*>(storage + get_depth_offset());
}
};
Info info_;
ExtraT extra_;
virtual char* get_storage() = 0;
virtual const char* get_storage() const = 0;
void destroy_storage(char* storage) {
// noop
}
td::Status init(const PrunnedCellInfo& prunned_cell_info) {
auto storage = get_storage();
auto& new_hash = prunned_cell_info.hash;
auto* hash = info_.get_hashes(storage);
size_t n = prunned_cell_info.level_mask.get_hashes_count();
CHECK(new_hash.size() == n * hash_bytes);
for (td::uint32 i = 0; i < n; i++) {
hash[i].as_slice().copy_from(new_hash.substr(i * Cell::hash_bytes, Cell::hash_bytes));
}
auto& new_depth = prunned_cell_info.depth;
CHECK(new_depth.size() == n * depth_bytes);
auto* depth = info_.get_depth(storage);
for (td::uint32 i = 0; i < n; i++) {
depth[i] = DataCell::load_depth(new_depth.substr(i * Cell::depth_bytes, Cell::depth_bytes).ubegin());
if (depth[i] > max_depth) {
return td::Status::Error("Depth is too big");
}
}
return td::Status::OK();
}
explicit PrunnedCell(Info info, ExtraT&& extra) : info_(info), extra_(std::move(extra)) {
}
td::uint32 get_virtualization() const override {
return 0;
}
CellUsageTree::NodePtr get_tree_node() const override {
return {};
}
bool is_loaded() const override {
return false;
}
private:
const Hash do_get_hash(td::uint32 level) const override {
return info_.get_hashes(get_storage())[get_level_mask().apply(level).get_hash_i()];
}
td::uint16 do_get_depth(td::uint32 level) const override {
return info_.get_depth(get_storage())[get_level_mask().apply(level).get_hash_i()];
}
td::Result<LoadedCell> load_cell() const override {
return td::Status::Error("Can't load prunned branch");
}
};
} // namespace vm

View file

@ -0,0 +1,88 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
#include "vm/cells/CellUsageTree.h"
namespace vm {
class UsageCell : public Cell {
private:
struct PrivateTag {};
public:
UsageCell(Ref<Cell> cell, CellUsageTree::NodePtr tree_node, PrivateTag)
: cell_(std::move(cell)), tree_node_(std::move(tree_node)) {
}
static Ref<Cell> create(Ref<Cell> cell, CellUsageTree::NodePtr tree_node) {
if (tree_node.empty()) {
return cell;
}
return Ref<UsageCell>{true, std::move(cell), std::move(tree_node), PrivateTag{}};
}
// load interface
td::Result<LoadedCell> load_cell() const override {
TRY_RESULT(loaded_cell, cell_->load_cell());
if (tree_node_.on_load()) {
CHECK(loaded_cell.tree_node.empty());
loaded_cell.tree_node = tree_node_;
}
return std::move(loaded_cell);
}
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
auto virtualized_cell = cell_->virtualize(virt);
if (tree_node_.empty()) {
return virtualized_cell;
}
if (virtualized_cell.get() == cell_.get()) {
return Ref<Cell>(this);
}
return create(std::move(virtualized_cell), tree_node_);
}
td::uint32 get_virtualization() const override {
return cell_->get_virtualization();
}
CellUsageTree::NodePtr get_tree_node() const override {
return tree_node_;
}
bool is_loaded() const override {
return cell_->is_loaded();
}
// hash and level
LevelMask get_level_mask() const override {
return cell_->get_level_mask();
}
protected:
const Hash do_get_hash(td::uint32 level) const override {
return cell_->get_hash(level);
}
td::uint16 do_get_depth(td::uint32 level) const override {
return cell_->get_depth(level);
}
private:
Ref<Cell> cell_;
CellUsageTree::NodePtr tree_node_;
};
} // namespace vm

View file

@ -0,0 +1,87 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells/Cell.h"
namespace vm {
class VirtualCell : public Cell {
private:
struct PrivateTag {};
public:
static Ref<Cell> create(VirtualizationParameters virt, Ref<Cell> cell) {
if (cell->get_level() <= virt.get_level()) {
return cell;
}
return Ref<VirtualCell>{true, virt, std::move(cell), PrivateTag{}};
}
VirtualCell(VirtualizationParameters virt, Ref<Cell> cell, PrivateTag) : virt_(virt), cell_(std::move(cell)) {
CHECK(cell_->get_virtualization() <= virt_.get_virtualization());
}
// load interface
td::Result<LoadedCell> load_cell() const override {
TRY_RESULT(loaded_cell, cell_->load_cell());
loaded_cell.virt = loaded_cell.virt.apply(virt_);
return std::move(loaded_cell);
}
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
auto new_virt = virt_.apply(virt);
if (new_virt == virt_) {
return Ref<Cell>(this);
}
return create(new_virt, cell_);
}
td::uint32 get_virtualization() const override {
return virt_.get_virtualization();
}
CellUsageTree::NodePtr get_tree_node() const override {
return cell_->get_tree_node();
}
bool is_loaded() const override {
return cell_->is_loaded();
}
// hash and level
LevelMask get_level_mask() const override {
return cell_->get_level_mask().apply(virt_.get_level());
}
protected:
const Hash do_get_hash(td::uint32 level) const override {
return cell_->get_hash(fix_level(level));
}
td::uint16 do_get_depth(td::uint32 level) const override {
return cell_->get_depth(fix_level(level));
}
private:
VirtualizationParameters virt_;
Ref<Cell> cell_;
int fix_level(int level) const {
return get_level_mask().apply(level).get_level();
}
};
} // namespace vm

View file

@ -0,0 +1,72 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/int_types.h"
#include "td/utils/logging.h"
#include <limits>
namespace vm {
namespace detail {
class VirtualizationParameters {
public:
static constexpr td::uint8 max_level() {
return std::numeric_limits<td::uint8>::max();
}
VirtualizationParameters() = default;
VirtualizationParameters(td::uint8 level, td::uint8 virtualization) : level_(level), virtualization_(virtualization) {
CHECK(virtualization_ != 0 || empty());
}
bool empty() const {
return level_ == max_level() && virtualization_ == 0;
}
VirtualizationParameters apply(VirtualizationParameters outer) const {
if (outer.level_ >= level_) {
return *this;
}
CHECK(virtualization_ <= outer.virtualization_);
return {outer.level_, outer.virtualization_};
}
td::uint8 get_level() const {
return level_;
}
td::uint8 get_virtualization() const {
return virtualization_;
}
bool operator==(const VirtualizationParameters &other) const {
return level_ == other.level_ && virtualization_ == other.virtualization_;
}
private:
td::uint8 level_ = max_level();
td::uint8 virtualization_ = 0;
};
inline td::StringBuilder &operator<<(td::StringBuilder &sb, const VirtualizationParameters &virt) {
return sb << "{level: " << virt.get_level() << ", virtualization: " << virt.get_virtualization() << "}";
}
} // namespace detail
} // namespace vm

19
crypto/vm/cellslice.h Normal file
View file

@ -0,0 +1,19 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/CellSlice.h"

877
crypto/vm/continuation.cpp Normal file
View file

@ -0,0 +1,877 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/dispatch.h"
#include "vm/continuation.h"
#include "vm/dict.h"
#include "vm/log.h"
namespace vm {
int Continuation::jump_w(VmState* st) & {
return static_cast<const Continuation*>(this)->jump(st);
}
bool Continuation::has_c0() const {
const ControlData* cont_data = get_cdata();
return cont_data && cont_data->save.c[0].not_null();
}
StackEntry ControlRegs::get(unsigned idx) const {
if (idx < creg_num) {
return get_c(idx);
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
return get_d(idx);
} else if (idx == 7) {
return c7;
} else {
return {};
}
}
bool ControlRegs::set(unsigned idx, StackEntry value) {
if (idx < creg_num) {
auto v = std::move(value).as_cont();
return v.not_null() && set_c(idx, std::move(v));
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
auto v = std::move(value).as_cell();
return v.not_null() && set_d(idx, std::move(v));
} else if (idx == 7) {
auto v = std::move(value).as_tuple();
return v.not_null() && set_c7(std::move(v));
} else {
return false;
}
}
bool ControlRegs::define(unsigned idx, StackEntry value) {
if (idx < creg_num) {
auto v = std::move(value).as_cont();
return v.not_null() && define_c(idx, std::move(v));
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
auto v = std::move(value).as_cell();
return v.not_null() && define_d(idx, std::move(v));
} else if (idx == 7) {
auto v = std::move(value).as_tuple();
return v.not_null() && define_c7(std::move(v));
} else {
return false;
}
}
ControlRegs& ControlRegs::operator^=(const ControlRegs& save) {
for (int i = 0; i < creg_num; i++) {
c[i] ^= save.c[i];
}
for (int i = 0; i < dreg_num; i++) {
d[i] ^= save.d[i];
}
c7 ^= save.c7;
return *this;
}
ControlRegs& ControlRegs::operator^=(ControlRegs&& save) {
for (int i = 0; i < creg_num; i++) {
c[i] ^= std::move(save.c[i]);
}
for (int i = 0; i < dreg_num; i++) {
d[i] ^= std::move(save.d[i]);
}
c7 ^= std::move(save.c7);
return *this;
}
ControlRegs& ControlRegs::operator&=(const ControlRegs& save) {
for (int i = 0; i < creg_num; i++) {
c[i] &= save.c[i].is_null();
}
for (int i = 0; i < dreg_num; i++) {
d[i] &= save.d[i].is_null();
}
c7 &= save.c7.is_null();
return *this;
}
int ExcQuitCont::jump(VmState* st) const & {
int n = 0;
try {
n = st->get_stack().pop_smallint_range(0xffff);
} catch (const VmError& vme) {
n = vme.get_errno();
}
VM_LOG(st) << "default exception handler, terminating vm with exit code " << n;
return ~n;
}
int PushIntCont::jump(VmState* st) const & {
VM_LOG(st) << "execute implicit PUSH " << push_val << " (slow)";
st->get_stack().push_smallint(push_val);
return st->jump(next);
}
int PushIntCont::jump_w(VmState* st) & {
VM_LOG(st) << "execute implicit PUSH " << push_val;
st->get_stack().push_smallint(push_val);
return st->jump(std::move(next));
}
int ArgContExt::jump(VmState* st) const & {
st->adjust_cr(data.save);
if (data.cp != -1) {
st->force_cp(data.cp);
}
return ext->jump(st);
}
int ArgContExt::jump_w(VmState* st) & {
st->adjust_cr(std::move(data.save));
if (data.cp != -1) {
st->force_cp(data.cp);
}
return st->jump_to(std::move(ext));
}
int RepeatCont::jump(VmState* st) const & {
VM_LOG(st) << "repeat " << count << " more times (slow)\n";
if (count <= 0) {
return st->jump(after);
}
if (body->has_c0()) {
return st->jump(body);
}
st->set_c0(Ref<RepeatCont>{true, body, after, count - 1});
return st->jump(body);
}
int RepeatCont::jump_w(VmState* st) & {
VM_LOG(st) << "repeat " << count << " more times\n";
if (count <= 0) {
body.clear();
return st->jump(std::move(after));
}
if (body->has_c0()) {
after.clear();
return st->jump(std::move(body));
}
// optimization: since this is unique, reuse *this instead of creating new object
--count;
st->set_c0(Ref<RepeatCont>{this});
return st->jump(body);
}
int VmState::repeat(Ref<Continuation> body, Ref<Continuation> after, long long count) {
if (count <= 0) {
body.clear();
return jump(std::move(after));
} else {
return jump(Ref<RepeatCont>{true, std::move(body), std::move(after), count});
}
}
int AgainCont::jump(VmState* st) const & {
VM_LOG(st) << "again an infinite loop iteration (slow)\n";
if (!body->has_c0()) {
st->set_c0(Ref<AgainCont>{this});
}
return st->jump(body);
}
int AgainCont::jump_w(VmState* st) & {
VM_LOG(st) << "again an infinite loop iteration\n";
if (!body->has_c0()) {
st->set_c0(Ref<AgainCont>{this});
return st->jump(body);
} else {
return st->jump(std::move(body));
}
}
int VmState::again(Ref<Continuation> body) {
return jump(Ref<AgainCont>{true, std::move(body)});
}
int UntilCont::jump(VmState* st) const & {
VM_LOG(st) << "until loop body end (slow)\n";
if (st->get_stack().pop_bool()) {
VM_LOG(st) << "until loop terminated\n";
return st->jump(after);
}
if (!body->has_c0()) {
st->set_c0(Ref<UntilCont>{this});
}
return st->jump(body);
}
int UntilCont::jump_w(VmState* st) & {
VM_LOG(st) << "until loop body end\n";
if (st->get_stack().pop_bool()) {
VM_LOG(st) << "until loop terminated\n";
body.clear();
return st->jump(std::move(after));
}
if (!body->has_c0()) {
st->set_c0(Ref<UntilCont>{this});
return st->jump(body);
} else {
after.clear();
return st->jump(std::move(body));
}
}
int VmState::until(Ref<Continuation> body, Ref<Continuation> after) {
if (!body->has_c0()) {
set_c0(Ref<UntilCont>{true, body, std::move(after)});
}
return jump(std::move(body));
}
int WhileCont::jump(VmState* st) const & {
if (chkcond) {
VM_LOG(st) << "while loop condition end (slow)\n";
if (!st->get_stack().pop_bool()) {
VM_LOG(st) << "while loop terminated\n";
return st->jump(after);
}
if (!body->has_c0()) {
st->set_c0(Ref<WhileCont>{true, cond, body, after, false});
}
return st->jump(body);
} else {
VM_LOG(st) << "while loop body end (slow)\n";
if (!cond->has_c0()) {
st->set_c0(Ref<WhileCont>{true, cond, body, after, true});
}
return st->jump(cond);
}
}
int WhileCont::jump_w(VmState* st) & {
if (chkcond) {
VM_LOG(st) << "while loop condition end\n";
if (!st->get_stack().pop_bool()) {
VM_LOG(st) << "while loop terminated\n";
cond.clear();
body.clear();
return st->jump(std::move(after));
}
if (!body->has_c0()) {
chkcond = false; // re-use current object since we hold the unique pointer to it
st->set_c0(Ref<WhileCont>{this});
return st->jump(body);
} else {
cond.clear();
after.clear();
return st->jump(std::move(body));
}
} else {
VM_LOG(st) << "while loop body end\n";
if (!cond->has_c0()) {
chkcond = true; // re-use current object
st->set_c0(Ref<WhileCont>{this});
return st->jump(cond);
} else {
body.clear();
after.clear();
return st->jump(std::move(cond));
}
}
}
int VmState::loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after) {
if (!cond->has_c0()) {
set_c0(Ref<WhileCont>{true, cond, std::move(body), std::move(after), true});
}
return jump(std::move(cond));
}
int OrdCont::jump(VmState* st) const & {
st->adjust_cr(data.save);
st->set_code(code, data.cp);
return 0;
}
int OrdCont::jump_w(VmState* st) & {
st->adjust_cr(std::move(data.save));
st->set_code(std::move(code), data.cp);
return 0;
}
void VmState::init_cregs(bool same_c3, bool push_0) {
cr.set_c0(quit0);
cr.set_c1(quit1);
cr.set_c2(Ref<ExcQuitCont>{true});
if (same_c3) {
cr.set_c3(Ref<OrdCont>{true, code, cp});
if (push_0) {
VM_LOG(this) << "implicit PUSH 0 at start\n";
get_stack().push_smallint(0);
}
} else {
cr.set_c3(Ref<QuitCont>{true, 11});
}
if (cr.d[0].is_null() || cr.d[1].is_null()) {
auto empty_cell = CellBuilder{}.finalize();
for (int i = 0; i < ControlRegs::dreg_num; i++) {
if (cr.d[i].is_null()) {
cr.d[i] = empty_cell;
}
}
}
if (cr.c7.is_null()) {
cr.set_c7(Ref<Tuple>{true});
}
}
VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
ensure_throw(init_cp(0));
init_cregs();
}
VmState::VmState(Ref<CellSlice> _code)
: code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
ensure_throw(init_cp(0));
init_cregs();
}
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
std::vector<Ref<Cell>> _libraries)
: code(std::move(_code))
, stack(std::move(_stack))
, cp(-1)
, dispatch(&dummy_dispatch_table)
, quit0(true, 0)
, quit1(true, 1)
, log(log)
, libraries(std::move(_libraries)) {
ensure_throw(init_cp(0));
set_c4(std::move(_data));
init_cregs(flags & 1, flags & 2);
}
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
std::vector<Ref<Cell>> _libraries)
: code(std::move(_code))
, stack(std::move(_stack))
, cp(-1)
, dispatch(&dummy_dispatch_table)
, quit0(true, 0)
, quit1(true, 1)
, log(log)
, gas(gas)
, libraries(std::move(_libraries)) {
ensure_throw(init_cp(0));
set_c4(std::move(_data));
init_cregs(flags & 1, flags & 2);
}
Ref<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
if (code_cell.is_null()) {
return {};
}
Ref<CellSlice> csr{true, NoVmOrd(), code_cell};
if (csr->is_valid()) {
return csr;
}
return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize());
}
bool VmState::init_cp(int new_cp) {
const DispatchTable* dt = DispatchTable::get_table(new_cp);
if (dt) {
cp = new_cp;
dispatch = dt;
return true;
} else {
return false;
}
}
bool VmState::set_cp(int new_cp) {
return new_cp == cp || init_cp(new_cp);
}
void VmState::force_cp(int new_cp) {
if (!set_cp(new_cp)) {
throw VmError{Excno::inv_opcode, "unsupported codepage"};
}
}
// simple call to a continuation cont
int VmState::call(Ref<Continuation> cont) {
const ControlData* cont_data = cont->get_cdata();
if (cont_data) {
if (cont_data->save.c[0].not_null()) {
// call reduces to a jump
return jump(std::move(cont));
}
if (cont_data->stack.not_null() || cont_data->nargs >= 0) {
// if cont has non-empty stack or expects fixed number of arguments, call is not simple
return call(std::move(cont), -1, -1);
}
// create return continuation, to be stored into new c0
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
cr.set_c0(
std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
return jump_to(std::move(cont));
}
// create return continuation, to be stored into new c0
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
// general implementation of a simple call
cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
return jump_to(std::move(cont));
}
// call with parameters to continuation cont
int VmState::call(Ref<Continuation> cont, int pass_args, int ret_args) {
const ControlData* cont_data = cont->get_cdata();
if (cont_data) {
if (cont_data->save.c[0].not_null()) {
// call reduces to a jump
return jump(std::move(cont), pass_args);
}
int depth = stack->depth();
if (pass_args > depth || cont_data->nargs > depth) {
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
}
if (cont_data->nargs > pass_args && pass_args >= 0) {
throw VmError{Excno::stk_und,
"stack underflow while calling a closure continuation: not enough arguments passed"};
}
auto old_c0 = std::move(cr.c[0]);
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
preclear_cr(cont_data->save);
// no exceptions should be thrown after this point
int copy = cont_data->nargs, skip = 0;
if (pass_args >= 0) {
if (copy >= 0) {
skip = pass_args - copy;
} else {
copy = pass_args;
}
}
// copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements.
Ref<Stack> new_stk;
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
// `cont` already has a stack, create resulting stack from it
if (copy < 0) {
copy = stack->depth();
}
if (cont->is_unique()) {
// optimization: avoid copying stack if we hold the only copy of `cont`
new_stk = std::move(cont.unique_write().get_cdata()->stack);
} else {
new_stk = cont_data->stack;
}
new_stk.write().move_from_stack(get_stack(), copy);
if (skip > 0) {
get_stack().pop_many(skip);
}
} else if (copy >= 0) {
new_stk = get_stack().split_top(copy, skip);
} else {
new_stk = std::move(stack);
stack.clear();
}
// create return continuation using the remainder of current stack
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(cont);
set_stack(std::move(new_stk));
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
return jump_to(std::move(cont));
} else {
// have no continuation data, situation is somewhat simpler
int depth = stack->depth();
if (pass_args > depth) {
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
}
// create new stack from the top `pass_args` elements of the current stack
Ref<Stack> new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack));
// create return continuation using the remainder of the current stack
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
set_stack(std::move(new_stk));
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
return jump_to(std::move(cont));
}
}
// simple jump to continuation cont
int VmState::jump(Ref<Continuation> cont) {
const ControlData* cont_data = cont->get_cdata();
if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) {
// if cont has non-empty stack or expects fixed number of arguments, jump is not simple
return jump(std::move(cont), -1);
} else {
return jump_to(std::move(cont));
}
}
// general jump to continuation cont
int VmState::jump(Ref<Continuation> cont, int pass_args) {
const ControlData* cont_data = cont->get_cdata();
if (cont_data) {
// first do the checks
int depth = stack->depth();
if (pass_args > depth || cont_data->nargs > depth) {
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
}
if (cont_data->nargs > pass_args && pass_args >= 0) {
throw VmError{Excno::stk_und,
"stack underflow while jumping to closure continuation: not enough arguments passed"};
}
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
preclear_cr(cont_data->save);
// no exceptions should be thrown after this point
int copy = cont_data->nargs;
if (pass_args >= 0 && copy < 0) {
copy = pass_args;
}
// copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder.
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
// `cont` already has a stack, create resulting stack from it
if (copy < 0) {
copy = get_stack().depth();
}
Ref<Stack> new_stk;
if (cont->is_unique()) {
// optimization: avoid copying the stack if we hold the only copy of `cont`
new_stk = std::move(cont.unique_write().get_cdata()->stack);
} else {
new_stk = cont_data->stack;
}
new_stk.write().move_from_stack(get_stack(), copy);
set_stack(std::move(new_stk));
} else {
if (copy >= 0) {
get_stack().drop_bottom(stack->depth() - copy);
}
}
return jump_to(std::move(cont));
} else {
// have no continuation data, situation is somewhat simpler
if (pass_args >= 0) {
int depth = get_stack().depth();
if (pass_args > depth) {
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
}
get_stack().drop_bottom(depth - pass_args);
}
return jump_to(std::move(cont));
}
}
int VmState::ret() {
Ref<Continuation> cont = quit0;
cont.swap(cr.c[0]);
return jump(std::move(cont));
}
int VmState::ret(int ret_args) {
Ref<Continuation> cont = quit0;
cont.swap(cr.c[0]);
return jump(std::move(cont), ret_args);
}
int VmState::ret_alt() {
Ref<Continuation> cont = quit1;
cont.swap(cr.c[1]);
return jump(std::move(cont));
}
int VmState::ret_alt(int ret_args) {
Ref<Continuation> cont = quit1;
cont.swap(cr.c[1]);
return jump(std::move(cont), ret_args);
}
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
Ref<Stack> new_stk;
if (stack_copy < 0 || stack_copy == stack->depth()) {
new_stk = std::move(stack);
stack.clear();
} else if (stack_copy > 0) {
stack->check_underflow(stack_copy);
new_stk = get_stack().split_top(stack_copy);
} else {
new_stk = Ref<Stack>{true};
}
Ref<OrdCont> cc = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), cc_args};
stack = std::move(new_stk);
if (save_cr & 7) {
ControlData* cdata = cc.unique_write().get_cdata();
if (save_cr & 1) {
cdata->save.set_c0(std::move(cr.c[0]));
cr.set_c0(quit0);
}
if (save_cr & 2) {
cdata->save.set_c1(std::move(cr.c[1]));
cr.set_c1(quit1);
}
if (save_cr & 4) {
cdata->save.set_c2(std::move(cr.c[2]));
// cr.set_c2(Ref<ExcQuitCont>{true});
}
}
return cc;
}
int VmState::throw_exception(int excno) {
Stack& stack_ref = get_stack();
stack_ref.clear();
stack_ref.push_smallint(0);
stack_ref.push_smallint(excno);
code.clear();
consume_gas(exception_gas_price);
return jump(get_c2());
}
int VmState::throw_exception(int excno, StackEntry&& arg) {
Stack& stack_ref = get_stack();
stack_ref.clear();
stack_ref.push(std::move(arg));
stack_ref.push_smallint(excno);
code.clear();
consume_gas(exception_gas_price);
return jump(get_c2());
}
void GasLimits::gas_exception() const {
throw VmNoGas{};
}
void GasLimits::set_limits(long long _max, long long _limit, long long _credit) {
gas_max = _max;
gas_limit = _limit;
gas_credit = _credit;
change_base(_limit + _credit);
}
void GasLimits::change_limit(long long _limit) {
_limit = std::min(std::max(_limit, 0LL), gas_max);
gas_credit = 0;
gas_limit = _limit;
change_base(_limit);
}
bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) {
gas.set_limits(_max, _limit, _credit);
return true;
}
void VmState::change_gas_limit(long long new_limit) {
VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max);
gas.change_limit(new_limit);
}
int VmState::step() {
assert(!code.is_null());
//VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st));
//VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl;
if (stack_trace) {
stack->dump(std::cerr);
}
++steps;
if (code->size()) {
return dispatch->dispatch(this, code.write());
} else if (code->size_refs()) {
VM_LOG(this) << "execute implicit JMPREF\n";
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
return jump(std::move(cont));
} else {
VM_LOG(this) << "execute implicit RET\n";
return ret();
}
}
int VmState::run() {
int res;
Guard guard(this);
do {
// LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells();
try {
try {
res = step();
gas.check();
} catch (vm::CellBuilder::CellWriteError) {
throw VmError{Excno::cell_ov};
} catch (vm::CellBuilder::CellCreateError) {
throw VmError{Excno::cell_ov};
} catch (vm::CellSlice::CellReadError) {
throw VmError{Excno::cell_und};
}
} catch (const VmError& vme) {
VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg();
try {
// LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells();
++steps;
res = throw_exception(vme.get_errno());
} catch (const VmError& vme2) {
VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg();
// LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells();
return ~vme2.get_errno();
}
} catch (VmNoGas vmoog) {
++steps;
VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed()
<< ", limit=" << gas.gas_limit;
get_stack().clear();
get_stack().push_smallint(gas.gas_consumed());
return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible)
}
} while (!res);
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
return res;
}
ControlData* force_cdata(Ref<Continuation>& cont) {
if (!cont->get_cdata()) {
cont = Ref<ArgContExt>{true, cont};
return cont.unique_write().get_cdata();
} else {
return cont.write().get_cdata();
}
}
ControlRegs* force_cregs(Ref<Continuation>& cont) {
return &force_cdata(cont)->save;
}
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries) {
VmState vm{
code, std::move(stack), gas_limits ? *gas_limits : GasLimits{}, flags, data_ptr ? *data_ptr : Ref<Cell>{},
log, std::move(libraries)};
int res = vm.run();
stack = vm.get_stack_ref();
if (res == -1 && data_ptr) {
*data_ptr = vm.get_c4();
}
if (steps) {
*steps = vm.get_steps_count();
}
if (gas_limits) {
*gas_limits = vm.get_gas_limits();
LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed()
<< ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit
<< ", credit=" << gas_limits->gas_credit;
}
if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) {
VM_LOG(&vm) << "BEGIN_STACK_DUMP";
for (int i = stack->depth(); i > 0; i--) {
VM_LOG(&vm) << (*stack)[i - 1].to_string();
}
VM_LOG(&vm) << "END_STACK_DUMP";
}
return ~res;
}
int run_vm_code(Ref<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries) {
Ref<Stack> stk{true};
stk.unique_write().set_contents(std::move(stack));
stack.clear();
int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries));
CHECK(stack.is_unique());
if (stk.is_null()) {
stack.clear();
} else if (&(*stk) != &stack) {
VmState* st = nullptr;
if (stk->is_unique()) {
VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)";
stack.set_contents(std::move(stk.unique_write()));
} else {
VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)";
stack.set_contents(*stk);
}
}
return res;
}
// may throw a dictionary exception; returns nullptr if library is not found in context
Ref<Cell> VmState::load_library(td::ConstBitPtr hash) {
std::unique_ptr<VmStateInterface> tmp_ctx;
// install temporary dummy vm state interface to prevent charging for cell load operations during library lookup
VmStateInterface::Guard(tmp_ctx.get());
for (const auto& lib_collection : libraries) {
auto lib = lookup_library_in(hash, lib_collection);
if (lib.not_null()) {
return lib;
}
}
return {};
}
bool VmState::register_library_collection(Ref<Cell> lib) {
if (lib.is_null()) {
return true;
}
libraries.push_back(std::move(lib));
return true;
}
void VmState::register_cell_load() {
consume_gas(cell_load_gas_price);
}
void VmState::register_cell_create() {
consume_gas(cell_create_gas_price);
}
td::BitArray<256> VmState::get_state_hash() const {
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
td::BitArray<256> res;
res.clear();
return res;
}
td::BitArray<256> VmState::get_final_state_hash(int exit_code) const {
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
td::BitArray<256> res;
res.clear();
return res;
}
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) {
try {
auto val = dict.lookup(key, 256);
if (val.is_null() || !val->have_refs()) {
return {};
}
auto root = val->prefetch_ref();
if (root.not_null() && !root->get_hash().bits().compare(key, 256)) {
return root;
}
return {};
} catch (vm::VmError) {
return {};
}
}
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root) {
if (lib_root.is_null()) {
return lib_root;
}
vm::Dictionary dict{std::move(lib_root), 256};
return lookup_library_in(key, dict);
}
} // namespace vm

585
crypto/vm/continuation.h Normal file
View file

@ -0,0 +1,585 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refcnt.hpp"
#include "vm/cellslice.h"
#include "vm/stack.hpp"
#include "vm/vmstate.h"
#include "vm/log.h"
namespace vm {
using td::Ref;
class VmState;
class Continuation;
class DispatchTable;
struct ControlRegs {
static constexpr int creg_num = 4, dreg_num = 2, dreg_idx = 4;
Ref<Continuation> c[creg_num]; // c0..c3
Ref<Cell> d[dreg_num]; // c4..c5
Ref<Tuple> c7; // c7
Ref<Continuation> get_c(unsigned idx) const {
return idx < creg_num ? c[idx] : Ref<Continuation>{};
}
Ref<Cell> get_d(unsigned idx) const {
idx -= dreg_idx;
return idx < dreg_num ? d[idx] : Ref<Cell>{};
}
Ref<Tuple> get_c7() const {
return c7;
}
StackEntry get(unsigned idx) const;
static bool valid_idx(unsigned idx) {
return idx < creg_num || (idx >= dreg_idx && idx < dreg_idx + dreg_num) || idx == 7;
}
void set_c0(Ref<Continuation> cont) {
c[0] = std::move(cont);
}
void set_c1(Ref<Continuation> cont) {
c[1] = std::move(cont);
}
void set_c2(Ref<Continuation> cont) {
c[2] = std::move(cont);
}
void set_c3(Ref<Continuation> cont) {
c[3] = std::move(cont);
}
void set_c4(Ref<Cell> cell) {
d[0] = std::move(cell);
}
bool set_c(unsigned idx, Ref<Continuation> cont) {
if (idx < creg_num) {
c[idx] = std::move(cont);
return true;
} else {
return false;
}
}
bool set_d(unsigned idx, Ref<Cell> cell) {
idx -= dreg_idx;
if (idx < dreg_num) {
d[idx] = std::move(cell);
return true;
} else {
return false;
}
}
bool set_c7(Ref<Tuple> tuple) {
c7 = std::move(tuple);
return true;
}
bool set(unsigned idx, StackEntry value);
void define_c0(Ref<Continuation> cont) {
if (c[0].is_null()) {
c[0] = std::move(cont);
}
}
void define_c1(Ref<Continuation> cont) {
if (c[1].is_null()) {
c[1] = std::move(cont);
}
}
void define_c2(Ref<Continuation> cont) {
if (c[2].is_null()) {
c[2] = std::move(cont);
}
}
bool define_c(unsigned idx, Ref<Continuation> cont) {
if (idx < creg_num && c[idx].is_null()) {
c[idx] = std::move(cont);
return true;
} else {
return false;
}
}
bool define_d(unsigned idx, Ref<Cell> cell) {
idx -= dreg_idx;
if (idx < dreg_num && d[idx].is_null()) {
d[idx] = std::move(cell);
return true;
} else {
return false;
}
}
void define_c4(Ref<Cell> cell) {
if (d[0].is_null()) {
d[0] = std::move(cell);
}
}
bool define_c7(Ref<Tuple> tuple) {
if (c7.is_null()) {
c7 = std::move(tuple);
}
return true;
}
bool define(unsigned idx, StackEntry value);
ControlRegs& operator&=(const ControlRegs& save); // clears all c[i]'s which are present in save
ControlRegs& operator^=(const ControlRegs& save); // sets c[i]=save.c[i] for all save.c[i] != 0
ControlRegs& operator^=(ControlRegs&& save);
};
struct ControlData {
Ref<Stack> stack;
ControlRegs save;
int nargs;
int cp;
ControlData() : nargs(-1), cp(-1) {
}
ControlData(int _cp) : nargs(-1), cp(_cp) {
}
ControlData(Ref<Stack> _stack) : stack(std::move(_stack)), nargs(-1), cp(-1) {
}
ControlData(int _cp, Ref<Stack> _stack, int _nargs = -1) : stack(std::move(_stack)), nargs(_nargs), cp(_cp) {
}
};
class Continuation : public td::CntObject {
public:
virtual int jump(VmState* st) const & = 0;
virtual int jump_w(VmState* st) &;
virtual ControlData* get_cdata() {
return 0;
}
virtual const ControlData* get_cdata() const {
return 0;
}
bool has_c0() const;
Continuation() {
}
Continuation(const Continuation&) = default;
Continuation(Continuation&&) {
}
Continuation& operator=(const Continuation&) {
return *this;
}
Continuation& operator=(Continuation&&) {
return *this;
}
~Continuation() override = default;
};
class QuitCont : public Continuation {
int exit_code;
public:
QuitCont(int _code = 0) : exit_code(_code) {
}
~QuitCont() override = default;
int jump(VmState* st) const & override {
return ~exit_code;
}
};
class ExcQuitCont : public Continuation {
public:
ExcQuitCont() = default;
~ExcQuitCont() override = default;
int jump(VmState* st) const & override;
};
class PushIntCont : public Continuation {
int push_val;
Ref<Continuation> next;
public:
PushIntCont(int val, Ref<Continuation> _next) : push_val(val), next(_next) {
}
~PushIntCont() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
};
class RepeatCont : public Continuation {
Ref<Continuation> body, after;
long long count;
public:
RepeatCont(Ref<Continuation> _body, Ref<Continuation> _after, long long _count)
: body(std::move(_body)), after(std::move(_after)), count(_count) {
}
~RepeatCont() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
};
class AgainCont : public Continuation {
Ref<Continuation> body;
public:
AgainCont(Ref<Continuation> _body) : body(std::move(_body)) {
}
~AgainCont() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
};
class UntilCont : public Continuation {
Ref<Continuation> body, after;
public:
UntilCont(Ref<Continuation> _body, Ref<Continuation> _after) : body(std::move(_body)), after(std::move(_after)) {
}
~UntilCont() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
};
class WhileCont : public Continuation {
Ref<Continuation> cond, body, after;
bool chkcond;
public:
WhileCont(Ref<Continuation> _cond, Ref<Continuation> _body, Ref<Continuation> _after, bool _chk = true)
: cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), chkcond(_chk) {
}
~WhileCont() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
};
class ArgContExt : public Continuation {
ControlData data;
Ref<Continuation> ext;
public:
ArgContExt(Ref<Continuation> _ext) : data(), ext(_ext) {
}
ArgContExt(Ref<Continuation> _ext, Ref<Stack> _stack) : data(_stack), ext(_ext) {
}
ArgContExt(const ArgContExt&) = default;
ArgContExt(ArgContExt&&) = default;
~ArgContExt() override = default;
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
ControlData* get_cdata() override {
return &data;
}
const ControlData* get_cdata() const override {
return &data;
}
td::CntObject* make_copy() const override {
return new ArgContExt{*this};
}
};
class OrdCont : public Continuation {
ControlData data;
Ref<CellSlice> code;
friend class VmState;
public:
OrdCont() : data(), code() {
}
//OrdCont(Ref<CellSlice> _code) : data(), code(std::move(_code)) {}
OrdCont(Ref<CellSlice> _code, int _cp) : data(_cp), code(std::move(_code)) {
}
//OrdCont(Ref<CellSlice> _code, Ref<Stack> _stack) : data(std::move(_stack)), code(std::move(_code)) {}
OrdCont(Ref<CellSlice> _code, int _cp, Ref<Stack> _stack, int nargs = -1)
: data(_cp, std::move(_stack), nargs), code(std::move(_code)) {
}
OrdCont(const OrdCont&) = default;
OrdCont(OrdCont&&) = default;
~OrdCont() override = default;
td::CntObject* make_copy() const override {
return new OrdCont{*this};
}
int jump(VmState* st) const & override;
int jump_w(VmState* st) & override;
ControlData* get_cdata() override {
return &data;
}
const ControlData* get_cdata() const override {
return &data;
}
Stack& get_stack() {
return data.stack.write();
}
const Stack& get_stack_const() const {
return *(data.stack);
}
Ref<Stack> get_stack_ref() const {
return data.stack;
}
Ref<OrdCont> copy_ord() const & {
return Ref<OrdCont>{true, *this};
}
Ref<OrdCont> copy_ord() && {
return Ref<OrdCont>{true, *this};
}
};
struct GasLimits {
static constexpr long long infty = (1ULL << 63) - 1;
long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base;
GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) {
}
GasLimits(long long _limit, long long _max = infty, long long _credit = 0)
: gas_max(_max)
, gas_limit(_limit)
, gas_credit(_credit)
, gas_remaining(_limit + _credit)
, gas_base(gas_remaining) {
}
long long gas_consumed() const {
return gas_base - gas_remaining;
}
void set_limits(long long _max, long long _limit, long long _credit = 0);
void change_base(long long _base) {
gas_remaining += _base - gas_base;
gas_base = _base;
}
void change_limit(long long _limit);
void consume(long long amount) {
// LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)";
gas_remaining -= amount;
}
bool try_consume(long long amount) {
// LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)";
return (gas_remaining -= amount) >= 0;
}
void gas_exception() const;
void gas_exception(bool cond) const {
if (!cond) {
gas_exception();
}
}
void consume_chk(long long amount) {
gas_exception(try_consume(amount));
}
void check() const {
gas_exception(gas_remaining >= 0);
}
bool final_ok() const {
return gas_remaining >= gas_credit;
}
};
class VmState final : public VmStateInterface {
Ref<CellSlice> code;
Ref<Stack> stack;
ControlRegs cr;
int cp;
long long steps{0};
const DispatchTable* dispatch;
Ref<QuitCont> quit0, quit1;
VmLog log;
GasLimits gas;
std::vector<Ref<Cell>> libraries;
int stack_trace{0}, debug_off{0};
public:
static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50,
tuple_entry_gas_price = 1;
VmState();
VmState(Ref<CellSlice> _code);
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
std::vector<Ref<Cell>> _libraries = {});
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {});
template <typename... Args>
VmState(Ref<Cell> code_cell, Args&&... args)
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
}
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
bool final_gas_ok() const {
return gas.final_ok();
}
long long gas_consumed() const {
return gas.gas_consumed();
}
void consume_gas(long long amount) {
gas.consume(amount);
}
void consume_tuple_gas(unsigned tuple_len) {
consume_gas(tuple_len * tuple_entry_gas_price);
}
void consume_tuple_gas(const Ref<vm::Tuple>& tup) {
if (tup.not_null()) {
consume_tuple_gas((unsigned)tup->size());
}
}
GasLimits get_gas_limits() const {
return gas;
}
void change_gas_limit(long long new_limit);
template <typename... Args>
void check_underflow(Args... args) {
stack->check_underflow(args...);
}
bool register_library_collection(Ref<Cell> lib);
Ref<Cell> load_library(
td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found
void register_cell_load() override;
void register_cell_create() override;
bool init_cp(int new_cp);
bool set_cp(int new_cp);
void force_cp(int new_cp);
int get_cp() const {
return cp;
}
int incr_stack_trace(int v) {
return stack_trace += v;
}
long long get_steps_count() const {
return steps;
}
td::BitArray<256> get_state_hash() const;
td::BitArray<256> get_final_state_hash(int exit_code) const;
int step();
int run();
Stack& get_stack() {
return stack.write();
}
const Stack& get_stack_const() const {
return *stack;
}
Ref<Stack> get_stack_ref() const {
return stack;
}
Ref<Continuation> get_c0() const {
return cr.c[0];
}
Ref<Continuation> get_c1() const {
return cr.c[1];
}
Ref<Continuation> get_c2() const {
return cr.c[2];
}
Ref<Continuation> get_c3() const {
return cr.c[3];
}
Ref<Cell> get_c4() const {
return cr.d[0];
}
Ref<Tuple> get_c7() const {
return cr.c7;
}
Ref<Continuation> get_c(unsigned idx) const {
return cr.get_c(idx);
}
Ref<Cell> get_d(unsigned idx) const {
return cr.get_d(idx);
}
StackEntry get(unsigned idx) const {
return cr.get(idx);
}
const VmLog& get_log() const {
return log;
}
void define_c0(Ref<Continuation> cont) {
cr.define_c0(std::move(cont));
}
void set_c0(Ref<Continuation> cont) {
cr.set_c0(std::move(cont));
}
void set_c1(Ref<Continuation> cont) {
cr.set_c1(std::move(cont));
}
void set_c2(Ref<Continuation> cont) {
cr.set_c2(std::move(cont));
}
bool set_c(unsigned idx, Ref<Continuation> val) {
return cr.set_c(idx, std::move(val));
}
bool set_d(unsigned idx, Ref<Cell> val) {
return cr.set_d(idx, std::move(val));
}
void set_c4(Ref<Cell> val) {
cr.set_c4(std::move(val));
}
bool set_c7(Ref<Tuple> val) {
return cr.set_c7(std::move(val));
}
bool set(unsigned idx, StackEntry val) {
return cr.set(idx, std::move(val));
}
void set_stack(Ref<Stack> new_stk) {
stack = std::move(new_stk);
}
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
stack.swap(new_stk);
return new_stk;
}
void ensure_throw(bool cond) const {
if (!cond) {
fatal();
}
}
void set_code(Ref<CellSlice> _code, int _cp) {
code = std::move(_code);
force_cp(_cp);
}
Ref<CellSlice> get_code() const {
return code;
}
void push_code() {
get_stack().push_cellslice(get_code());
}
void adjust_cr(const ControlRegs& save) {
cr ^= save;
}
void adjust_cr(ControlRegs&& save) {
cr ^= save;
}
void preclear_cr(const ControlRegs& save) {
cr &= save;
}
int call(Ref<Continuation> cont);
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
int jump(Ref<Continuation> cont);
int jump(Ref<Continuation> cont, int pass_args);
int ret();
int ret(int ret_args);
int ret_alt();
int ret_alt(int ret_args);
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
int again(Ref<Continuation> body);
int until(Ref<Continuation> body, Ref<Continuation> after);
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
int throw_exception(int excno, StackEntry&& arg);
int throw_exception(int excno);
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
void fatal(void) const {
throw VmFatal{};
}
int jump_to(Ref<Continuation> cont) {
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
}
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
private:
void init_cregs(bool same_c3 = false, bool push_0 = true);
};
int run_vm_code(Ref<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = 0, VmLog log = {},
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {});
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = 0, VmLog log = {},
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {});
ControlData* force_cdata(Ref<Continuation>& cont);
ControlRegs* force_cregs(Ref<Continuation>& cont);
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
} // namespace vm

1043
crypto/vm/contops.cpp Normal file

File diff suppressed because it is too large Load diff

28
crypto/vm/contops.h Normal file
View file

@ -0,0 +1,28 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_continuation_ops(OpcodeTable& cp0);
void register_codepage_ops(OpcodeTable& cp0);
} // namespace vm

50
crypto/vm/cp0.cpp Normal file
View file

@ -0,0 +1,50 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "cp0.h"
#include "opctable.h"
#include "stackops.h"
#include "tupleops.h"
#include "arithops.h"
#include "cellops.h"
#include "contops.h"
#include "dictops.h"
#include "debugops.h"
#include "tonops.h"
namespace vm {
const OpcodeTable* init_op_cp0() {
static const OpcodeTable* static_op_cp0 = [] {
auto op_cp0 = new OpcodeTable("TEST CODEPAGE", Codepage::test_cp);
register_stack_ops(*op_cp0); // stackops.cpp
register_tuple_ops(*op_cp0); // tupleops.cpp
register_arith_ops(*op_cp0); // arithops.cpp
register_cell_ops(*op_cp0); // cellops.cpp
register_continuation_ops(*op_cp0); // contops.cpp
register_dictionary_ops(*op_cp0); // dictops.cpp
register_ton_ops(*op_cp0); // tonops.cpp
register_debug_ops(*op_cp0); // debugops.cpp
register_codepage_ops(*op_cp0); // contops.cpp
op_cp0->finalize()->register_table(Codepage::test_cp);
return op_cp0;
}();
return static_op_cp0;
}
} // namespace vm

28
crypto/vm/cp0.h Normal file
View file

@ -0,0 +1,28 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/dispatch.h"
namespace vm {
class OpcodeTable;
const OpcodeTable* init_op_cp0();
} // namespace vm

181
crypto/vm/db/BlobView.cpp Normal file
View file

@ -0,0 +1,181 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/db/BlobView.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/HashMap.h"
#include "td/utils/format.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/port/MemoryMapping.h"
#include <limits>
#include <mutex>
namespace vm {
td::Result<td::Slice> BlobView::view(td::MutableSlice slice, td::uint64 offset) {
if (offset > size() || slice.size() > size() - offset) {
return td::Status::Error(PSLICE() << "BlobView: invalid range requested " << td::tag("slice offset", offset)
<< td::tag("slice size", slice.size()) << td::tag("blob size", size()));
}
return view_impl(slice, offset);
}
namespace {
class BufferSliceBlobViewImpl : public BlobView {
public:
BufferSliceBlobViewImpl(td::BufferSlice slice) : slice_(std::move(slice)) {
}
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
// optimize anyway
if (offset > std::numeric_limits<std::size_t>::max()) {
return td::Slice();
}
return slice_.as_slice().substr(static_cast<std::size_t>(offset), slice.size());
}
td::uint64 size() override {
return slice_.size();
}
private:
td::BufferSlice slice_;
};
} // namespace
std::unique_ptr<BlobView> BufferSliceBlobView::create(td::BufferSlice slice) {
return std::make_unique<BufferSliceBlobViewImpl>(std::move(slice));
}
class FileBlobViewImpl : public BlobView {
public:
FileBlobViewImpl(td::FileFd fd, td::uint64 file_size) : fd_(std::move(fd)), file_size_(file_size) {
}
td::uint64 size() override {
return file_size_;
}
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
CHECK(offset < size());
CHECK(size() - offset >= slice.size());
slice.truncate(file_size_ - offset);
auto first_page = offset / page_size;
auto last_page = (offset + slice.size() - 1) / page_size;
td::uint64 res_offset = 0;
for (auto page_i = first_page; page_i <= last_page; page_i++) {
auto page_offset = page_i * page_size;
auto from = td::max(page_offset, offset);
auto till = td::min(page_offset + page_size, offset + slice.size());
CHECK(from < till);
TRY_RESULT(page, load_page(page_i));
auto len = till - from;
slice.substr(res_offset, len).copy_from(page.substr(from - page_offset, len));
res_offset += len;
}
CHECK(slice.size() == res_offset);
total_view_size_ += slice.size();
return slice;
}
~FileBlobViewImpl() {
//LOG(ERROR) << "LOADED " << pages_.size() << " " << total_view_size_;
}
private:
td::FileFd fd_;
td::uint64 file_size_;
const td::uint64 page_size = 4096;
td::uint64 total_view_size_{0};
td::RwMutex pages_rw_mutex_;
td::HashMap<td::uint64, td::BufferSlice> pages_;
std::mutex fd_mutex_;
td::Result<td::Slice> load_page(td::uint64 page_i) {
{
auto pages_guard = pages_rw_mutex_.lock_read();
auto it = pages_.find(page_i);
if (it != pages_.end()) {
return it->second.as_slice();
}
}
std::lock_guard<std::mutex> fd_guard(fd_mutex_);
{
auto pages_guard = pages_rw_mutex_.lock_read();
auto it = pages_.find(page_i);
if (it != pages_.end()) {
return it->second.as_slice();
}
}
auto offset = page_i * page_size;
auto size = td::min(file_size_ - offset, page_size);
auto buffer_slice = td::BufferSlice(size);
TRY_RESULT(read_size, fd_.pread(buffer_slice.as_slice(), offset));
if (read_size != buffer_slice.size()) {
return td::Status::Error("not enough data in file");
}
auto pages_guard = pages_rw_mutex_.lock_write();
auto &res = pages_[page_i];
res = std::move(buffer_slice);
return res.as_slice();
}
};
td::Result<std::unique_ptr<BlobView>> FileBlobView::create(td::CSlice file_path, td::uint64 file_size) {
TRY_RESULT(fd, td::FileFd::open(file_path, td::FileFd::Flags::Read));
TRY_RESULT(stat, fd.stat());
if (file_size == 0) {
file_size = stat.size_;
} else if (file_size != (td::uint64)stat.size_) {
return td::Status::Error("Wrong file size");
}
return std::make_unique<FileBlobViewImpl>(std::move(fd), file_size);
}
class FileMemoryMappingBlobViewImpl : public BlobView {
public:
FileMemoryMappingBlobViewImpl(td::MemoryMapping mapping) : mapping_(std::move(mapping)) {
}
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
// optimize anyway
return mapping_.as_slice().substr(offset, slice.size());
}
td::uint64 size() override {
return mapping_.as_slice().size();
}
private:
td::MemoryMapping mapping_;
};
td::Result<std::unique_ptr<BlobView>> FileMemoryMappingBlobView::create(td::CSlice file_path, td::uint64 file_size) {
TRY_RESULT(fd, td::FileFd::open(file_path, td::FileFd::Flags::Read));
TRY_RESULT(stat, fd.stat());
if (file_size == 0) {
file_size = stat.size_;
} else if (file_size != (td::uint64)stat.size_) {
return td::Status::Error("Wrong file size");
}
TRY_RESULT(mapping, td::MemoryMapping::create_from_file(fd));
return std::make_unique<FileMemoryMappingBlobViewImpl>(std::move(mapping));
}
} // namespace vm

45
crypto/vm/db/BlobView.h Normal file
View file

@ -0,0 +1,45 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/buffer.h"
namespace vm {
class BlobView {
public:
virtual ~BlobView() = default;
td::Result<td::Slice> view(td::MutableSlice slice, td::uint64 offset);
virtual td::uint64 size() = 0;
private:
virtual td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) = 0;
};
class BufferSliceBlobView {
public:
static std::unique_ptr<BlobView> create(td::BufferSlice slice);
};
class FileBlobView {
public:
static td::Result<std::unique_ptr<BlobView>> create(td::CSlice file_path, td::uint64 file_size = 0);
};
class FileMemoryMappingBlobView {
public:
static td::Result<std::unique_ptr<BlobView>> create(td::CSlice file_path, td::uint64 file_size = 0);
};
} // namespace vm

View file

@ -0,0 +1,71 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/Slice.h"
#include <set>
namespace vm {
template <class InfoT>
class CellHashTable {
public:
template <class F>
InfoT &apply(td::Slice hash, F &&f) {
auto it = set_.find(hash);
if (it != set_.end()) {
auto &res = const_cast<InfoT &>(*it);
f(res);
return res;
}
InfoT info;
f(info);
auto &res = const_cast<InfoT &>(*(set_.insert(std::move(info)).first));
return res;
}
template <class F>
void for_each(F &&f) {
for (auto &info : set_) {
f(info);
}
}
template <class F>
void filter(F &&f) {
for (auto it = set_.begin(); it != set_.end();) {
if (f(*it)) {
it++;
} else {
it = set_.erase(it);
}
}
}
void erase(td::Slice hash) {
auto it = set_.find(hash);
CHECK(it != set_.end());
set_.erase(it);
}
size_t size() const {
return set_.size();
}
private:
std::set<InfoT, std::less<>> set_;
};
} // namespace vm

View file

@ -0,0 +1,163 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/db/CellStorage.h"
#include "vm/db/DynamicBagOfCellsDb.h"
#include "vm/boc.h"
#include "td/utils/base64.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_helpers.h"
namespace vm {
namespace {
class RefcntCellStorer {
public:
RefcntCellStorer(td::int32 refcnt, const DataCell &cell) : refcnt_(refcnt), cell_(cell) {
}
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
store(refcnt_, storer);
store(cell_, storer);
for (unsigned i = 0; i < cell_.size_refs(); i++) {
auto cell = cell_.get_ref(i);
auto level_mask = cell->get_level_mask();
auto level = level_mask.get_level();
td::uint8 x = static_cast<td::uint8>(level_mask.get_mask());
storer.store_slice(td::Slice(&x, 1));
for (unsigned level_i = 0; level_i <= level; level_i++) {
if (!level_mask.is_significant(level_i)) {
continue;
}
storer.store_slice(cell->get_hash(level_i).as_slice());
}
for (unsigned level_i = 0; level_i <= level; level_i++) {
if (!level_mask.is_significant(level_i)) {
continue;
}
td::uint8 depth_buf[Cell::depth_bytes];
DataCell::store_depth(depth_buf, cell->get_depth(level_i));
storer.store_slice(td::Slice(depth_buf, Cell::depth_bytes));
}
}
}
private:
td::int32 refcnt_;
const DataCell &cell_;
};
class RefcntCellParser {
public:
RefcntCellParser(bool need_data) : need_data_(need_data) {
}
td::int32 refcnt;
Ref<DataCell> cell;
template <class ParserT>
void parse(ParserT &parser, ExtCellCreator &ext_cell_creator) {
using ::td::parse;
parse(refcnt, parser);
if (!need_data_) {
return;
}
auto status = [&]() -> td::Status {
TRY_STATUS(parser.get_status());
auto size = parser.get_left_len();
td::Slice data = parser.template fetch_string_raw<td::Slice>(size);
CellSerializationInfo info;
auto cell_data = data;
TRY_STATUS(info.init(cell_data, 0 /*ref_byte_size*/));
data = data.substr(info.end_offset);
Ref<Cell> refs[Cell::max_refs];
for (int i = 0; i < info.refs_cnt; i++) {
if (data.size() < 1) {
return td::Status::Error("Not enought data");
}
Cell::LevelMask level_mask(data[0]);
auto n = level_mask.get_hashes_count();
auto end_offset = 1 + n * (Cell::hash_bytes + Cell::depth_bytes);
if (data.size() < end_offset) {
return td::Status::Error("Not enought data");
}
TRY_RESULT(ext_cell, ext_cell_creator.ext_cell(level_mask, data.substr(1, n * Cell::hash_bytes),
data.substr(1 + n * Cell::hash_bytes, n * Cell::depth_bytes)));
refs[i] = std::move(ext_cell);
CHECK(refs[i]->get_level() == level_mask.get_level());
data = data.substr(end_offset);
}
if (!data.empty()) {
return td::Status::Error("Too much data");
}
TRY_RESULT(data_cell, info.create_data_cell(cell_data, td::Span<Ref<Cell>>(refs, info.refs_cnt)));
cell = std::move(data_cell);
return td::Status::OK();
}();
if (status.is_error()) {
parser.set_error(status.message().str());
return;
}
}
private:
bool need_data_;
};
} // namespace
CellLoader::CellLoader(std::shared_ptr<KeyValueReader> reader) : reader_(std::move(reader)) {
CHECK(reader_);
}
td::Result<CellLoader::LoadResult> CellLoader::load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator) {
//LOG(ERROR) << "Storage: load cell " << hash.size() << " " << td::base64_encode(hash);
LoadResult res;
std::string serialized;
TRY_RESULT(get_status, reader_->get(hash, serialized));
if (get_status != KeyValue::GetStatus::Ok) {
DCHECK(get_status == KeyValue::GetStatus::NotFound);
return res;
}
res.status = LoadResult::Ok;
RefcntCellParser refcnt_cell(need_data);
td::TlParser parser(serialized);
refcnt_cell.parse(parser, ext_cell_creator);
TRY_STATUS(parser.get_status());
res.refcnt_ = refcnt_cell.refcnt;
res.cell_ = std::move(refcnt_cell.cell);
//CHECK(res.cell_->get_hash() == hash);
return res;
}
CellStorer::CellStorer(KeyValue &kv) : kv_(kv) {
}
td::Status CellStorer::erase(td::Slice hash) {
return kv_.erase(hash);
}
td::Status CellStorer::set(td::int32 refcnt, const DataCell &cell) {
return kv_.set(cell.get_hash().as_slice(), td::serialize(RefcntCellStorer(refcnt, cell)));
}
} // namespace vm

View file

@ -0,0 +1,65 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/db/KeyValue.h"
#include "vm/db/DynamicBagOfCellsDb.h"
#include "vm/cells.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
namespace vm {
using KeyValue = td::KeyValue;
using KeyValueReader = td::KeyValueReader;
class CellLoader {
public:
struct LoadResult {
public:
enum { Ok, NotFound } status{NotFound};
Ref<DataCell> &cell() {
DCHECK(status == Ok);
return cell_;
}
td::int32 refcnt() const {
return refcnt_;
}
Ref<DataCell> cell_;
td::int32 refcnt_{0};
};
CellLoader(std::shared_ptr<KeyValueReader> reader);
td::Result<LoadResult> load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator);
private:
std::shared_ptr<KeyValueReader> reader_;
};
class CellStorer {
public:
CellStorer(KeyValue &kv);
td::Status erase(td::Slice hash);
td::Status set(td::int32 refcnt, const DataCell &cell);
private:
KeyValue &kv_;
};
} // namespace vm

View file

@ -0,0 +1,500 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/db/DynamicBagOfCellsDb.h"
#include "vm/db/CellStorage.h"
#include "vm/db/CellHashTable.h"
#include "vm/cells/ExtCell.h"
#include "td/utils/base64.h"
#include "td/utils/format.h"
#include "td/utils/ThreadSafeCounter.h"
#include "vm/cellslice.h"
namespace vm {
namespace {
class CellDbReader {
public:
virtual ~CellDbReader() = default;
virtual td::Result<Ref<DataCell>> load_cell(td::Slice hash) = 0;
};
struct DynamicBocExtCellExtra {
std::shared_ptr<CellDbReader> reader;
};
class DynamicBocCellLoader {
public:
static td::Result<Ref<DataCell>> load_data_cell(const Cell &cell, const DynamicBocExtCellExtra &extra) {
return extra.reader->load_cell(cell.get_hash().as_slice());
}
};
using DynamicBocExtCell = ExtCell<DynamicBocExtCellExtra, DynamicBocCellLoader>;
struct CellInfo {
bool sync_with_db{false};
bool in_db{false};
bool was_dfs_new_cells{false};
bool was{false};
td::int32 db_refcnt{0};
td::int32 refcnt_diff{0};
Ref<Cell> cell;
Cell::Hash key() const {
return cell->get_hash();
}
bool operator<(const CellInfo &other) const {
return key() < other.key();
}
};
bool operator<(const CellInfo &a, td::Slice b) {
return a.key().as_slice() < b;
}
bool operator<(td::Slice a, const CellInfo &b) {
return a < b.key().as_slice();
}
class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreator {
public:
DynamicBagOfCellsDbImpl() {
get_thread_safe_counter().add(1);
}
~DynamicBagOfCellsDbImpl() {
get_thread_safe_counter().add(-1);
reset_cell_db_reader();
}
td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) override {
return get_cell_info_lazy(level_mask, hash, depth).cell;
}
td::Result<Ref<DataCell>> load_cell(td::Slice hash) override {
TRY_RESULT(loaded_cell, get_cell_info_force(hash).cell->load_cell());
return std::move(loaded_cell.data_cell);
}
CellInfo &get_cell_info_force(td::Slice hash) {
return hash_table_.apply(hash, [&](CellInfo &info) { update_cell_info_force(info, hash); });
}
CellInfo &get_cell_info_lazy(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
return hash_table_.apply(hash.substr(hash.size() - Cell::hash_bytes),
[&](CellInfo &info) { update_cell_info_lazy(info, level_mask, hash, depth); });
}
CellInfo &get_cell_info(const Ref<Cell> &cell) {
return hash_table_.apply(cell->get_hash().as_slice(), [&](CellInfo &info) { update_cell_info(info, cell); });
}
void inc(const Ref<Cell> &cell) override {
if (cell.is_null()) {
return;
}
if (cell->get_virtualization() != 0) {
return;
}
//LOG(ERROR) << "INC";
//CellSlice(cell, nullptr).print_rec(std::cout);
to_inc_.push_back(cell);
}
void dec(const Ref<Cell> &cell) override {
if (cell.is_null()) {
return;
}
if (cell->get_virtualization() != 0) {
return;
}
//LOG(ERROR) << "DEC";
//CellSlice(cell, nullptr).print_rec(std::cout);
to_dec_.push_back(cell);
}
bool is_prepared_for_commit() {
return to_inc_.empty() && to_dec_.empty();
}
Stats get_stats_diff() override {
CHECK(is_prepared_for_commit());
return stats_diff_;
}
td::Status prepare_commit() override {
if (is_prepared_for_commit()) {
return td::Status::OK();
}
//LOG(ERROR) << "dfs_new_cells_in_db";
for (auto &new_cell : to_inc_) {
auto &new_cell_info = get_cell_info(new_cell);
dfs_new_cells_in_db(new_cell_info);
}
//return td::Status::OK();
//LOG(ERROR) << "dfs_new_cells";
for (auto &new_cell : to_inc_) {
auto &new_cell_info = get_cell_info(new_cell);
dfs_new_cells(new_cell_info);
}
//LOG(ERROR) << "dfs_old_cells";
for (auto &old_cell : to_dec_) {
auto &old_cell_info = get_cell_info(old_cell);
dfs_old_cells(old_cell_info);
}
//LOG(ERROR) << "save_diff_prepare";
save_diff_prepare();
to_inc_.clear();
to_dec_.clear();
return td::Status::OK();
}
td::Status commit(CellStorer &storer) override {
prepare_commit();
save_diff(storer);
// Some elements are erased from hash table, to keep it small.
// Hash table is no longer represents the difference between the loader and
// the current bag of cells.
reset_cell_db_reader();
return td::Status::OK();
}
td::Status set_loader(std::unique_ptr<CellLoader> loader) override {
reset_cell_db_reader();
loader_ = std::move(loader);
//cell_db_reader_ = std::make_shared<CellDbReaderImpl>(this);
// Temporary(?) fix to make ExtCell thread safe.
// Downside(?) - loaded cells won't be cached
cell_db_reader_ = std::make_shared<CellDbReaderImpl>(std::make_unique<CellLoader>(*loader_));
stats_diff_ = {};
return td::Status::OK();
}
private:
std::unique_ptr<CellLoader> loader_;
std::vector<Ref<Cell>> to_inc_;
std::vector<Ref<Cell>> to_dec_;
CellHashTable<CellInfo> hash_table_;
std::vector<CellInfo *> visited_;
Stats stats_diff_;
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DynamicBagOfCellsDb");
return res;
}
class CellDbReaderImpl : public CellDbReader,
private ExtCellCreator,
public std::enable_shared_from_this<CellDbReaderImpl> {
public:
CellDbReaderImpl(std::unique_ptr<CellLoader> cell_loader) : db_(nullptr), cell_loader_(std::move(cell_loader)) {
if (cell_loader_) {
get_thread_safe_counter().add(1);
}
}
CellDbReaderImpl(DynamicBagOfCellsDb *db) : db_(db) {
}
~CellDbReaderImpl() {
if (cell_loader_) {
get_thread_safe_counter().add(-1);
}
}
void set_loader(std::unique_ptr<CellLoader> cell_loader) {
if (cell_loader_) {
// avoid race
return;
}
cell_loader_ = std::move(cell_loader);
db_ = nullptr;
if (cell_loader_) {
get_thread_safe_counter().add(1);
}
}
td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) override {
CHECK(!db_);
TRY_RESULT(ext_cell, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
DynamicBocExtCellExtra{shared_from_this()}));
return std::move(ext_cell);
}
td::Result<Ref<DataCell>> load_cell(td::Slice hash) override {
if (db_) {
return db_->load_cell(hash);
}
TRY_RESULT(load_result, cell_loader_->load(hash, true, *this));
CHECK(load_result.status == CellLoader::LoadResult::Ok);
return std::move(load_result.cell());
}
private:
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DynamicBagOfCellsDbLoader");
return res;
}
DynamicBagOfCellsDb *db_;
std::unique_ptr<CellLoader> cell_loader_;
};
std::shared_ptr<CellDbReaderImpl> cell_db_reader_;
void reset_cell_db_reader() {
if (!cell_db_reader_) {
return;
}
cell_db_reader_->set_loader(std::move(loader_));
cell_db_reader_.reset();
//EXPERIMENTAL: clear cache to drop all references to old reader.
hash_table_ = {};
}
bool is_in_db(CellInfo &info) {
if (info.in_db) {
return true;
}
load_cell(info);
return info.in_db;
}
bool is_loaded(CellInfo &info) {
return info.sync_with_db;
}
void load_cell(CellInfo &info) {
if (is_loaded(info)) {
return;
}
do_load_cell(info);
}
bool dfs_new_cells_in_db(CellInfo &info) {
if (info.sync_with_db) {
return is_in_db(info);
}
if (info.in_db) {
return true;
}
bool not_in_db = false;
for_each(
info, [&not_in_db, this](auto &child_info) { not_in_db |= !dfs_new_cells_in_db(child_info); }, false);
if (not_in_db) {
CHECK(!info.in_db);
info.sync_with_db = true;
}
return is_in_db(info);
}
void dfs_new_cells(CellInfo &info) {
info.refcnt_diff++;
if (!info.was) {
info.was = true;
visited_.push_back(&info);
}
//LOG(ERROR) << "dfs new " << td::format::escaped(info.cell->hash());
if (info.was_dfs_new_cells) {
return;
}
info.was_dfs_new_cells = true;
if (is_in_db(info)) {
return;
}
CHECK(is_loaded(info));
for_each(info, [this](auto &child_info) { dfs_new_cells(child_info); });
}
void dfs_old_cells(CellInfo &info) {
info.refcnt_diff--;
if (!info.was) {
info.was = true;
visited_.push_back(&info);
}
//LOG(ERROR) << "dfs old " << td::format::escaped(info.cell->hash());
load_cell(info);
auto new_refcnt = info.refcnt_diff + info.db_refcnt;
CHECK(new_refcnt >= 0);
if (new_refcnt != 0) {
return;
}
for_each(info, [this](auto &child_info) { dfs_old_cells(child_info); });
}
void save_diff_prepare() {
stats_diff_ = {};
for (auto info_ptr : visited_) {
save_cell_prepare(*info_ptr);
}
}
void save_diff(CellStorer &storer) {
//LOG(ERROR) << hash_table_.size();
for (auto info_ptr : visited_) {
save_cell(*info_ptr, storer);
}
visited_.clear();
}
void save_cell_prepare(CellInfo &info) {
if (info.refcnt_diff == 0) {
//CellSlice(info.cell, nullptr).print_rec(std::cout);
return;
}
load_cell(info);
auto loaded_cell = info.cell->load_cell().move_as_ok();
if (info.db_refcnt + info.refcnt_diff == 0) {
CHECK(info.in_db);
// erase
stats_diff_.cells_total_count--;
stats_diff_.cells_total_size -= loaded_cell.data_cell->get_serialized_size(true);
} else {
//save
if (info.in_db == false) {
stats_diff_.cells_total_count++;
stats_diff_.cells_total_size += loaded_cell.data_cell->get_serialized_size(true);
}
}
}
void save_cell(CellInfo &info, CellStorer &storer) {
auto guard = td::ScopeExit{} + [&] {
info.was_dfs_new_cells = false;
info.was = false;
};
if (info.refcnt_diff == 0) {
//CellSlice(info.cell, nullptr).print_rec(std::cout);
return;
}
CHECK(info.sync_with_db);
info.db_refcnt += info.refcnt_diff;
info.refcnt_diff = 0;
if (info.db_refcnt == 0) {
CHECK(info.in_db);
//LOG(ERROR) << "ERASE";
//CellSlice(NoVm(), info.cell).print_rec(std::cout);
storer.erase(info.cell->get_hash().as_slice());
info.in_db = false;
hash_table_.erase(info.cell->get_hash().as_slice());
guard.dismiss();
} else {
//LOG(ERROR) << "SAVE " << info.db_refcnt;
//CellSlice(NoVm(), info.cell).print_rec(std::cout);
auto loaded_cell = info.cell->load_cell().move_as_ok();
storer.set(info.db_refcnt, *loaded_cell.data_cell);
info.in_db = true;
}
}
template <class F>
void for_each(CellInfo &info, F &&f, bool force = true) {
auto cell = info.cell;
if (!cell->is_loaded()) {
if (!force) {
return;
}
load_cell(info);
cell = info.cell;
}
if (!cell->is_loaded()) {
cell->load_cell().ensure();
}
CHECK(cell->is_loaded());
vm::CellSlice cs(vm::NoVm{}, cell); // FIXME
for (unsigned i = 0; i < cs.size_refs(); i++) {
//LOG(ERROR) << "---> " << td::format::escaped(cell->ref(i)->hash());
f(get_cell_info(cs.prefetch_ref(i)));
}
}
void do_load_cell(CellInfo &info) {
update_cell_info_force(info, info.cell->get_hash().as_slice());
}
void update_cell_info(CellInfo &info, const Ref<Cell> &cell) {
CHECK(!cell.is_null());
if (info.sync_with_db) {
return;
}
info.cell = cell;
}
void update_cell_info_lazy(CellInfo &info, Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
if (info.sync_with_db) {
CHECK(info.cell.not_null());
CHECK(info.cell->get_level_mask() == level_mask);
return;
}
if (info.cell.is_null()) {
auto ext_cell_r = create_empty_ext_cell(level_mask, hash, depth);
if (ext_cell_r.is_error()) {
//FIXME
LOG(ERROR) << "Failed to create ext_cell" << ext_cell_r.error();
return;
}
info.cell = ext_cell_r.move_as_ok();
info.in_db = true; // TODO
}
}
void update_cell_info_force(CellInfo &info, td::Slice hash) {
if (info.sync_with_db) {
return;
}
do {
CHECK(loader_);
auto r_res = loader_->load(hash, true, *this);
if (r_res.is_error()) {
//FIXME
LOG(ERROR) << "Failed to load cell from db" << r_res.error();
break;
}
auto res = r_res.move_as_ok();
if (res.status != CellLoader::LoadResult::Ok) {
break;
}
info.cell = std::move(res.cell());
CHECK(info.cell->get_hash().as_slice() == hash);
info.in_db = true;
info.db_refcnt = res.refcnt();
} while (false);
info.sync_with_db = true;
}
td::Result<Ref<Cell>> create_empty_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
TRY_RESULT(res, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
DynamicBocExtCellExtra{cell_db_reader_}));
return std::move(res);
}
};
} // namespace
std::unique_ptr<DynamicBagOfCellsDb> DynamicBagOfCellsDb::create() {
return std::make_unique<DynamicBagOfCellsDbImpl>();
}
} // namespace vm

View file

@ -0,0 +1,62 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
namespace vm {
class CellLoader;
class CellStorer;
} // namespace vm
namespace vm {
class ExtCellCreator {
public:
virtual ~ExtCellCreator() = default;
virtual td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) = 0;
};
class DynamicBagOfCellsDb {
public:
virtual ~DynamicBagOfCellsDb() = default;
virtual td::Result<Ref<DataCell>> load_cell(td::Slice hash) = 0;
struct Stats {
td::int64 cells_total_count{0};
td::int64 cells_total_size{0};
void apply_diff(Stats diff) {
cells_total_count += diff.cells_total_count;
cells_total_size += diff.cells_total_size;
}
};
virtual void inc(const Ref<Cell> &old_root) = 0;
virtual void dec(const Ref<Cell> &old_root) = 0;
virtual td::Status prepare_commit() = 0;
virtual Stats get_stats_diff() = 0;
virtual td::Status commit(CellStorer &) = 0;
// restart with new loader will also reset stats_diff
virtual td::Status set_loader(std::unique_ptr<CellLoader> loader) = 0;
static std::unique_ptr<DynamicBagOfCellsDb> create();
};
} // namespace vm

View file

@ -0,0 +1,545 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/db/StaticBagOfCellsDb.h"
#include "vm/cells/CellWithStorage.h"
#include "vm/boc.h"
#include "vm/cells/ExtCell.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/misc.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/ConcurrentHashTable.h"
#include <limits>
namespace vm {
//
// Common interface
//
template <class ExtraT>
class RootCell : public Cell {
struct PrivateTag {};
public:
td::Result<LoadedCell> load_cell() const override {
return cell_->load_cell();
}
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
return cell_->virtualize(virt);
}
td::uint32 get_virtualization() const override {
return cell_->get_virtualization();
}
CellUsageTree::NodePtr get_tree_node() const override {
return cell_->get_tree_node();
}
bool is_loaded() const override {
return cell_->is_loaded();
}
// hash and level
LevelMask get_level_mask() const override {
return cell_->get_level_mask();
}
template <class T>
static Ref<Cell> create(Ref<Cell> cell, T&& extra) {
return Ref<RootCell>(true, std::move(cell), std::forward<T>(extra), PrivateTag{});
}
template <class T>
RootCell(Ref<Cell> cell, T&& extra, PrivateTag) : cell_(std::move(cell)), extra_(std::forward<T>(extra)) {
}
private:
Ref<Cell> cell_;
ExtraT extra_;
td::uint16 do_get_depth(td::uint32 level) const override {
return cell_->get_depth(level);
}
const Hash do_get_hash(td::uint32 level) const override {
return cell_->get_hash(level);
}
};
class DataCellCacheNoop {
public:
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
return cell;
}
Ref<DataCell> load(int idx) {
return {};
}
void clear() {
}
};
class DataCellCacheMutex {
public:
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
auto lock = cells_rw_mutex_.lock_write();
return cells_.emplace(idx, std::move(cell)).first->second;
}
Ref<DataCell> load(int idx) {
auto lock = cells_rw_mutex_.lock_read();
auto it = cells_.find(idx);
if (it != cells_.end()) {
return it->second;
}
return {};
}
void clear() {
auto guard = cells_rw_mutex_.lock_write();
cells_.clear();
}
private:
td::RwMutex cells_rw_mutex_;
td::HashMap<int, Ref<DataCell>> cells_;
};
class DataCellCacheTdlib {
public:
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
return Ref<DataCell>(cells_.insert(as_key(idx), cell.get()));
}
Ref<DataCell> load(int idx) {
return Ref<DataCell>(cells_.find(as_key(idx), nullptr));
}
void clear() {
cells_.for_each([](auto key, auto value) { Ref<DataCell>{value}; });
}
private:
td::ConcurrentHashMap<td::uint32, const DataCell*> cells_;
td::uint32 as_key(int idx) {
td::uint32 key = static_cast<td::uint32>(idx + 1);
key *= 1000000007;
return key;
}
};
struct StaticBocExtCellExtra {
int idx;
std::weak_ptr<StaticBagOfCellsDb> deserializer;
};
class StaticBocLoader {
public:
static td::Result<Ref<DataCell>> load_data_cell(const Cell& cell, const StaticBocExtCellExtra& extra) {
auto deserializer = extra.deserializer.lock();
if (!deserializer) {
return td::Status::Error("StaticBocDb is already destroyed, cannot fetch cell");
}
return deserializer->load_by_idx(extra.idx);
}
};
using StaticBocExtCell = ExtCell<StaticBocExtCellExtra, StaticBocLoader>;
using StaticBocRootCell = RootCell<std::shared_ptr<StaticBagOfCellsDb>>;
td::Result<Ref<Cell>> StaticBagOfCellsDb::create_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth,
int idx) {
TRY_RESULT(res, StaticBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
StaticBocExtCellExtra{idx, shared_from_this()}));
return std::move(res);
}
//
// Baseline implementation
//
class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb {
public:
StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
}
td::Result<size_t> get_root_count() override {
return roots_.size();
};
td::Result<Ref<Cell>> get_root_cell(size_t idx) override {
if (idx >= roots_.size()) {
return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx);
}
return roots_[idx];
};
private:
std::vector<Ref<Cell>> roots_;
td::Result<Ref<DataCell>> load_by_idx(int idx) override {
UNREACHABLE();
}
};
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbBaseline::create(std::unique_ptr<BlobView> data) {
std::string buf(data->size(), '\0');
TRY_RESULT(slice, data->view(buf, 0));
return create(slice);
}
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbBaseline::create(td::Slice data) {
BagOfCells boc;
TRY_RESULT(x, boc.deserialize(data));
if (x <= 0) {
return td::Status::Error("failed to deserialize");
}
std::vector<Ref<Cell>> roots(boc.get_root_count());
for (int i = 0; i < boc.get_root_count(); i++) {
roots[i] = boc.get_root_cell(i);
}
return std::make_shared<StaticBagOfCellsDbBaselineImpl>(std::move(roots));
}
//
// Main implementation
//
class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
public:
explicit StaticBagOfCellsDbLazyImpl(std::unique_ptr<BlobView> data, StaticBagOfCellsDbLazy::Options options)
: data_(std::move(data)), options_(std::move(options)) {
get_thread_safe_counter().add(1);
}
td::Result<size_t> get_root_count() override {
TRY_STATUS(check_status());
TRY_STATUS(check_result(load_header()));
return info_.root_count;
};
td::Result<Ref<Cell>> get_root_cell(size_t idx) override {
TRY_STATUS(check_status());
TRY_RESULT(root_count, get_root_count());
if (idx >= root_count) {
return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx);
}
TRY_RESULT(cell_idx, load_root_idx(td::narrow_cast<int>(idx)));
// Load DataCell in order to ensure lower hashes correctness
// They will be valid for all non-root cell automaically
TRY_RESULT(data_cell, check_result(load_data_cell(td::narrow_cast<int>(cell_idx))));
return create_root_cell(std::move(data_cell));
};
~StaticBagOfCellsDbLazyImpl() {
//LOG(ERROR) << deserialize_cell_cnt_ << " " << deserialize_cell_hash_cnt_;
get_thread_safe_counter().add(-1);
}
private:
std::atomic<bool> should_cache_cells_{true};
std::unique_ptr<BlobView> data_;
StaticBagOfCellsDbLazy::Options options_;
bool has_info_{false};
BagOfCells::Info info_;
std::mutex index_i_mutex_;
td::RwMutex index_data_rw_mutex_;
std::string index_data_;
std::atomic<int> index_i_{0};
size_t index_offset_{0};
DataCellCacheMutex cells_;
//DataCellCacheNoop cells_;
//DataCellCacheTdlib cells_;
int next_idx_{0};
Ref<Cell> empty_cell_;
//stats
td::ThreadSafeCounter deserialize_cell_cnt_;
td::ThreadSafeCounter deserialize_cell_hash_cnt_;
std::atomic<bool> has_error_{false};
std::mutex status_mutex_;
td::Status status_;
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("StaticBagOfCellsDbLazy");
return res;
}
td::Status check_status() TD_WARN_UNUSED_RESULT {
if (has_error_.load(std::memory_order_relaxed)) {
std::lock_guard<std::mutex> guard(status_mutex_);
return status_.clone();
}
return td::Status::OK();
}
template <class T>
T check_result(T&& to_check) {
CHECK(status_.is_ok());
if (to_check.is_error()) {
std::lock_guard<std::mutex> guard(status_mutex_);
has_error_.store(true);
status_ = to_check.error().clone();
}
return std::forward<T>(to_check);
}
td::Result<Ref<DataCell>> load_by_idx(int idx) override {
TRY_STATUS(check_status());
return check_result(load_data_cell(idx));
}
struct Ptr {
td::MutableSlice as_slice() {
return data;
}
td::string data;
};
// May be optimized
auto alloc(size_t size) {
//return td::StackAllocator::alloc(size);
return Ptr{std::string(size, '\0')};
}
td::Result<size_t> load_idx_offset(int idx) {
if (idx < 0) {
return 0;
}
td::Slice offset_view;
CHECK(info_.offset_byte_size <= 8);
char arr[8];
td::RwMutex::ReadLock guard;
if (info_.has_index) {
TRY_RESULT(new_offset_view, data_->view(td::MutableSlice(arr, info_.offset_byte_size),
info_.index_offset + idx * info_.offset_byte_size));
offset_view = new_offset_view;
} else {
guard = index_data_rw_mutex_.lock_read().move_as_ok();
offset_view = td::Slice(index_data_).substr(idx * info_.offset_byte_size, info_.offset_byte_size);
}
CHECK(offset_view.size() == (size_t)info_.offset_byte_size);
return td::narrow_cast<std::size_t>(info_.read_offset(offset_view.ubegin()));
}
td::Result<td::int64> load_root_idx(int root_i) {
CHECK(root_i >= 0 && root_i < info_.root_count);
if (!info_.has_roots) {
return 0;
}
char arr[8];
TRY_RESULT(idx_view, data_->view(td::MutableSlice(arr, info_.ref_byte_size),
info_.roots_offset + root_i * info_.ref_byte_size));
CHECK(idx_view.size() == (size_t)info_.ref_byte_size);
return info_.read_ref(idx_view.ubegin());
}
struct CellLocation {
std::size_t begin;
std::size_t end;
bool should_cache;
};
td::Result<CellLocation> get_cell_location(int idx) {
CHECK(idx >= 0);
CHECK(idx < info_.cell_count);
TRY_STATUS(preload_index(idx));
TRY_RESULT(from, load_idx_offset(idx - 1));
TRY_RESULT(till, load_idx_offset(idx));
CellLocation res;
res.begin = from;
res.end = till;
res.should_cache = true;
if (info_.has_cache_bits) {
res.begin /= 2;
res.should_cache = res.end % 2 == 1;
res.end /= 2;
}
CHECK(std::numeric_limits<std::size_t>::max() - res.begin >= info_.data_offset);
CHECK(std::numeric_limits<std::size_t>::max() - res.end >= info_.data_offset);
res.begin += static_cast<std::size_t>(info_.data_offset);
res.end += static_cast<std::size_t>(info_.data_offset);
return res;
}
td::Status load_header() {
if (has_info_) {
return td::Status::OK();
}
std::string header(1000, '\0');
TRY_RESULT(header_view, data_->view(td::MutableSlice(header).truncate(data_->size()), 0))
auto parse_res = info_.parse_serialized_header(header_view);
if (parse_res <= 0) {
return td::Status::Error("bag-of-cell error: failed to read header");
}
if (info_.total_size < data_->size()) {
return td::Status::Error("bag-of-cell error: not enough data");
}
if (options_.check_crc32c && info_.has_crc32c) {
std::string buf(td::narrow_cast<std::size_t>(info_.total_size), '\0');
TRY_RESULT(data, data_->view(td::MutableSlice(buf), 0));
unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4});
unsigned crc_stored = td::as<unsigned>(data.uend() - 4);
if (crc_computed != crc_stored) {
return td::Status::Error(PSLICE()
<< "bag-of-cells CRC32C mismatch: expected " << td::format::as_hex(crc_computed)
<< ", found " << td::format::as_hex(crc_stored));
}
}
has_info_ = true;
return td::Status::OK();
}
td::Status preload_index(int idx) {
if (info_.has_index) {
return td::Status::OK();
}
CHECK(idx < info_.cell_count);
if (index_i_.load(std::memory_order_relaxed) > idx) {
return td::Status::OK();
}
std::lock_guard<std::mutex> index_i_guard(index_i_mutex_);
std::array<char, 1024> buf;
auto buf_slice = td::MutableSlice(buf.data(), buf.size());
for (; index_i_ <= idx; index_i_++) {
auto offset = td::narrow_cast<size_t>(info_.data_offset + index_offset_);
CHECK(data_->size() >= offset);
TRY_RESULT(cell, data_->view(buf_slice.copy().truncate(data_->size() - offset), offset));
CellSerializationInfo cell_info;
TRY_STATUS(cell_info.init(cell, info_.ref_byte_size));
index_offset_ += cell_info.end_offset;
LOG_CHECK((unsigned)info_.offset_byte_size <= 8) << info_.offset_byte_size;
td::uint8 tmp[8];
info_.write_offset(tmp, index_offset_);
auto guard = index_data_rw_mutex_.lock_write();
index_data_.append(reinterpret_cast<const char*>(tmp), info_.offset_byte_size);
}
return td::Status::OK();
}
Ref<Cell> get_any_cell(int idx) {
return get_data_cell(idx);
}
Ref<DataCell> get_data_cell(int idx) {
return cells_.load(idx);
}
Ref<DataCell> set_data_cell(int idx, Ref<DataCell> cell) {
if (/*idx >= info_.root_count || */ !should_cache_cells_.load(std::memory_order_relaxed)) {
return cell;
}
CHECK(cell.not_null());
return cells_.store(idx, std::move(cell));
}
Ref<Cell> set_any_cell(int idx, Ref<Cell> cell) {
auto data_cell = Ref<DataCell>(cell);
if (data_cell.is_null()) {
return cell;
}
return set_data_cell(idx, std::move(data_cell));
}
td::Result<Ref<Cell>> load_any_cell(int idx) {
{
auto cell = get_any_cell(idx);
if (cell.not_null()) {
return std::move(cell);
}
}
TRY_RESULT(cell_location, get_cell_location(idx));
auto buf = alloc(cell_location.end - cell_location.begin);
TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin));
TRY_RESULT(res, deserialize_any_cell(idx, cell_slice, cell_location.should_cache));
return std::move(res);
}
td::Result<Ref<DataCell>> load_data_cell(int idx) {
{
auto cell = get_data_cell(idx);
if (cell.not_null()) {
return std::move(cell);
}
}
TRY_RESULT(cell_location, get_cell_location(idx));
auto buf = alloc(cell_location.end - cell_location.begin);
TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin));
TRY_RESULT(res, deserialize_data_cell(idx, cell_slice, cell_location.should_cache));
return std::move(res);
}
td::Result<Ref<DataCell>> deserialize_data_cell(int idx, td::Slice cell_slice, bool should_cache) {
CellSerializationInfo cell_info;
TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size));
if (cell_slice.size() != cell_info.end_offset) {
return td::Status::Error(PSLICE() << "unused space in cell #" << idx << " serialization");
}
return deserialize_data_cell(idx, cell_slice, cell_info, should_cache);
}
td::Result<Ref<DataCell>> deserialize_data_cell(int idx, td::Slice cell_slice, const CellSerializationInfo& cell_info,
bool should_cache) {
deserialize_cell_cnt_.add(1);
Ref<Cell> refs[4];
CHECK(cell_info.refs_cnt <= 4);
auto* ref_ptr = cell_slice.ubegin() + cell_info.refs_offset;
for (int k = 0; k < cell_info.refs_cnt; k++, ref_ptr += info_.ref_byte_size) {
int ref_idx = td::narrow_cast<int>(info_.read_ref(ref_ptr));
if (ref_idx >= info_.cell_count) {
return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx
<< " which is too big " << td::tag("cell_count", info_.cell_count));
}
if (idx >= ref_idx) {
return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx
<< " which is a backward reference");
}
TRY_RESULT(ref, load_any_cell(ref_idx));
refs[k] = std::move(ref);
}
TRY_RESULT(data_cell, cell_info.create_data_cell(cell_slice, td::Span<Ref<Cell>>(refs, cell_info.refs_cnt)));
if (!should_cache) {
return std::move(data_cell);
}
return set_data_cell(idx, std::move(data_cell));
}
td::Result<Ref<Cell>> deserialize_any_cell(int idx, td::Slice cell_slice, bool should_cache) {
CellSerializationInfo cell_info;
TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size));
if (cell_info.with_hashes) {
deserialize_cell_hash_cnt_.add(1);
int n = cell_info.level_mask.get_hashes_count();
return create_ext_cell(cell_info.level_mask, cell_slice.substr(cell_info.hashes_offset, n * Cell::hash_bytes),
cell_slice.substr(cell_info.depth_offset, n * Cell::depth_bytes), idx);
}
TRY_RESULT(data_cell, deserialize_data_cell(idx, cell_slice, cell_info, should_cache));
return std::move(data_cell);
}
td::Result<Ref<Cell>> create_root_cell(Ref<DataCell> data_cell) {
return StaticBocRootCell::create(std::move(data_cell), shared_from_this());
}
};
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(std::unique_ptr<BlobView> data,
Options options) {
return std::make_shared<StaticBagOfCellsDbLazyImpl>(std::move(data), std::move(options));
}
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(td::BufferSlice data, Options options) {
return std::make_shared<StaticBagOfCellsDbLazyImpl>(vm::BufferSliceBlobView::create(std::move(data)),
std::move(options));
}
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(std::string data, Options options) {
return create(BufferSliceBlobView::create(td::BufferSlice(data)), std::move(options));
}
} // namespace vm

View file

@ -0,0 +1,60 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells.h"
#include "vm/db/BlobView.h"
#include "td/utils/Status.h"
namespace vm {
class StaticBagOfCellsDb : public std::enable_shared_from_this<StaticBagOfCellsDb> {
public:
virtual ~StaticBagOfCellsDb() = default;
// TODO: handle errors
virtual td::Result<size_t> get_root_count() = 0;
virtual td::Result<Ref<Cell>> get_root_cell(size_t idx) = 0;
protected:
virtual td::Result<Ref<DataCell>> load_by_idx(int idx) = 0;
friend class StaticBocLoader;
friend class StaticBocRootLoader;
td::Result<Ref<Cell>> create_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth, int idx);
td::Result<Ref<Cell>> create_root_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth, int idx);
};
class StaticBagOfCellsDbBaseline {
public:
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::unique_ptr<BlobView> data);
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(td::Slice data);
};
class StaticBagOfCellsDbLazy {
public:
struct Options {
Options() {
}
bool check_crc32c{false};
};
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::unique_ptr<BlobView> data, Options options = {});
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(td::BufferSlice data, Options options = {});
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::string data, Options options = {});
};
} // namespace vm

325
crypto/vm/db/TonDb.cpp Normal file
View file

@ -0,0 +1,325 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/db/TonDb.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/Random.h"
#if TDDB_USE_ROCKSDB
#include "td/db/RocksDb.h"
#endif
namespace vm {
template <class StorerT>
void SmartContractMeta::store(StorerT &storer) const {
using td::store;
store(stats.cells_total_count, storer);
store(stats.cells_total_size, storer);
store(type, storer);
}
template <class ParserT>
void SmartContractMeta::parse(ParserT &parser) {
using td::parse;
parse(stats.cells_total_count, parser);
parse(stats.cells_total_size, parser);
parse(type, parser);
}
//
// SmartContractDbImpl
//
Ref<Cell> SmartContractDbImpl::get_root() {
if (sync_root_with_db_ || !new_root_.is_null()) {
return new_root_;
}
sync_root_with_db();
return new_root_;
}
void SmartContractDbImpl::set_root(Ref<Cell> new_root) {
CHECK(new_root.not_null());
sync_root_with_db();
if (is_dynamic()) {
cell_db_->dec(new_root_);
}
new_root_ = std::move(new_root);
if (is_dynamic()) {
cell_db_->inc(new_root_);
}
}
SmartContractDbImpl::SmartContractDbImpl(td::Slice hash, std::shared_ptr<KeyValueReader> kv)
: hash_(hash.str()), kv_(std::move(kv)) {
cell_db_ = DynamicBagOfCellsDb::create();
}
SmartContractMeta SmartContractDbImpl::get_meta() {
sync_root_with_db();
return meta_;
}
td::Status SmartContractDbImpl::validate_meta() {
if (!is_dynamic()) {
return td::Status::OK();
}
sync_root_with_db();
TRY_RESULT(in_db, kv_->count({}));
if (static_cast<td::int64>(in_db) != meta_.stats.cells_total_count + 2) {
return td::Status::Error(PSLICE() << "Invalid meta " << td::tag("expected_count", in_db)
<< td::tag("meta_count", meta_.stats.cells_total_count + 2));
}
return td::Status::OK();
}
bool SmartContractDbImpl::is_dynamic() const {
return meta_.type == SmartContractMeta::Dynamic;
}
bool SmartContractDbImpl::is_root_changed() const {
return !new_root_.is_null() && (db_root_.is_null() || db_root_->get_hash() != new_root_->get_hash());
}
void SmartContractDbImpl::sync_root_with_db() {
if (sync_root_with_db_) {
return;
}
std::string root_hash;
kv_->get("root", root_hash);
std::string meta_serialized;
kv_->get("meta", meta_serialized);
// TODO: proper serialization
td::unserialize(meta_, meta_serialized).ignore();
sync_root_with_db_ = true;
if (root_hash.empty()) {
meta_.type = SmartContractMeta::Static;
//meta_.type = SmartContractMeta::Dynamic;
} else {
if (is_dynamic()) {
//FIXME: error handling
db_root_ = cell_db_->load_cell(root_hash).move_as_ok();
} else {
std::string boc_serialized;
kv_->get("boc", boc_serialized);
BagOfCells boc;
//TODO: check error
boc.deserialize(boc_serialized);
db_root_ = boc.get_root_cell();
}
CHECK(db_root_->get_hash().as_slice() == root_hash);
new_root_ = db_root_;
}
}
enum { boc_size = 2000 };
void SmartContractDbImpl::prepare_commit_dynamic(bool force) {
if (!is_dynamic()) {
CHECK(force);
meta_.stats = {};
cell_db_->inc(new_root_);
}
cell_db_->prepare_commit();
meta_.stats.apply_diff(cell_db_->get_stats_diff());
if (!force && meta_.stats.cells_total_size < boc_size) {
//LOG(ERROR) << "DYNAMIC -> BOC";
return prepare_commit_static(true);
}
is_dynamic_commit_ = true;
};
void SmartContractDbImpl::prepare_commit_static(bool force) {
BagOfCells boc;
boc.add_root(new_root_);
boc.import_cells().ensure(); // FIXME
if (!force && boc.estimate_serialized_size(15) > boc_size) {
//LOG(ERROR) << "BOC -> DYNAMIC ";
return prepare_commit_dynamic(true);
}
if (is_dynamic()) {
cell_db_->dec(new_root_);
cell_db_->prepare_commit();
// stats is invalid now
}
is_dynamic_commit_ = false;
boc_to_commit_ = boc.serialize_to_string(15);
meta_.stats = {};
}
void SmartContractDbImpl::prepare_transaction() {
sync_root_with_db();
if (!is_root_changed()) {
return;
}
if (is_dynamic()) {
prepare_commit_dynamic(false);
} else {
prepare_commit_static(false);
}
}
void SmartContractDbImpl::commit_transaction(KeyValue &kv) {
if (!is_root_changed()) {
return;
}
if (is_dynamic_commit_) {
//LOG(ERROR) << "STORE DYNAMIC";
if (!is_dynamic() && db_root_.not_null()) {
kv.erase("boc");
}
CellStorer storer(kv);
cell_db_->commit(storer);
meta_.type = SmartContractMeta::Dynamic;
} else {
//LOG(ERROR) << "STORE BOC";
if (is_dynamic() && db_root_.not_null()) {
//LOG(ERROR) << "Clear Dynamic db";
CellStorer storer(kv);
cell_db_->commit(storer);
cell_db_ = DynamicBagOfCellsDb::create();
}
meta_.type = SmartContractMeta::Static;
kv.set("boc", boc_to_commit_);
boc_to_commit_ = {};
}
kv.set("root", new_root_->get_hash().as_slice());
kv.set("meta", td::serialize(meta_));
db_root_ = new_root_;
}
void SmartContractDbImpl::set_reader(std::shared_ptr<KeyValueReader> reader) {
kv_ = std::move(reader);
cell_db_->set_loader(std::make_unique<CellLoader>(kv_));
}
//
// TonDbTransactionImpl
//
SmartContractDb TonDbTransactionImpl::begin_smartcontract(td::Slice hash) {
SmartContractDb res;
contracts_.apply(hash, [&](auto &info) {
if (!info.is_inited) {
info.is_inited = true;
info.hash = hash.str();
info.smart_contract_db = std::make_unique<SmartContractDbImpl>(hash, nullptr);
}
LOG_CHECK(info.generation_ != generation_) << "Cannot begin one smartcontract twice during the same transaction";
CHECK(info.smart_contract_db);
info.smart_contract_db->set_reader(std::make_shared<td::PrefixedKeyValueReader>(reader_, hash));
res = std::move(info.smart_contract_db);
});
return res;
}
void TonDbTransactionImpl::commit_smartcontract(SmartContractDb txn) {
commit_smartcontract(SmartContractDiff(std::move(txn)));
}
void TonDbTransactionImpl::commit_smartcontract(SmartContractDiff txn) {
{
td::PrefixedKeyValue kv(kv_, txn.hash());
txn.commit_transaction(kv);
}
end_smartcontract(txn.extract_smartcontract());
}
void TonDbTransactionImpl::abort_smartcontract(SmartContractDb txn) {
end_smartcontract(std::move(txn));
}
void TonDbTransactionImpl::abort_smartcontract(SmartContractDiff txn) {
end_smartcontract(txn.extract_smartcontract());
}
TonDbTransactionImpl::TonDbTransactionImpl(std::shared_ptr<KeyValue> kv) : kv_(std::move(kv)) {
CHECK(kv_ != nullptr);
reader_.reset(kv_->snapshot().release());
}
void TonDbTransactionImpl::begin() {
kv_->begin_transaction();
generation_++;
}
void TonDbTransactionImpl::commit() {
kv_->commit_transaction();
reader_.reset(kv_->snapshot().release());
}
void TonDbTransactionImpl::abort() {
kv_->abort_transaction();
}
void TonDbTransactionImpl::clear_cache() {
contracts_ = {};
}
void TonDbTransactionImpl::end_smartcontract(SmartContractDb smart_contract) {
contracts_.apply(smart_contract->hash(), [&](auto &info) {
CHECK(info.hash == smart_contract->hash());
CHECK(!info.smart_contract_db);
info.smart_contract_db = std::move(smart_contract);
});
}
//
// TonDbImpl
//
TonDbImpl::TonDbImpl(std::unique_ptr<KeyValue> kv)
: kv_(std::move(kv)), transaction_(std::make_unique<TonDbTransactionImpl>(kv_)) {
}
TonDbImpl::~TonDbImpl() {
CHECK(transaction_);
kv_->flush();
}
TonDbTransaction TonDbImpl::begin_transaction() {
CHECK(transaction_);
transaction_->begin();
return std::move(transaction_);
}
void TonDbImpl::commit_transaction(TonDbTransaction transaction) {
CHECK(!transaction_);
CHECK(&transaction->kv() == kv_.get());
transaction_ = std::move(transaction);
transaction_->commit();
}
void TonDbImpl::abort_transaction(TonDbTransaction transaction) {
CHECK(!transaction_);
CHECK(&transaction->kv() == kv_.get());
transaction_ = std::move(transaction);
transaction_->abort();
}
void TonDbImpl::clear_cache() {
CHECK(transaction_);
transaction_->clear_cache();
}
std::string TonDbImpl::stats() const {
return kv_->stats();
}
td::Result<TonDb> TonDbImpl::open(td::Slice path) {
#if TDDB_USE_ROCKSDB
TRY_RESULT(rocksdb, td::RocksDb::open(path.str()));
return std::make_unique<TonDbImpl>(std::make_unique<td::RocksDb>(std::move(rocksdb)));
#else
return td::Status::Error("TonDb is not supported in this build");
#endif
}
} // namespace vm

179
crypto/vm/db/TonDb.h Normal file
View file

@ -0,0 +1,179 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cellslice.h"
#include "vm/cells.h"
#include "vm/boc.h"
#include "td/db/KeyValue.h"
#include "vm/db/CellStorage.h"
#include "vm/db/CellHashTable.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
namespace vm {
class SmartContractDbImpl;
using SmartContractDb = std::unique_ptr<SmartContractDbImpl>;
using KeyValue = td::KeyValue;
using KeyValueReader = td::KeyValueReader;
struct SmartContractMeta {
DynamicBagOfCellsDb::Stats stats;
enum BagOfCellsType { Dynamic, Static } type{Static};
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
class SmartContractDbImpl {
public:
Ref<Cell> get_root();
SmartContractMeta get_meta();
td::Status validate_meta();
void set_root(Ref<Cell> new_root);
SmartContractDbImpl(td::Slice hash, std::shared_ptr<KeyValueReader> kv);
private:
std::string hash_;
std::shared_ptr<KeyValueReader> kv_;
bool sync_root_with_db_{false};
Ref<Cell> db_root_;
Ref<Cell> new_root_;
SmartContractMeta meta_;
bool is_dynamic_commit_;
std::string boc_to_commit_;
std::unique_ptr<DynamicBagOfCellsDb> cell_db_;
std::unique_ptr<BagOfCells> bag_of_cells_;
friend class SmartContractDiff;
friend class TonDbTransactionImpl;
void sync_root_with_db();
td::Slice hash() const {
return hash_;
}
void prepare_transaction();
void commit_transaction(KeyValue &kv);
void set_reader(std::shared_ptr<KeyValueReader> reader);
bool is_dynamic() const;
void prepare_commit_dynamic(bool force);
void prepare_commit_static(bool force);
bool is_root_changed() const;
};
class SmartContractDiff {
public:
explicit SmartContractDiff(SmartContractDb db) : db_(std::move(db)) {
db_->prepare_transaction();
}
SmartContractDb extract_smartcontract() {
return std::move(db_);
}
td::Slice hash() const {
return db_->hash();
}
void commit_transaction(KeyValue &kv) {
db_->commit_transaction(kv);
}
private:
SmartContractDb db_;
};
class TonDbTransactionImpl;
using TonDbTransaction = std::unique_ptr<TonDbTransactionImpl>;
class TonDbTransactionImpl {
public:
SmartContractDb begin_smartcontract(td::Slice hash = {});
void commit_smartcontract(SmartContractDb txn);
void commit_smartcontract(SmartContractDiff txn);
void abort_smartcontract(SmartContractDb txn);
void abort_smartcontract(SmartContractDiff txn);
TonDbTransactionImpl(std::shared_ptr<KeyValue> kv);
private:
std::shared_ptr<KeyValue> kv_;
std::shared_ptr<KeyValueReader> reader_;
td::uint64 generation_{0};
struct SmartContractInfo {
bool is_inited{false};
td::uint64 generation_{0};
std::string hash;
SmartContractDb smart_contract_db;
bool operator<(const SmartContractInfo &other) const {
return hash < other.hash;
}
friend bool operator<(const SmartContractInfo &info, td::Slice hash) {
return info.hash < hash;
}
friend bool operator<(td::Slice hash, const SmartContractInfo &info) {
return hash < info.hash;
}
};
CellHashTable<SmartContractInfo> contracts_;
KeyValue &kv() {
return *kv_;
}
friend class TonDbImpl;
void begin();
void commit();
void abort();
void clear_cache();
void end_smartcontract(SmartContractDb smart_contract);
};
class TonDbImpl;
using TonDb = std::unique_ptr<TonDbImpl>;
class TonDbImpl {
public:
TonDbImpl(std::unique_ptr<KeyValue> kv);
~TonDbImpl();
TonDbTransaction begin_transaction();
void commit_transaction(TonDbTransaction transaction);
void abort_transaction(TonDbTransaction transaction);
void clear_cache();
static td::Result<TonDb> open(td::Slice path);
std::string stats() const;
private:
std::shared_ptr<KeyValue> kv_;
TonDbTransaction transaction_;
};
} // namespace vm

110
crypto/vm/debugops.cpp Normal file
View file

@ -0,0 +1,110 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <functional>
#include "vm/debugops.h"
#include "vm/log.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
namespace vm {
bool vm_debug_enabled = true;
int exec_dummy_debug(VmState* st, int args) {
VM_LOG(st) << "execute DEBUG " << (args & 0xff);
return 0;
}
// similar to PUSHSLICE instruction in cellops.cpp
int exec_dummy_debug_str(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
int data_bits = ((args & 15) + 1) * 8;
if (!cs.have(pfx_bits + data_bits)) {
throw VmError{Excno::inv_opcode, "not enough data bits for a DEBUGSTR instruction"};
}
cs.advance(pfx_bits);
auto slice = cs.fetch_subslice(data_bits);
VM_LOG(st) << "execute DEBUGSTR " << slice->as_bitslice().to_hex();
return 0;
}
std::string dump_dummy_debug_str(CellSlice& cs, unsigned args, int pfx_bits) {
int data_bits = ((args & 15) + 1) * 8;
if (!cs.have(pfx_bits + data_bits)) {
return "";
}
cs.advance(pfx_bits);
auto slice = cs.fetch_subslice(data_bits);
slice.unique_write().remove_trailing();
std::ostringstream os;
os << "DEBUGSTR ";
slice->dump_hex(os, 1, false);
return os.str();
}
int compute_len_debug_str(const CellSlice& cs, unsigned args, int pfx_bits) {
unsigned bits = pfx_bits + ((args & 15) + 1) * 8;
return cs.have(bits) ? bits : 0;
}
int exec_dump_stack(VmState* st) {
VM_LOG(st) << "execute DUMPSTK";
Stack& stack = st->get_stack();
int d = stack.depth();
std::cerr << "#DEBUG#: stack(" << d << " values) : ";
if (d > 255) {
std::cerr << "... ";
d = 255;
}
for (int i = d; i > 0; i--) {
std::cerr << stack[i - 1].to_string() << " ";
}
std::cerr << std::endl;
return 0;
}
int exec_dump_value(VmState* st, unsigned arg) {
arg &= 15;
VM_LOG(st) << "execute DUMP s" << arg;
Stack& stack = st->get_stack();
if ((int)arg < stack.depth()) {
std::cerr << "#DEBUG#: s" << arg << " = " << stack[arg].to_string() << std::endl;
} else {
std::cerr << "#DEBUG#: s" << arg << " is absent" << std::endl;
}
return 0;
}
void register_debug_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
if (!vm_debug_enabled) {
cp0.insert(OpcodeInstr::mkfixedrange(0xfe00, 0xfef0, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
.insert(OpcodeInstr::mkext(0xfef, 12, 4, dump_dummy_debug_str, exec_dummy_debug_str, compute_len_debug_str));
} else {
// NB: all non-redefined opcodes in fe00..feff should be redirected to dummy debug definitions
cp0.insert(OpcodeInstr::mksimple(0xfe00, 16, "DUMPSTK", exec_dump_stack))
.insert(OpcodeInstr::mkfixedrange(0xfe01, 0xfe20, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
.insert(OpcodeInstr::mkfixed(0xfe2, 12, 4, instr::dump_1sr("DUMP"), exec_dump_value))
.insert(OpcodeInstr::mkfixedrange(0xfe30, 0xfef0, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
.insert(OpcodeInstr::mkext(0xfef, 12, 4, dump_dummy_debug_str, exec_dummy_debug_str, compute_len_debug_str));
}
}
} // namespace vm

29
crypto/vm/debugops.h Normal file
View file

@ -0,0 +1,29 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
extern bool vm_debug_enabled;
void register_debug_ops(OpcodeTable& cp0);
} // namespace vm

2647
crypto/vm/dict.cpp Normal file

File diff suppressed because it is too large Load diff

474
crypto/vm/dict.h Normal file
View file

@ -0,0 +1,474 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/bitstring.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/stack.hpp"
#include <functional>
namespace vm {
using td::BitSlice;
using td::Ref;
namespace dict {
struct LabelParser {
enum { chk_none = 0, chk_min = 1, chk_size = 2, chk_all = 3 };
Ref<CellSlice> remainder;
int l_offs;
int l_same;
int l_bits;
unsigned s_bits;
LabelParser(Ref<CellSlice> cs, int max_label_len, int auto_validate = chk_all);
LabelParser(Ref<Cell> cell, int max_label_len, int auto_validate = chk_all);
int is_valid() const {
return l_offs;
}
void validate() const;
void validate_simple(int n) const;
void validate_ext(int n) const;
bool is_prefix_of(td::ConstBitPtr key, int len) const;
bool has_prefix(td::ConstBitPtr key, int len) const;
int common_prefix_len(td::ConstBitPtr key, int len) const;
int extract_label_to(td::BitPtr to);
int copy_label_prefix_to(td::BitPtr to, int max_len) const;
td::ConstBitPtr bits() const {
return remainder->data_bits();
}
td::ConstBitPtr bits_end() const {
return bits() + l_bits;
}
void skip_label() {
remainder.write().advance(s_bits);
}
void clear() {
remainder.clear();
}
private:
bool parse_label(CellSlice& cs, int max_label_len);
};
struct AugmentationData {
virtual ~AugmentationData() = default;
virtual bool skip_extra(vm::CellSlice& cs) const = 0;
virtual bool eval_leaf(vm::CellBuilder& cb, vm::CellSlice& val_cs) const = 0;
virtual bool eval_fork(vm::CellBuilder& cb, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const = 0;
virtual bool eval_empty(vm::CellBuilder& cb) const = 0;
virtual bool check_leaf(vm::CellSlice& cs, vm::CellSlice& val_cs) const;
virtual bool check_fork(vm::CellSlice& cs, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const;
virtual bool check_empty(vm::CellSlice& cs) const;
virtual bool check_leaf_key_extra(vm::CellSlice& val_cs, vm::CellSlice& extra_cs, td::ConstBitPtr key,
int key_len) const {
return check_leaf(extra_cs, val_cs);
}
Ref<vm::CellSlice> extract_extra(vm::CellSlice& cs) const;
Ref<vm::CellSlice> extract_extra(Ref<vm::CellSlice> cs_ref) const;
bool extract_extra_to(vm::CellSlice& cs, Ref<vm::CellSlice>& extra_csr) const {
return (extra_csr = extract_extra(cs)).not_null();
}
bool extract_extra_to(Ref<vm::CellSlice> cs_ref, Ref<vm::CellSlice>& extra_csr) const {
return (extra_csr = extract_extra(std::move(cs_ref))).not_null();
}
bool extract_extra_to(vm::CellSlice& cs, vm::CellSlice& extra) const;
};
static inline bool store_cell_dict(vm::CellBuilder& cb, Ref<vm::Cell> dict_root) {
return dict_root.not_null() ? cb.store_long_bool(1, 1) && cb.store_ref_bool(std::move(dict_root))
: cb.store_long_bool(0, 1);
}
} // namespace dict
struct CombineError {}; // thrown by Dictionary::combine_with
struct CombineErrorValue {
int arg_;
};
struct DictNonEmpty {};
struct DictAdvance {};
class DictionaryBase {
protected:
mutable Ref<CellSlice> root;
Ref<Cell> root_cell;
int key_bits;
mutable int flags;
enum { f_valid = 1, f_root_cached = 2, f_invalid = 0x80 };
public:
enum class SetMode : int { Set = 3, Replace = 1, Add = 2 };
enum { max_key_bits = 1023, max_key_bytes = (max_key_bits + 7) / 8 };
typedef std::function<bool(CellBuilder&)> store_value_func_t;
DictionaryBase(int _n, bool validate = true);
DictionaryBase(Ref<CellSlice> _root, int _n, bool validate = true);
DictionaryBase(const CellSlice& root_cs, int _n, bool validate = true);
DictionaryBase(DictAdvance, CellSlice& root_cs, int _n, bool validate = true);
DictionaryBase(Ref<Cell> cell, int _n, bool validate = true);
DictionaryBase(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true);
DictionaryBase(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true);
virtual ~DictionaryBase() = default;
static Ref<Cell> construct_root_from(const CellSlice& root_node_cs);
Ref<CellSlice> get_root() const;
Ref<CellSlice> extract_root() &&;
Ref<Cell> get_root_cell() const {
return root_cell;
}
Ref<Cell> extract_root_cell() && {
return std::move(root_cell);
}
bool append_dict_to_bool(CellBuilder& cb) &&;
bool append_dict_to_bool(CellBuilder& cb) const &;
int get_key_bits() const {
return key_bits;
}
bool is_valid() const {
return flags & f_valid;
}
void reset() {
root.clear();
root_cell.clear();
flags = f_valid;
}
virtual bool validate();
void force_validate();
bool is_empty() const {
return root_cell.is_null();
}
static Ref<CellSlice> get_empty_dictionary();
protected:
bool init_root_for_nonempty(const CellSlice& cs);
bool invalidate() {
flags |= f_invalid;
return false;
}
bool compute_root() const;
static Ref<CellSlice> new_empty_dictionary();
void set_root_cell(Ref<Cell> cell) {
root_cell = std::move(cell);
flags &= ~f_root_cached;
}
};
class DictionaryFixed : public DictionaryBase {
public:
typedef std::function<int(vm::CellSlice&, td::ConstBitPtr, int)> filter_func_t;
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, Ref<CellSlice>)> simple_combine_func_t;
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr, int)> combine_func_t;
typedef std::function<bool(Ref<CellSlice>, td::ConstBitPtr, int)> foreach_func_t;
typedef std::function<bool(td::ConstBitPtr, int, Ref<CellSlice>, Ref<CellSlice>)> scan_diff_func_t;
DictionaryFixed(int _n, bool validate = true) : DictionaryBase(_n, validate) {
}
DictionaryFixed(Ref<CellSlice> _root, int _n, bool validate = true) : DictionaryBase(std::move(_root), _n, validate) {
}
DictionaryFixed(const CellSlice& root_cs, int _n, bool validate = true) : DictionaryBase(root_cs, _n, validate) {
}
DictionaryFixed(DictAdvance, CellSlice& root_cs, int _n, bool validate = true)
: DictionaryBase(DictAdvance(), root_cs, _n, validate) {
}
DictionaryFixed(Ref<Cell> cell, int _n, bool validate = true) : DictionaryBase(std::move(cell), _n, validate) {
}
DictionaryFixed(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true)
: DictionaryBase(DictNonEmpty(), std::move(_root), _n, validate) {
}
DictionaryFixed(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true)
: DictionaryBase(DictNonEmpty(), root_cs, _n, validate) {
}
static BitSlice integer_key(td::RefInt256 x, unsigned n, bool sgnd = true, unsigned char buffer[128] = 0,
bool quiet = false);
static bool integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet = false);
bool key_exists(td::ConstBitPtr key, int key_len);
bool int_key_exists(long long key);
bool uint_key_exists(unsigned long long key);
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
Ref<CellSlice> get_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max = false, bool invert_first = false);
Ref<CellSlice> extract_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max = false,
bool invert_first = false);
Ref<CellSlice> lookup_nearest_key(td::BitPtr key_buffer, int key_len, bool fetch_next = false, bool allow_eq = false,
bool invert_first = false);
bool has_common_prefix(td::ConstBitPtr prefix, int prefix_len);
int get_common_prefix(td::BitPtr buffer, unsigned buffer_len);
bool cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false);
Ref<vm::Cell> extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false);
bool check_for_each(const foreach_func_t& foreach_func, bool invert_first = false);
int filter(filter_func_t check);
bool combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode = 0);
bool combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode = 0);
bool combine_with(DictionaryFixed& dict2);
bool scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm = 0);
bool validate_check(const foreach_func_t& foreach_func, bool invert_first = false);
bool validate_all();
template <typename T>
bool key_exists(const T& key) {
return key_exists(key.bits(), key.size());
}
template <typename T>
Ref<CellSlice> lookup(const T& key) {
return lookup(key.bits(), key.size());
}
template <typename T>
Ref<CellSlice> lookup_delete(const T& key) {
return lookup_delete(key.bits(), key.size());
}
template <typename T>
Ref<CellSlice> get_minmax_key(T& key_buffer, bool fetch_max = false, bool invert_first = false) {
return get_minmax_key(key_buffer.bits(), key_buffer.size(), fetch_max, invert_first);
}
protected:
virtual int label_mode() const {
return dict::LabelParser::chk_all;
}
virtual Ref<Cell> finish_create_leaf(CellBuilder& cb, const CellSlice& value) const;
virtual Ref<Cell> finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const;
virtual bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const {
return true;
}
virtual bool check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const {
return true;
}
bool check_leaf(Ref<CellSlice> cs_ref, td::ConstBitPtr key, int key_len) const {
return check_leaf(cs_ref.write(), key, key_len);
}
bool check_fork_raw(Ref<CellSlice> cs_ref, int n) const;
private:
std::pair<Ref<CellSlice>, Ref<Cell>> dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key, int n) const;
Ref<CellSlice> dict_lookup_minmax(Ref<Cell> dict, td::BitPtr key_buffer, int n, int mode) const;
Ref<CellSlice> dict_lookup_nearest(Ref<Cell> dict, td::BitPtr key_buffer, int n, bool allow_eq, int mode) const;
std::pair<Ref<Cell>, bool> extract_prefix_subdict_internal(Ref<Cell> dict, td::ConstBitPtr prefix, int prefix_len,
bool remove_prefix = false) const;
bool dict_check_for_each(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
const foreach_func_t& foreach_func, bool invert_first = false) const;
std::pair<Ref<Cell>, int> dict_filter(Ref<Cell> dict, td::BitPtr key, int n, const filter_func_t& check_leaf) const;
Ref<Cell> dict_combine_with(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
const combine_func_t& combine_func, int mode = 0, int skip1 = 0, int skip2 = 0) const;
bool dict_scan_diff(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
const scan_diff_func_t& diff_func, int mode = 0, int skip1 = 0, int skip2 = 0) const;
bool dict_validate_check(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
const foreach_func_t& foreach_func, bool invert_first = false) const;
};
class Dictionary final : public DictionaryFixed {
public:
typedef std::function<bool(CellBuilder&, Ref<CellSlice>)> simple_map_func_t;
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, td::ConstBitPtr, int)> map_func_t;
Dictionary(int _n, bool validate = true) : DictionaryFixed(_n, validate) {
}
Dictionary(Ref<CellSlice> _root, int _n, bool validate = true) : DictionaryFixed(std::move(_root), _n, validate) {
}
Dictionary(const CellSlice& root_cs, int _n, bool validate = true) : DictionaryFixed(root_cs, _n, validate) {
}
Dictionary(DictAdvance, CellSlice& root_cs, int _n, bool validate = true)
: DictionaryFixed(DictAdvance(), root_cs, _n, validate) {
}
Dictionary(Ref<Cell> cell, int _n, bool validate = true) : DictionaryFixed(std::move(cell), _n, validate) {
}
Dictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true)
: DictionaryFixed(DictNonEmpty(), std::move(_root), _n, validate) {
}
Dictionary(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true)
: DictionaryFixed(DictNonEmpty(), root_cs, _n, validate) {
}
Ref<Cell> lookup_ref(td::ConstBitPtr key, int key_len);
Ref<Cell> lookup_delete_ref(td::ConstBitPtr key, int key_len);
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
bool set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
bool set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode = SetMode::Set);
bool set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& val_b, SetMode mode = SetMode::Set);
bool set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val, SetMode mode = SetMode::Set);
Ref<CellSlice> lookup_set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
Ref<Cell> lookup_set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
Ref<CellSlice> lookup_set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b,
SetMode mode = SetMode::Set);
Ref<CellSlice> lookup_set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val,
SetMode mode = SetMode::Set);
Ref<Cell> get_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max = false, bool invert_first = false);
Ref<Cell> extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max = false,
bool invert_first = false);
void map(const map_func_t& map_func);
void map(const simple_map_func_t& simple_map_func);
template <typename T>
Ref<Cell> lookup_ref(const T& key) {
return lookup_ref(key.bits(), key.size());
}
template <typename T>
Ref<Cell> lookup_delete_ref(const T& key) {
return lookup_delete_ref(key.bits(), key.size());
}
template <typename T>
bool set(const T& key, Ref<CellSlice> value, SetMode mode = SetMode::Set) {
return set(key.bits(), key.size(), std::move(value), mode);
}
template <typename T>
bool set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
return set_ref(key.bits(), key.size(), std::move(val_ref), mode);
}
template <typename T>
bool set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
return set_builder(key.bits(), key.size(), val_b, mode);
}
template <typename T>
bool set_builder(const T& key, Ref<vm::CellBuilder> val_ref, SetMode mode = SetMode::Set) {
return set_builder(key.bits(), key.size(), std::move(val_ref), mode);
}
template <typename T>
Ref<CellSlice> lookup_set(const T& key, Ref<CellSlice> value, SetMode mode = SetMode::Set) {
return lookup_set(key.bits(), key.size(), std::move(value), mode);
}
template <typename T>
Ref<Cell> lookup_set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
return lookup_set_ref(key.bits(), key.size(), std::move(val_ref), mode);
}
template <typename T>
Ref<CellSlice> lookup_set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
return lookup_set_builder(key.bits(), key.size(), val_b, mode);
}
template <typename T>
Ref<CellSlice> lookup_set_builder(const T& key, Ref<vm::CellBuilder> val_ref, SetMode mode = SetMode::Set) {
return lookup_set_builder(key.bits(), key.size(), std::move(val_ref), mode);
}
private:
bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const override {
return cs.empty_ext();
}
static Ref<Cell> extract_value_ref(Ref<CellSlice> cs);
std::pair<Ref<Cell>, int> dict_filter(Ref<Cell> dict, td::BitPtr key, int n, const filter_func_t& check_leaf) const;
};
class PrefixDictionary final : public DictionaryBase {
public:
PrefixDictionary(int _n, bool validate = true) : DictionaryBase(_n, validate) {
}
PrefixDictionary(Ref<CellSlice> _root, int _n, bool validate = true)
: DictionaryBase(std::move(_root), _n, validate) {
}
PrefixDictionary(Ref<Cell> cell, int _n, bool validate = true) : DictionaryBase(std::move(cell), _n, validate) {
}
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
std::pair<Ref<CellSlice>, int> lookup_prefix(td::ConstBitPtr key, int key_len);
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
bool set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode = SetMode::Set);
bool set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val, SetMode mode = SetMode::Set);
};
using dict::AugmentationData;
class AugmentedDictionary final : public DictionaryFixed {
const AugmentationData& aug;
public:
typedef std::function<bool(Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr, int)> foreach_extra_func_t;
// return value of traverse_func: < 0 = error, 0 = skip, 1 = visit only left, 2 = visit only right, 5 = visit right, then left, 6 = visit left, then right
// for leaf nodes, all >0 values mean accept and return node as the final result, 0 = skip (continue scanning)
typedef std::function<int(td::ConstBitPtr key_prefix, int key_pfx_len, Ref<CellSlice> extra, Ref<CellSlice> value)>
traverse_func_t;
AugmentedDictionary(int _n, const AugmentationData& _aug, bool validate = true);
AugmentedDictionary(Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate = true);
AugmentedDictionary(Ref<Cell> cell, int _n, const AugmentationData& _aug, bool validate = true);
AugmentedDictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate = true);
Ref<CellSlice> get_empty_dictionary() const;
Ref<CellSlice> get_root() const;
Ref<CellSlice> extract_root() &&;
bool append_dict_to_bool(CellBuilder& cb) &&;
bool append_dict_to_bool(CellBuilder& cb) const &;
Ref<CellSlice> get_root_extra() const;
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
Ref<Cell> lookup_ref(td::ConstBitPtr key, int key_len);
Ref<CellSlice> lookup_with_extra(td::ConstBitPtr key, int key_len);
std::pair<Ref<CellSlice>, Ref<CellSlice>> lookup_extra(td::ConstBitPtr key, int key_len);
std::pair<Ref<Cell>, Ref<CellSlice>> lookup_ref_extra(td::ConstBitPtr key, int key_len);
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
Ref<Cell> lookup_delete_ref(td::ConstBitPtr key, int key_len);
Ref<CellSlice> lookup_delete_with_extra(td::ConstBitPtr key, int key_len);
std::pair<Ref<CellSlice>, Ref<CellSlice>> lookup_delete_extra(td::ConstBitPtr key, int key_len);
std::pair<Ref<Cell>, Ref<CellSlice>> lookup_delete_ref_extra(td::ConstBitPtr key, int key_len);
bool set(td::ConstBitPtr key, int key_len, const CellSlice& value, SetMode mode = SetMode::Set);
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
bool set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
bool set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& value, SetMode mode = SetMode::Set);
bool check_for_each_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first = false);
std::pair<Ref<CellSlice>, Ref<CellSlice>> traverse_extra(td::BitPtr key_buffer, int key_len,
const traverse_func_t& traverse_node);
bool validate_check_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first = false);
bool validate() override;
template <typename T>
Ref<CellSlice> lookup(const T& key) {
return lookup(key.bits(), key.size());
}
template <typename T>
Ref<Cell> lookup_ref(const T& key) {
return lookup_ref(key.bits(), key.size());
}
template <typename T>
bool set(const T& key, Ref<CellSlice> val_ref, SetMode mode = SetMode::Set) {
return set(key.bits(), key.size(), std::move(val_ref), mode);
}
template <typename T>
bool set(const T& key, const CellSlice& value, SetMode mode = SetMode::Set) {
return set(key.bits(), key.size(), value, mode);
}
template <typename T>
bool set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
return set_ref(key.bits(), key.size(), std::move(val_ref), mode);
}
template <typename T>
bool set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
return set_builder(key.bits(), key.size(), val_b, mode);
}
template <typename T>
Ref<CellSlice> lookup_delete(const T& key) {
return lookup_delete(key.bits(), key.size());
}
template <typename T>
Ref<Cell> lookup_delete_ref(const T& key) {
return lookup_delete_ref(key.bits(), key.size());
}
Ref<CellSlice> extract_value(Ref<CellSlice> value_extra) const;
Ref<Cell> extract_value_ref(Ref<CellSlice> value_extra) const;
std::pair<Ref<CellSlice>, Ref<CellSlice>> decompose_value_extra(Ref<CellSlice> value_extra) const;
std::pair<Ref<Cell>, Ref<CellSlice>> decompose_value_ref_extra(Ref<CellSlice> value_extra) const;
private:
bool compute_root() const;
Ref<CellSlice> get_node_extra(Ref<Cell> cell_ref, int n) const;
bool check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const override;
bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const override;
Ref<Cell> finish_create_leaf(CellBuilder& cb, const CellSlice& value) const override;
Ref<Cell> finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const override;
std::pair<Ref<Cell>, bool> dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n, const CellSlice& value,
SetMode mode = SetMode::Set) const;
int label_mode() const override {
return dict::LabelParser::chk_size;
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> dict_traverse_extra(Ref<Cell> dict, td::BitPtr key_buffer, int n,
const traverse_func_t& traverse_node) const;
};
} // namespace vm

811
crypto/vm/dictops.cpp Normal file
View file

@ -0,0 +1,811 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <functional>
#include "vm/log.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
#include "common/bigint.hpp"
#include "common/refint.h"
#include "vm/dictops.h"
#include "vm/dict.h"
namespace vm {
template <typename T>
void push_dict(Stack& stack, T&& dict) {
stack.push_maybe_cell(std::move(dict).extract_root_cell());
}
template <typename T>
void push_dict(Stack& stack, const T& dict) {
stack.push_maybe_cell(dict.get_root_cell());
}
int exec_dict_empty(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICTEMPTY";
auto dict = stack.pop_cellslice();
if (!dict->have(1)) {
throw VmError{Excno::cell_und};
}
stack.push_smallint(~dict->prefetch_long(1));
return 0;
}
int exec_store_dict(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute STDICT";
stack.check_underflow(2);
auto cb = stack.pop_builder();
auto d = stack.pop_maybe_cell();
if (!cb.write().store_maybe_ref(std::move(d))) {
throw VmError{Excno::cell_ov};
}
stack.push_builder(std::move(cb));
return 0;
}
int dict_nonempty(const CellSlice& dict) {
if (!dict.have(1)) {
return -1;
}
int res = (int)dict.prefetch_ulong(1);
return dict.have_refs(res) ? res : -1;
}
int dict_nonempty_chk(const CellSlice& dict) {
int res = dict_nonempty(dict);
if (res < 0) {
throw VmError{Excno::cell_und};
}
return res;
}
int exec_skip_dict(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SKIPDICT\n";
auto dict = stack.pop_cellslice();
int res = dict_nonempty_chk(*dict);
dict.write().advance_ext(1, res);
stack.push_cellslice(std::move(dict));
return 0;
}
int exec_load_optref(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute LDOPTREF\n";
auto dict = stack.pop_cellslice();
int res = dict_nonempty_chk(*dict);
dict.write().advance(1);
if (res) {
auto cell = dict.write().fetch_ref();
stack.push_cellslice(std::move(dict));
stack.push_cell(std::move(cell));
} else {
stack.push_cellslice(std::move(dict));
}
stack.push_smallint(-res);
return 0;
}
int exec_preload_optref(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PLDOPTREF\n";
auto dict = stack.pop_cellslice();
int res = dict_nonempty_chk(*dict);
if (res) {
stack.push_cell(dict->prefetch_ref());
}
stack.push_smallint(-res);
return 0;
}
int exec_load_dict_slice(VmState* st, unsigned args) {
bool preload = args & 1, quiet = args & 2;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << (preload ? "P" : "") << "LDDICTS" << (quiet ? "Q\n" : "\n");
auto cs = stack.pop_cellslice();
int res = dict_nonempty(*cs);
if (res < 0) {
if (!quiet) {
throw VmError{Excno::cell_und};
}
if (!preload) {
stack.push_cellslice(std::move(cs));
}
} else {
if (preload) {
stack.push_cellslice(cs->prefetch_subslice(1, res));
} else {
stack.push_cellslice(cs.write().fetch_subslice(1, res));
stack.push_cellslice(std::move(cs));
}
}
if (quiet) {
stack.push_bool(res >= 0);
}
return 0;
}
int exec_load_dict(VmState* st, unsigned args) {
bool preload = args & 1, quiet = args & 2;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute " << (preload ? "P" : "") << "LDDICT" << (quiet ? "Q\n" : "\n");
auto cs = stack.pop_cellslice();
int res = dict_nonempty(*cs);
if (res < 0) {
if (!quiet) {
throw VmError{Excno::cell_und};
}
if (!preload) {
stack.push_cellslice(std::move(cs));
}
} else {
stack.push_maybe_cell(res ? cs->prefetch_ref() : Ref<Cell>{});
if (!preload) {
cs.write().advance_ext(1, res);
stack.push_cellslice(std::move(cs));
}
}
if (quiet) {
stack.push_bool(res >= 0);
}
return 0;
}
std::string dump_dictop(unsigned args, const char* name) {
std::ostringstream os{"DICT"};
if (args & 4) {
os << (args & 2 ? 'U' : 'I');
}
os << name;
if (args & 1) {
os << "REF";
}
return os.str();
}
std::string dump_dictop2(unsigned args, const char* name) {
std::ostringstream os{"DICT"};
if (args & 2) {
os << (args & 1 ? 'U' : 'I');
}
os << name;
return os.str();
}
std::string dump_subdictop2(unsigned args, const char* name) {
std::ostringstream os{"SUBDICT"};
if (args & 2) {
os << (args & 1 ? 'U' : 'I');
}
os << name;
return os.str();
}
int exec_dict_get(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << "GET" << (args & 1 ? "REF" : "");
stack.check_underflow(3);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 4) {
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer, true);
if (!key.is_valid()) {
stack.push_smallint(0);
return 0;
}
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
if (args & 1) {
auto value = dict.lookup_ref(key);
if (value.not_null()) {
stack.push_cell(std::move(value));
stack.push_smallint(-1);
} else {
stack.push_smallint(0);
}
} else {
auto value = dict.lookup(key);
if (value.not_null()) {
stack.push_cellslice(std::move(value));
stack.push_smallint(-1);
} else {
stack.push_smallint(0);
}
}
return 0;
}
int exec_dict_get_optref(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "GETOPTREF";
stack.check_underflow(3);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 2) {
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer, true);
if (!key.is_valid()) {
stack.push_null();
return 0;
}
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
stack.push_maybe_cell(dict.lookup_ref(key));
return 0;
}
int exec_dict_set(VmState* st, unsigned args, Dictionary::SetMode mode, const char* name, bool bld = false) {
args <<= (bld ? 1 : 0);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << name
<< (args & 1 ? "REF" : (bld ? "B" : ""));
stack.check_underflow(4);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 4) {
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
bool res;
if (bld) {
auto new_value = stack.pop_builder();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
res = dict.set_builder(key, std::move(new_value), mode);
} else if (!(args & 1)) {
auto new_value = stack.pop_cellslice();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
res = dict.set(key, std::move(new_value), mode);
} else {
auto new_value_ref = stack.pop_cell();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
res = dict.set_ref(key, std::move(new_value_ref), mode);
}
push_dict(stack, std::move(dict));
if (mode == Dictionary::SetMode::Set) {
st->ensure_throw(res);
} else {
stack.push_bool(res);
}
return 0;
}
int exec_dict_setget(VmState* st, unsigned args, Dictionary::SetMode mode, const char* name, bool bld = false) {
args <<= (bld ? 1 : 0);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << name
<< (args & 1 ? "REF\n" : (bld ? "B\n" : "\n"));
stack.check_underflow(4);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 4) {
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
bool ok_f = (mode != Dictionary::SetMode::Add);
if (bld) {
auto new_value = stack.pop_builder();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
auto res = dict.lookup_set_builder(key, std::move(new_value), mode);
push_dict(stack, std::move(dict));
if (res.not_null()) {
stack.push_cellslice(std::move(res));
stack.push_bool(ok_f);
} else {
stack.push_bool(!ok_f);
}
} else if (!(args & 1)) {
auto new_value = stack.pop_cellslice();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
auto res = dict.lookup_set(key, std::move(new_value), mode);
push_dict(stack, std::move(dict));
if (res.not_null()) {
stack.push_cellslice(std::move(res));
stack.push_bool(ok_f);
} else {
stack.push_bool(!ok_f);
}
} else {
auto new_value_ref = stack.pop_cell();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
auto res = dict.lookup_set_ref(key, std::move(new_value_ref), mode);
push_dict(stack, std::move(dict));
if (res.not_null()) {
stack.push_cell(std::move(res));
stack.push_bool(ok_f);
} else {
stack.push_bool(!ok_f);
}
}
return 0;
}
int exec_dict_delete(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "DEL\n";
stack.check_underflow(3);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 2) {
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer);
if (!key.is_valid()) {
push_dict(stack, std::move(dict));
stack.push_smallint(0);
return 0;
}
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
bool res = dict.lookup_delete(key).not_null();
push_dict(stack, std::move(dict));
stack.push_bool(res);
return 0;
}
int exec_dict_deleteget(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << "DELGET" << (args & 1 ? "REF\n" : "\n");
stack.check_underflow(3);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 4) {
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
if (!key.is_valid()) {
push_dict(stack, std::move(dict));
stack.push_smallint(0);
return 0;
}
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
if (!(args & 1)) {
auto res = dict.lookup_delete(key);
push_dict(stack, std::move(dict));
bool ok = res.not_null();
if (ok) {
stack.push_cellslice(std::move(res));
}
stack.push_bool(ok);
} else {
auto res = dict.lookup_delete_ref(key);
push_dict(stack, std::move(dict));
bool ok = res.not_null();
if (ok) {
stack.push_cell(std::move(res));
}
stack.push_bool(ok);
}
return 0;
}
int exec_dict_setget_optref(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "SETGETOPTREF";
stack.check_underflow(4);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 2) {
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer);
} else {
key = stack.pop_cellslice()->prefetch_bits(n);
}
auto new_value = stack.pop_maybe_cell();
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
}
Ref<vm::Cell> value;
if (new_value.not_null()) {
value = dict.lookup_set_ref(key, std::move(new_value));
} else {
value = dict.lookup_delete_ref(key);
}
push_dict(stack, std::move(dict));
stack.push_maybe_cell(std::move(value));
return 0;
}
int exec_dict_getmin(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << (args & 16 ? "REM" : "")
<< (args & 8 ? "MAX" : "MIN") << (args & 1 ? "REF\n" : "\n");
stack.check_underflow(2);
int n = stack.pop_smallint_range(args & 4 ? (args & 2 ? 256 : 257) : Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
unsigned char buffer[Dictionary::max_key_bytes];
bool flip_first = !(args & 2);
if (!(args & 1)) {
auto res = (args & 16) ? dict.extract_minmax_key(buffer, n, args & 8, flip_first)
: dict.get_minmax_key(buffer, n, args & 8, flip_first);
if (args & 16) {
push_dict(stack, std::move(dict));
}
if (res.is_null()) {
stack.push_bool(false);
return 0;
}
stack.push_cellslice(std::move(res));
} else {
auto res = (args & 16) ? dict.extract_minmax_key_ref(buffer, n, args & 8, flip_first)
: dict.get_minmax_key_ref(buffer, n, args & 8, flip_first);
if (args & 16) {
push_dict(stack, std::move(dict));
}
if (res.is_null()) {
stack.push_bool(false);
return 0;
}
stack.push_cell(std::move(res));
}
if (args & 4) {
td::RefInt256 x{true};
x.unique_write().import_bits(td::ConstBitPtr{buffer}, n, !(args & 2));
stack.push_int(std::move(x));
} else {
stack.push_cellslice(Ref<CellSlice>{true, CellBuilder().store_bits(td::ConstBitPtr{buffer}, n).finalize()});
}
stack.push_bool(true);
return 0;
}
std::string dump_dictop_getnear(CellSlice& cs, unsigned args) {
std::ostringstream os{"DICT"};
if (args & 8) {
os << (args & 4 ? 'U' : 'I');
}
os << "GET" << (args & 2 ? "PREV" : "NEXT") << (args & 1 ? "EQ" : "");
return os.str();
}
int exec_dict_getnear(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 8 ? (args & 4 ? "U" : "I") : "") << "GET" << (args & 2 ? "PREV" : "NEXT")
<< (args & 1 ? "EQ\n" : "\n");
stack.check_underflow(3);
int n = stack.pop_smallint_range(args & 8 ? (args & 4 ? 256 : 257) : Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
unsigned char buffer[Dictionary::max_key_bytes];
bool sgnd = !(args & 4), go_up = !(args & 2), allow_eq = args & 1;
if (!(args & 8)) {
auto key_hint = stack.pop_cellslice()->prefetch_bits(n);
if (!key_hint.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key hint"};
}
td::BitPtr{buffer}.copy_from(key_hint.bits(), n);
key_hint.forget();
auto res = dict.lookup_nearest_key(td::BitPtr{buffer}, n, go_up, allow_eq, false);
if (res.is_null()) {
stack.push_bool(false);
return 0;
}
stack.push_cellslice(std::move(res));
stack.push_cellslice(Ref<CellSlice>{true, CellBuilder().store_bits(td::ConstBitPtr{buffer}, n).finalize()});
} else {
auto key = stack.pop_int_finite();
Ref<CellSlice> res;
if (key->export_bits(td::BitPtr{buffer}, n, sgnd)) {
res = dict.lookup_nearest_key(buffer, n, go_up, allow_eq, sgnd);
} else if ((td::sgn(key) >= 0) ^ go_up) {
res = dict.get_minmax_key(buffer, n, !go_up, sgnd);
}
if (res.is_null()) {
stack.push_bool(false);
return 0;
}
stack.push_cellslice(std::move(res));
key.write().import_bits(td::ConstBitPtr{buffer}, n, sgnd);
stack.push_int(std::move(key));
}
stack.push_bool(true);
return 0;
}
int exec_pfx_dict_set(VmState* st, Dictionary::SetMode mode, const char* name) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PFXDICT" << name;
stack.check_underflow(3);
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
PrefixDictionary dict{stack.pop_maybe_cell(), n};
auto key_slice = stack.pop_cellslice();
auto new_value = stack.pop_cellslice();
bool res = dict.set(key_slice->data_bits(), key_slice->size(), std::move(new_value), mode);
push_dict(stack, std::move(dict));
stack.push_bool(res);
return 0;
}
int exec_pfx_dict_delete(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PFXDICTDEL\n";
stack.check_underflow(2);
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
PrefixDictionary dict{stack.pop_maybe_cell(), n};
auto key_slice = stack.pop_cellslice();
bool res = dict.lookup_delete(key_slice->data_bits(), key_slice->size()).not_null();
push_dict(stack, std::move(dict));
stack.push_bool(res);
return 0;
}
int exec_dict_get_exec(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DICT" << (args & 1 ? 'U' : 'I') << "GET" << (args & 2 ? "EXEC\n" : "JMP\n");
stack.check_underflow(3);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
unsigned char buffer[Dictionary::max_key_bytes];
dict.integer_key_simple(stack.pop_int(), n, !(args & 1), td::BitPtr{buffer});
auto value = dict.lookup(td::BitPtr{buffer}, n);
if (value.not_null()) {
Ref<OrdCont> cont{true, std::move(value), st->get_cp()};
return (args & 2) ? st->call(std::move(cont)) : st->jump(std::move(cont));
} else {
return 0;
}
}
std::string dump_dict_get_exec(CellSlice& cs, unsigned args) {
return std::string{"DICT"} + (args & 1 ? 'U' : 'I') + "GET" + (args & 2 ? "EXEC" : "JMP");
}
int exec_push_const_dict(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
if (!cs.have(pfx_bits)) {
throw VmError{Excno::inv_opcode, "not enough data bits for a DICTPUSHCONST instruction"};
}
if (!cs.have_refs(1)) {
throw VmError{Excno::inv_opcode, "not enough references for a DICTPUSHCONST instruction"};
}
Stack& stack = st->get_stack();
cs.advance(pfx_bits - 11);
auto slice = cs.fetch_subslice(1, 1);
int n = (int)cs.fetch_ulong(10);
VM_LOG(st) << "execute DICTPUSHCONST " << n << " (" << slice << ")";
stack.push_cell(slice->prefetch_ref());
stack.push_smallint(n);
return 0;
}
std::string dump_push_const_dict(CellSlice& cs, int pfx_bits, const char* name) {
if (!cs.have(pfx_bits, 1)) {
return "";
}
cs.advance(pfx_bits - 11);
auto slice = cs.fetch_subslice(1, 1);
int n = (int)cs.fetch_ulong(10);
std::ostringstream os{name};
os << ' ' << n << " (";
slice->dump_hex(os, false);
os << ')';
return os.str();
}
int compute_len_push_const_dict(const CellSlice& cs, unsigned args, int pfx_bits) {
if (!cs.have(pfx_bits, 1)) {
return 0;
}
return 0x10000 + pfx_bits;
}
int exec_pfx_dict_get(VmState* st, int op, const char* name_suff) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PFXDICTGET" << name_suff;
stack.check_underflow(3);
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
PrefixDictionary dict{stack.pop_maybe_cell(), n};
auto cs = stack.pop_cellslice();
auto res = dict.lookup_prefix(cs->data_bits(), cs->size());
if (res.first.is_null()) {
if (op & 1) {
throw VmError{Excno::cell_und, "cannot parse a prefix belonging to a given prefix code dictionary"};
}
stack.push_cellslice(std::move(cs));
if (!op) {
stack.push_bool(false);
}
return 0;
}
stack.push_cellslice(cs.write().fetch_subslice(res.second));
if (!(op & 2)) {
stack.push_cellslice(std::move(res.first));
}
stack.push_cellslice(std::move(cs));
if (!op) {
stack.push_bool(true);
return 0;
}
if (op == 1) {
return 0;
}
Ref<OrdCont> cont{true, std::move(res.first), st->get_cp()};
return op & 1 ? st->call(std::move(cont)) : st->jump(std::move(cont));
}
int exec_const_pfx_dict_switch(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
if (!cs.have(pfx_bits)) {
throw VmError{Excno::inv_opcode, "not enough data bits for a PFXDICTSWITCH instruction"};
}
if (!cs.have_refs(1)) {
throw VmError{Excno::inv_opcode, "not enough references for a PFXDICTSWITCH instruction"};
}
Stack& stack = st->get_stack();
cs.advance(pfx_bits - 11);
auto dict_slice = cs.fetch_subslice(1, 1);
int n = (int)cs.fetch_ulong(10);
VM_LOG(st) << "execute PFXDICTSWITCH " << n << " (" << dict_slice << ")";
PrefixDictionary dict{std::move(dict_slice), n};
auto cs1 = stack.pop_cellslice();
auto res = dict.lookup_prefix(cs1->data_bits(), cs1->size());
if (res.first.is_null()) {
stack.push_cellslice(std::move(cs1));
return 0;
} else {
stack.push_cellslice(cs1.write().fetch_subslice(res.second));
stack.push_cellslice(std::move(cs1));
Ref<OrdCont> cont{true, std::move(res.first), st->get_cp()};
return st->jump(std::move(cont));
}
}
int exec_subdict_get(VmState* st, unsigned args) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SUBDICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << (args & 4 ? "RP" : "") << "GET";
stack.check_underflow(4);
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
Dictionary dict{stack.pop_maybe_cell(), n};
int mk = (args & 2 ? (args & 1 ? 256 : 257) : Dictionary::max_key_bits);
int k = stack.pop_smallint_range(std::min(mk, n));
BitSlice key;
unsigned char buffer[Dictionary::max_key_bytes];
if (args & 2) {
key = dict.integer_key(stack.pop_int(), k, !(args & 1), buffer, true);
} else {
key = stack.pop_cellslice()->prefetch_bits(k);
}
if (!key.is_valid()) {
throw VmError{Excno::cell_und, "not enough bits for a dictionary key prefix"};
}
if (!dict.cut_prefix_subdict(key.bits(), k, args & 4)) {
throw VmError{Excno::dict_err, "cannot construct subdictionary by key prefix"};
}
push_dict(stack, std::move(dict));
return 0;
}
void register_dictionary_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xf400, 16, "STDICT", exec_store_dict))
.insert(OpcodeInstr::mksimple(0xf401, 16, "SKIPDICT", exec_skip_dict))
.insert(OpcodeInstr::mksimple(0xf402, 16, "LDDICTS", std::bind(exec_load_dict_slice, _1, 0)))
.insert(OpcodeInstr::mksimple(0xf403, 16, "PLDDICTS", std::bind(exec_load_dict_slice, _1, 1)))
.insert(OpcodeInstr::mksimple(0xf404, 16, "LDDICT", std::bind(exec_load_dict, _1, 0)))
.insert(OpcodeInstr::mksimple(0xf405, 16, "PLDDICT", std::bind(exec_load_dict, _1, 1)))
.insert(OpcodeInstr::mksimple(0xf406, 16, "LDDICTQ", std::bind(exec_load_dict, _1, 2)))
.insert(OpcodeInstr::mksimple(0xf407, 16, "PLDDICTQ", std::bind(exec_load_dict, _1, 3)))
.insert(OpcodeInstr::mkfixedrange(0xf40a, 0xf410, 16, 3, std::bind(dump_dictop, _2, "GET"), exec_dict_get))
.insert(OpcodeInstr::mkfixedrange(0xf412, 0xf418, 16, 3, std::bind(dump_dictop, _2, "SET"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Set, "SET", false)))
.insert(OpcodeInstr::mkfixedrange(0xf41a, 0xf420, 16, 3, std::bind(dump_dictop, _2, "SETGET"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Set, "SETGET", false)))
.insert(
OpcodeInstr::mkfixedrange(0xf422, 0xf428, 16, 3, std::bind(dump_dictop, _2, "REPLACE"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Replace, "REPLACE", false)))
.insert(OpcodeInstr::mkfixedrange(
0xf42a, 0xf430, 16, 3, std::bind(dump_dictop, _2, "REPLACEGET"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Replace, "REPLACEGET", false)))
.insert(OpcodeInstr::mkfixedrange(0xf432, 0xf438, 16, 3, std::bind(dump_dictop, _2, "ADD"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Add, "ADD", false)))
.insert(OpcodeInstr::mkfixedrange(0xf43a, 0xf440, 16, 3, std::bind(dump_dictop, _2, "ADDGET"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Add, "ADDGET", false)))
.insert(OpcodeInstr::mkfixedrange(0xf441, 0xf444, 16, 2, std::bind(dump_dictop2, _2, "SETB"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Set, "SET", true)))
.insert(OpcodeInstr::mkfixedrange(0xf445, 0xf448, 16, 2, std::bind(dump_dictop2, _2, "SETGETB"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Set, "SETGET", true)))
.insert(
OpcodeInstr::mkfixedrange(0xf449, 0xf44c, 16, 2, std::bind(dump_dictop2, _2, "REPLACEB"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Replace, "REPLACE", true)))
.insert(OpcodeInstr::mkfixedrange(
0xf44d, 0xf450, 16, 2, std::bind(dump_dictop2, _2, "REPLACEGETB"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Replace, "REPLACEGET", true)))
.insert(OpcodeInstr::mkfixedrange(0xf451, 0xf454, 16, 2, std::bind(dump_dictop2, _2, "ADDB"),
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Add, "ADD", true)))
.insert(OpcodeInstr::mkfixedrange(0xf455, 0xf458, 16, 2, std::bind(dump_dictop2, _2, "ADDGETB"),
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Add, "ADDGET", true)))
.insert(OpcodeInstr::mkfixedrange(0xf459, 0xf45c, 16, 2, std::bind(dump_dictop2, _2, "DEL"), exec_dict_delete))
.insert(
OpcodeInstr::mkfixedrange(0xf462, 0xf468, 16, 3, std::bind(dump_dictop, _2, "DELGET"), exec_dict_deleteget))
.insert(OpcodeInstr::mkfixedrange(0xf469, 0xf46c, 16, 2, std::bind(dump_dictop2, _2, "GETOPTREF"),
exec_dict_get_optref))
.insert(OpcodeInstr::mkfixedrange(0xf46d, 0xf470, 16, 2, std::bind(dump_dictop2, _2, "SETGETOPTREF"),
exec_dict_setget_optref))
.insert(OpcodeInstr::mksimple(0xf470, 16, "PFXDICTSET",
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Set, "SET")))
.insert(OpcodeInstr::mksimple(0xf471, 16, "PFXDICTREPLACE",
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Replace, "REPLACE")))
.insert(OpcodeInstr::mksimple(0xf472, 16, "PFXDICTADD",
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Add, "ADD")))
.insert(OpcodeInstr::mksimple(0xf473, 16, "PFXDICTDEL", exec_pfx_dict_delete))
.insert(OpcodeInstr::mkfixedrange(0xf474, 0xf480, 16, 4, dump_dictop_getnear, exec_dict_getnear))
.insert(OpcodeInstr::mkfixedrange(0xf482, 0xf488, 16, 5, std::bind(dump_dictop, _2, "MIN"), exec_dict_getmin))
.insert(OpcodeInstr::mkfixedrange(0xf48a, 0xf490, 16, 5, std::bind(dump_dictop, _2, "MAX"), exec_dict_getmin))
.insert(OpcodeInstr::mkfixedrange(0xf492, 0xf498, 16, 5, std::bind(dump_dictop, _2, "REMMIN"), exec_dict_getmin))
.insert(OpcodeInstr::mkfixedrange(0xf49a, 0xf4a0, 16, 5, std::bind(dump_dictop, _2, "REMMAX"), exec_dict_getmin))
.insert(OpcodeInstr::mkfixed(0xf4a0 >> 2, 14, 2, dump_dict_get_exec, exec_dict_get_exec))
.insert(OpcodeInstr::mkextrange(0xf4a400, 0xf4a800, 24, 11,
std::bind(dump_push_const_dict, _1, _3, "DICTPUSHCONST"), exec_push_const_dict,
compute_len_push_const_dict))
.insert(OpcodeInstr::mksimple(0xf4a8, 16, "PFXDICTGETQ", std::bind(exec_pfx_dict_get, _1, 0, "Q")))
.insert(OpcodeInstr::mksimple(0xf4a9, 16, "PFXDICTGET", std::bind(exec_pfx_dict_get, _1, 1, "")))
.insert(OpcodeInstr::mksimple(0xf4aa, 16, "PFXDICTGETJMP", std::bind(exec_pfx_dict_get, _1, 2, "JMP")))
.insert(OpcodeInstr::mksimple(0xf4ab, 16, "PFXDICTGETEXEC", std::bind(exec_pfx_dict_get, _1, 3, "EXEC")))
.insert(OpcodeInstr::mkextrange(0xf4ac00, 0xf4b000, 24, 11,
std::bind(dump_push_const_dict, _1, _3, "PFXDICTSWITCH"),
exec_const_pfx_dict_switch, compute_len_push_const_dict))
.insert(OpcodeInstr::mkfixedrange(0xf4b1, 0xf4b4, 16, 3, std::bind(dump_subdictop2, _2, "GET"), exec_subdict_get))
.insert(
OpcodeInstr::mkfixedrange(0xf4b5, 0xf4b8, 16, 3, std::bind(dump_subdictop2, _2, "RPGET"), exec_subdict_get));
}
} // namespace vm

27
crypto/vm/dictops.h Normal file
View file

@ -0,0 +1,27 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_dictionary_ops(OpcodeTable& cp0);
} // namespace vm

71
crypto/vm/dispatch.cpp Normal file
View file

@ -0,0 +1,71 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/dispatch.h"
#include "vm/excno.hpp"
#include <cassert>
#include <map>
#include <mutex>
namespace vm {
namespace {
std::mutex dispatch_tables_mutex;
std::map<int, const DispatchTable*> dispatch_tables;
} // namespace
DummyDispatchTable dummy_dispatch_table;
bool DispatchTable::register_table(Codepage _cp, const DispatchTable& dt) {
assert(dt.is_final());
int cp = (int)_cp;
if (cp < -0x8000 || cp >= 0x8000 || cp == -1) {
return false;
} else {
std::lock_guard<std::mutex> guard(dispatch_tables_mutex);
return dispatch_tables.emplace(cp, &dt).second;
}
}
bool DispatchTable::register_table(Codepage cp) const {
return register_table(cp, *this);
}
const DispatchTable* DispatchTable::get_table(Codepage cp) {
return get_table((int)cp);
}
const DispatchTable* DispatchTable::get_table(int cp) {
std::lock_guard<std::mutex> guard(dispatch_tables_mutex);
auto entry = dispatch_tables.find(cp);
return entry != dispatch_tables.end() ? entry->second : 0;
}
int DummyDispatchTable::dispatch(VmState* st, CellSlice& cs) const {
throw VmError{Excno::inv_opcode, "empty opcode table"};
}
std::string DummyDispatchTable::dump_instr(CellSlice& cs) const {
return "";
}
int DummyDispatchTable::instr_len(const CellSlice& cs) const {
return 0;
}
} // namespace vm

62
crypto/vm/dispatch.h Normal file
View file

@ -0,0 +1,62 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <string>
namespace vm {
class VmState;
class CellSlice;
enum class Codepage { test_cp = 0 };
class DispatchTable {
public:
DispatchTable() = default;
virtual ~DispatchTable() = default;
virtual int dispatch(VmState* st, CellSlice& cs) const = 0;
virtual std::string dump_instr(CellSlice& cs) const = 0;
virtual int instr_len(const CellSlice& cs) const = 0;
virtual DispatchTable* finalize() = 0;
virtual bool is_final() const = 0;
static const DispatchTable* get_table(Codepage cp);
static const DispatchTable* get_table(int cp);
static bool register_table(Codepage cp, const DispatchTable& dt);
bool register_table(Codepage cp) const;
};
class DummyDispatchTable : public DispatchTable {
public:
DummyDispatchTable() : DispatchTable() {
}
~DummyDispatchTable() override = default;
int dispatch(VmState* st, CellSlice& cs) const override;
std::string dump_instr(CellSlice& cs) const override;
int instr_len(const CellSlice& cs) const override;
DispatchTable* finalize() override {
return this;
}
bool is_final() const override {
return true;
}
};
extern DummyDispatchTable dummy_dispatch_table;
} // namespace vm

98
crypto/vm/excno.hpp Normal file
View file

@ -0,0 +1,98 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
enum class Excno : int {
none = 0,
alt = 1,
stk_und = 2,
stk_ov = 3,
int_ov = 4,
range_chk = 5,
inv_opcode = 6,
type_chk = 7,
cell_ov = 8,
cell_und = 9,
dict_err = 10,
unknown = 11,
fatal = 12,
out_of_gas = 13,
virt_err = 14,
total
};
const char* get_exception_msg(Excno exc_no);
class VmError {
Excno exc_no;
const char* msg;
long long arg;
public:
VmError(Excno _excno, const char* _msg) : exc_no(_excno), msg(_msg), arg(0) {
}
VmError(Excno _excno) : exc_no(_excno), msg(0), arg(0) {
}
VmError(Excno _excno, const char* _msg, long long _arg) : exc_no(_excno), msg(_msg), arg(_arg) {
}
int get_errno() const {
return static_cast<int>(exc_no);
}
const char* get_msg() const {
return msg ? msg : get_exception_msg(exc_no);
}
long long get_arg() const {
return arg;
}
};
struct VmNoGas {
VmNoGas() = default;
int get_errno() const {
return static_cast<int>(Excno::out_of_gas);
}
const char* get_msg() const {
return "out of gas";
}
operator VmError() const {
return VmError{Excno::out_of_gas, "out of gas"};
}
};
struct VmVirtError {
int virtualization{0};
VmVirtError() = default;
VmVirtError(int virtualization) : virtualization(virtualization) {
}
int get_errno() const {
return static_cast<int>(Excno::virt_err);
}
const char* get_msg() const {
return "prunned branch";
}
operator VmError() const {
return VmError{Excno::virt_err, "prunned branch", virtualization};
}
};
struct VmFatal {};
} // namespace vm

503
crypto/vm/fmt.hpp Normal file
View file

@ -0,0 +1,503 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refint.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/cellparse.hpp"
#include <type_traits>
namespace vm {
namespace fmt {
// main idea: use cs >> i(32,x) >> ... or cs >> i32(x) to deserialize integers, instead of cs.write().fetch_long(32, true)
// and cb << i(32,x+y) or cb << i32(x+y) will serialize 32-bit integers
// i, u, i16, u16, i32, u32, ub=u1, ib=i1 for integers
template <bool S, class T, bool C = true>
class ConstInt {
int bits;
T value;
public:
ConstInt(int _bits, T _val) : bits(_bits), value(_val) {
}
bool serialize(CellBuilder& cb) const {
if (C) {
return (S ? cb.store_long_rchk_bool(value, bits) : cb.store_ulong_rchk_bool(value, bits));
} else {
return cb.store_long_bool(value, bits);
}
}
bool deserialize(CellSlice& cs) const {
if (S) {
long long x;
return cs.fetch_long_bool(bits, x) && x == value;
} else {
unsigned long long x;
return cs.fetch_ulong_bool(bits, x) && x == value;
}
}
};
template <bool S, class T, bool C = true>
class Int {
int bits;
T& value;
public:
Int(int _bits, T& _val) : bits(_bits), value(_val) {
}
bool deserialize(CellSlice& cs) const {
if (S) {
long long x;
if (cs.fetch_long_bool(bits, x)) {
value = static_cast<T>(x);
return true;
}
} else {
unsigned long long x;
if (cs.fetch_ulong_bool(bits, x)) {
value = static_cast<T>(x);
return true;
}
}
return false;
}
bool serialize(CellBuilder& cb) const {
if (C) {
return S ? cb.store_long_rchk_bool(value, bits) : cb.store_ulong_rchk_bool(value, bits);
} else {
return cb.store_long_bool(value, bits);
}
}
};
template <bool S, class T>
class PrefetchInt {
int bits;
T& value;
public:
PrefetchInt(int _bits, T& _val) : bits(_bits), value(_val) {
}
bool deserialize(CellSlice& cs) const {
if (S) {
long long x;
if (cs.prefetch_long_bool(bits, x)) {
value = static_cast<T>(x);
return true;
}
} else {
unsigned long long x;
if (cs.prefetch_ulong_bool(bits, x)) {
value = static_cast<T>(x);
return true;
}
}
return false;
}
};
template <bool S>
class ConstRefInt {
int bits;
const td::RefInt256& value;
public:
ConstRefInt(int _bits, const td::RefInt256& _val) : bits(_bits), value(_val) {
}
bool serialize(CellBuilder& cb) const {
return value.not_null() && cb.store_int256_bool(*value, bits, S);
}
};
template <bool S>
class ConstRefIntVal {
int bits;
td::RefInt256 value;
public:
ConstRefIntVal(int _bits, const td::RefInt256& _val) : bits(_bits), value(_val) {
}
ConstRefIntVal(int _bits, td::RefInt256&& _val) : bits(_bits), value(std::move(_val)) {
}
bool serialize(CellBuilder& cb) const {
return value.not_null() && cb.store_int256_bool(*value, bits, S);
}
};
template <bool S>
class ConstBigInt {
int bits;
const td::BigInt256& value;
public:
ConstBigInt(int _bits, const td::BigInt256& _val) : bits(_bits), value(_val) {
}
bool serialize(CellBuilder& cb) const {
return cb.store_int256_bool(value, bits, S);
}
};
template <bool S>
class RefInt {
int bits;
td::RefInt256& value;
public:
RefInt(int _bits, td::RefInt256& _val) : bits(_bits), value(_val) {
}
bool deserialize(CellSlice& cs) const {
value = cs.fetch_int256(bits, S);
return value.not_null();
}
bool serialize(CellBuilder& cb) const {
return value.not_null() && cb.store_int256_bool(*value, bits, S);
}
};
inline ConstRefInt<true> i(int l, const td::RefInt256& val) {
return {l, val};
}
inline ConstRefIntVal<true> i(int l, td::RefInt256&& val) {
return {l, std::move(val)};
}
inline ConstBigInt<true> i(int l, const td::BigInt256& val) {
return {l, val};
}
inline RefInt<true> i(int l, td::RefInt256& val) {
return {l, val};
}
inline ConstRefInt<false> u(int l, const td::RefInt256& val) {
return {l, val};
}
inline ConstRefIntVal<false> u(int l, td::RefInt256&& val) {
return {l, std::move(val)};
}
inline ConstBigInt<false> u(int l, const td::BigInt256& val) {
return {l, val};
}
inline RefInt<false> u(int l, td::RefInt256& val) {
return {l, val};
}
template <class T>
const ConstInt<true, T> i(int l, const T& val) {
return {l, val};
}
template <class T>
Int<true, T> i(int l, T& val) {
return {l, val};
}
template <class T>
PrefetchInt<true, T> pi(int l, T& val) {
return {l, val};
}
template <class T>
const ConstInt<false, T> u(int l, const T& val) {
return {l, val};
}
template <class T>
Int<false, T> u(int l, T& val) {
return {l, val};
}
template <class T>
PrefetchInt<false, T> pu(int l, T& val) {
return {l, val};
}
template <class T>
const ConstInt<true, T, false> iw(int l, const T& val) {
return {l, val};
}
template <class T>
Int<true, T, false> iw(int l, T& val) {
return {l, val};
}
inline ConstInt<true, bool> ib(bool flag) {
return {1, flag};
}
template <class T>
Int<true, T> ib(T& val) {
return {1, val};
}
template <class T>
PrefetchInt<true, T> pib(T& val) {
return {1, val};
}
inline ConstInt<false, bool> ub(bool flag) {
return {1, flag};
}
template <class T>
Int<false, T> ub(T& val) {
return {1, val};
}
template <class T>
PrefetchInt<false, T> pub(T& val) {
return {1, val};
}
inline ConstInt<true, signed char> i8(signed char val) {
return {8, val};
}
template <class T>
Int<true, T> i8(T& val) {
return {8, val};
}
inline ConstInt<false, unsigned char> u8(unsigned char val) {
return {8, val};
}
template <class T>
Int<false, T> u8(T& val) {
return {8, val};
}
inline ConstInt<true, short> i16(short val) {
return {16, val};
}
template <class T>
Int<true, T> i16(T& val) {
static_assert(sizeof(T) >= 2, "i16 needs at least 16-bit integer variable as a result");
return {16, val};
}
inline ConstInt<false, unsigned short> u16(unsigned short val) {
return {16, val};
}
template <class T>
Int<false, T> u16(T& val) {
static_assert(sizeof(T) >= 2, "u16 needs at least 16-bit integer variable as a result");
return {16, val};
}
template <class T>
const ConstInt<true, T> i32(const T& val) {
return {32, val};
}
template <class T>
Int<true, T> i32(T& val) {
static_assert(sizeof(T) >= 4, "i32 needs at least 32-bit integer variable as a result");
return {32, val};
}
template <class T>
PrefetchInt<true, T> pi32(T& val) {
static_assert(sizeof(T) >= 4, "pi32 needs at least 32-bit integer variable as a result");
return {32, val};
}
template <class T>
const ConstInt<false, unsigned> u32(const T& val) {
return {32, val};
}
template <class T>
Int<false, T> u32(T& val) {
static_assert(sizeof(T) >= 4, "u32 needs at least 32-bit integer variable as a result");
return {32, val};
}
template <class T>
PrefetchInt<false, T> pu32(T& val) {
static_assert(sizeof(T) >= 4, "pu32 needs at least 32-bit integer variable as a result");
return {32, val};
}
template <class T>
const ConstInt<true, T> i64(const T& val) {
return {64, val};
}
template <class T>
Int<true, T> i64(T& val) {
static_assert(sizeof(T) >= 8, "i64 needs 64-bit integer variable as a result");
return {64, val};
}
template <class T>
const ConstInt<false, T> u64(const T& val) {
return {64, val};
}
template <class T>
Int<false, T> u64(T& val) {
static_assert(sizeof(T) >= 8, "u64 needs 64-bit integer variable as a result");
return {64, val};
}
/*
*
* non-integer types
*
*/
// cr(Ref<Cell>& cell_ref) for (de)serializing cell references
class ConstCellRef {
const td::Ref<vm::Cell>& value;
public:
ConstCellRef(const td::Ref<vm::Cell>& _val) : value(_val) {
}
bool serialize(CellBuilder& cb) const {
return cb.store_ref_bool(value);
}
};
class ConstCellRefVal {
td::Ref<vm::Cell> value;
public:
ConstCellRefVal(const td::Ref<vm::Cell>& _val) : value(_val) {
}
ConstCellRefVal(td::Ref<vm::Cell>&& _val) : value(std::move(_val)) {
}
bool serialize(CellBuilder& cb) const {
return cb.store_ref_bool(std::move(value));
}
};
class CellRefFmt {
td::Ref<vm::Cell>& value;
public:
CellRefFmt(td::Ref<vm::Cell>& _val) : value(_val) {
}
bool deserialize(CellSlice& cs) const {
value = cs.fetch_ref();
return value.not_null();
}
};
inline ConstCellRef cr(const td::Ref<vm::Cell>& val) {
return {val};
}
inline ConstCellRefVal cr(td::Ref<vm::Cell>&& val) {
return {std::move(val)};
}
inline CellRefFmt cr(td::Ref<vm::Cell>& val) {
return {val};
}
// skip(n) will skip n bits
class SkipFmt {
int bits;
public:
explicit SkipFmt(int _bits) : bits(_bits) {
}
bool deserialize(CellSlice& cs) const {
return cs.advance(bits);
}
};
inline SkipFmt skip(int bits) {
return SkipFmt{bits};
}
// end will throw an exception if any bits or references remain, or if a previous operation failed
// ends similar, but checks only bits
class ChkEnd {
public:
explicit ChkEnd() = default;
bool deserialize(CellSlice& cs) const {
return (cs.empty() && !cs.size_refs());
}
};
class ChkEndS {
public:
explicit ChkEndS() = default;
bool deserialize(CellSlice& cs) const {
return cs.empty();
}
};
template <class Cond>
class Chk {
Cond cond;
public:
template <typename... Args>
explicit constexpr Chk(Args... args) : cond(args...){};
bool deserialize_ext(CellSlice& cs, bool state) const {
if (!state || !cond.deserialize(cs)) {
cs.error();
}
return true;
}
};
class ChkOk {
public:
explicit ChkOk() = default;
bool deserialize_ext(CellSlice& cs, bool state) const {
if (!state) {
cs.error();
}
return true;
}
};
constexpr ChkEnd end = ChkEnd{};
constexpr ChkEndS ends = ChkEndS{};
constexpr Chk<ChkEnd> okend = Chk<ChkEnd>{};
constexpr Chk<ChkEndS> okends = Chk<ChkEndS>{};
constexpr Chk<ChkEndS> oke = Chk<ChkEndS>{};
constexpr ChkOk ok = ChkOk{};
inline ::vm::CellParser parser(CellSlice& cs) {
return ::vm::CellParser{cs};
}
} // namespace fmt
} // namespace vm

53
crypto/vm/log.h Normal file
View file

@ -0,0 +1,53 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/logging.h"
#define VM_LOG_IMPL(st, mask) \
LOG_IMPL_FULL(get_log_interface(st), get_log_options(st), DEBUG, VERBOSITY_NAME(DEBUG), \
(get_log_mask(st) & mask) != 0, "")
#define VM_LOG(st) VM_LOG_IMPL(st, 1)
#define VM_LOG_MASK(st, mask) VM_LOG_IMPL(st, mask)
namespace vm {
struct VmLog {
td::LogInterface *log_interface{td::log_interface};
td::LogOptions log_options{td::log_options};
enum { DumpStack = 2 };
int log_mask{1};
};
template <class State>
td::LogInterface &get_log_interface(State *st) {
return st ? *st->get_log().log_interface : *::td::log_interface;
}
template <class State>
auto get_log_options(State *st) {
return st ? st->get_log().log_options : ::td::log_options;
}
template <class State>
auto get_log_mask(State *st) {
return st ? st->get_log().log_mask : 1;
}
} // namespace vm

450
crypto/vm/opctable.cpp Normal file
View file

@ -0,0 +1,450 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <cassert>
#include <iterator>
#include "vm/opctable.h"
#include "vm/cellslice.h"
#include "vm/excno.hpp"
#include "vm/continuation.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <functional>
#include "td/utils/format.h"
namespace vm {
DispatchTable* OpcodeTable::finalize() {
if (final) {
return this;
}
instruction_list.clear();
instruction_list.reserve(instructions.size() * 2 + 1);
unsigned upto = 0;
for (const auto& x : instructions) {
auto range = x.second->get_opcode_range();
assert(range.first == x.first);
assert(range.first < range.second);
assert(range.first >= upto);
assert(range.second <= top_opcode);
if (range.first > upto) {
instruction_list.emplace_back(upto, new OpcodeInstrDummy{upto, range.first});
}
instruction_list.emplace_back(x);
upto = range.second;
}
if (upto < top_opcode) {
instruction_list.emplace_back(upto, new OpcodeInstrDummy{upto, top_opcode});
}
instruction_list.shrink_to_fit();
final = true;
return this;
}
OpcodeTable& OpcodeTable::insert(const OpcodeInstr* instr) {
LOG_IF(FATAL, !insert_bool(instr)) << td::format::lambda([&](auto& sb) {
sb << "cannot insert instruction into table " << name << ": ";
if (!instr) {
sb << "instruction is null";
} else if (final) {
sb << "instruction table already finalized";
} else {
auto range = instr->get_opcode_range();
sb << "opcode range " << td::format::as_hex(range.first) << ".." << td::format::as_hex(range.second - 1)
<< " already occupied or invalid";
}
});
return *this;
}
bool OpcodeTable::insert_bool(const OpcodeInstr* instr) {
if (!instr || final) {
return false;
}
auto range = instr->get_opcode_range();
assert(range.first < range.second);
assert(range.second <= top_opcode);
auto it = instructions.lower_bound(range.first);
if (it != instructions.end() && it->first < range.second) {
return false;
}
if (it != instructions.begin()) {
auto prev_range = std::prev(it)->second->get_opcode_range();
assert(prev_range.first < prev_range.second);
assert(prev_range.first == std::prev(it)->first);
if (prev_range.second > range.first) {
return false;
}
}
instructions.emplace_hint(it, range.first, instr);
return true;
}
const OpcodeInstr* OpcodeTable::lookup_instr(unsigned opcode, unsigned bits) const {
std::size_t i = 0, j = instruction_list.size();
assert(j);
while (j - i > 1) {
auto k = ((j + i) >> 1);
if (instruction_list[k].first <= opcode) {
i = k;
} else {
j = k;
}
}
return instruction_list[i].second;
}
const OpcodeInstr* OpcodeTable::lookup_instr(const CellSlice& cs, unsigned& opcode, unsigned& bits) const {
bits = max_opcode_bits;
unsigned long long prefetch = cs.prefetch_ulong_top(bits);
opcode = (unsigned)(prefetch >> (64 - max_opcode_bits));
opcode &= (static_cast<int32_t>(static_cast<td::uint32>(-1) << max_opcode_bits) >> bits);
return lookup_instr(opcode, bits);
}
int OpcodeTable::dispatch(VmState* st, CellSlice& cs) const {
assert(final);
unsigned bits, opcode;
auto instr = lookup_instr(cs, opcode, bits);
//std::cerr << "lookup_instr: cs.size()=" << cs.size() << "; bits=" << bits << "; opcode=" << std::setw(6) << std::setfill('0') << std::hex << opcode << std::dec << std::endl;
return instr->dispatch(st, cs, opcode, bits);
}
std::string OpcodeTable::dump_instr(CellSlice& cs) const {
assert(final);
unsigned bits, opcode;
auto instr = lookup_instr(cs, opcode, bits);
return instr->dump(cs, opcode, bits);
}
int OpcodeTable::instr_len(const CellSlice& cs) const {
assert(final);
unsigned bits, opcode;
auto instr = lookup_instr(cs, opcode, bits);
return instr->instr_len(cs, opcode, bits);
}
OpcodeInstr::OpcodeInstr(unsigned _opcode, unsigned _bits, bool)
: min_opcode(_opcode << (max_opcode_bits - _bits)), max_opcode((_opcode + 1) << (max_opcode_bits - _bits)) {
assert(_opcode < (1U << _bits) && _bits <= max_opcode_bits);
}
int OpcodeInstrDummy::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
st->consume_gas(gas_per_instr);
throw VmError{Excno::inv_opcode, "invalid opcode", opcode};
}
std::string OpcodeInstr::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
return "";
}
int OpcodeInstr::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
return 0;
}
OpcodeInstrSimple::OpcodeInstrSimple(unsigned opcode, unsigned _opc_bits, std::string _name, exec_instr_func_t exec)
: OpcodeInstr(opcode, _opc_bits, false)
, opc_bits(static_cast<unsigned char>(_opc_bits))
, name(_name)
, exec_instr(exec) {
}
int OpcodeInstrSimple::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
st->consume_gas(gas_per_instr + opc_bits * gas_per_bit);
if (bits < opc_bits) {
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
}
cs.advance(opc_bits);
return exec_instr(st, cs, opcode >> (max_opcode_bits - opc_bits), opc_bits);
}
std::string OpcodeInstrSimple::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < opc_bits) {
return "";
}
cs.advance(opc_bits);
return name;
}
int OpcodeInstrSimple::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < opc_bits) {
return 0;
} else {
return opc_bits;
}
}
OpcodeInstrSimplest::OpcodeInstrSimplest(unsigned opcode, unsigned _opc_bits, std::string _name,
exec_simple_instr_func_t exec)
: OpcodeInstr(opcode, _opc_bits, false)
, opc_bits(static_cast<unsigned char>(_opc_bits))
, name(_name)
, exec_instr(exec) {
}
int OpcodeInstrSimplest::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
st->consume_gas(gas_per_instr + opc_bits * gas_per_bit);
if (bits < opc_bits) {
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
}
cs.advance(opc_bits);
return exec_instr(st);
}
std::string OpcodeInstrSimplest::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < opc_bits) {
return "";
}
cs.advance(opc_bits);
return name;
}
int OpcodeInstrSimplest::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < opc_bits) {
return 0;
} else {
return opc_bits;
}
}
OpcodeInstrFixed::OpcodeInstrFixed(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_arg_instr_func_t dump,
exec_arg_instr_func_t exec)
: OpcodeInstr(opcode, _opc_bits, false)
, opc_bits(static_cast<unsigned char>(_opc_bits))
, tot_bits(static_cast<unsigned char>(_opc_bits + _arg_bits))
, dump_instr(dump)
, exec_instr(exec) {
assert(_arg_bits <= max_opcode_bits && _opc_bits <= max_opcode_bits && _arg_bits + _opc_bits <= max_opcode_bits);
}
OpcodeInstrFixed::OpcodeInstrFixed(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec)
: OpcodeInstr(opcode_min << (max_opcode_bits - _tot_bits), opcode_max << (max_opcode_bits - _tot_bits))
, opc_bits(static_cast<unsigned char>(_tot_bits - _arg_bits))
, tot_bits(static_cast<unsigned char>(_tot_bits))
, dump_instr(dump)
, exec_instr(exec) {
assert(_arg_bits <= _tot_bits && _tot_bits <= max_opcode_bits);
assert(opcode_min < opcode_max && opcode_max <= (1U << _tot_bits));
}
int OpcodeInstrFixed::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
st->consume_gas(gas_per_instr + tot_bits * gas_per_bit);
if (bits < tot_bits) {
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
}
cs.advance(tot_bits);
return exec_instr(st, opcode >> (max_opcode_bits - tot_bits));
}
std::string OpcodeInstrFixed::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < tot_bits) {
return "";
}
cs.advance(tot_bits);
return dump_instr(cs, opcode >> (max_opcode_bits - tot_bits));
}
int OpcodeInstrFixed::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < tot_bits) {
return 0;
} else {
return tot_bits;
}
}
OpcodeInstrExt::OpcodeInstrExt(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_instr_func_t dump,
exec_instr_func_t exec, compute_instr_len_func_t comp_len)
: OpcodeInstr(opcode, _opc_bits, false)
, opc_bits(static_cast<unsigned char>(_opc_bits))
, tot_bits(static_cast<unsigned char>(_opc_bits + _arg_bits))
, dump_instr(dump)
, exec_instr(exec)
, compute_instr_len(comp_len) {
assert(_arg_bits <= max_opcode_bits && _opc_bits <= max_opcode_bits && _arg_bits + _opc_bits <= max_opcode_bits);
}
OpcodeInstrExt::OpcodeInstrExt(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len)
: OpcodeInstr(opcode_min << (max_opcode_bits - _tot_bits), opcode_max << (max_opcode_bits - _tot_bits))
, opc_bits(static_cast<unsigned char>(_tot_bits - _arg_bits))
, tot_bits(static_cast<unsigned char>(_tot_bits))
, dump_instr(dump)
, exec_instr(exec)
, compute_instr_len(comp_len) {
assert(_arg_bits <= _tot_bits && _tot_bits <= max_opcode_bits);
assert(opcode_min < opcode_max && opcode_max <= (1U << _tot_bits));
}
int OpcodeInstrExt::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
st->consume_gas(gas_per_instr + tot_bits * gas_per_bit);
if (bits < tot_bits) {
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
}
return exec_instr(st, cs, opcode >> (max_opcode_bits - tot_bits), tot_bits);
}
std::string OpcodeInstrExt::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < tot_bits) {
return "";
}
return dump_instr(cs, opcode >> (max_opcode_bits - tot_bits), (int)tot_bits);
}
int OpcodeInstrExt::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
if (bits < tot_bits) {
return 0;
} else {
return compute_instr_len(cs, opcode >> (max_opcode_bits - tot_bits), (int)tot_bits);
}
}
/*
OpcodeInstr* OpcodeInstr::mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_instr_func_t exec) {
return new OpcodeInstrSimple(opcode, opc_bits, _name, exec);
}
*/
OpcodeInstr* OpcodeInstr::mksimple(unsigned opcode, unsigned opc_bits, std::string _name,
exec_simple_instr_func_t exec) {
return new OpcodeInstrSimplest(opcode, opc_bits, _name, exec);
}
OpcodeInstr* OpcodeInstr::mkfixed(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_arg_instr_func_t dump,
exec_arg_instr_func_t exec) {
return new OpcodeInstrFixed(opcode, opc_bits, arg_bits, dump, exec);
}
OpcodeInstr* OpcodeInstr::mkfixedrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec) {
return new OpcodeInstrFixed(opcode_min, opcode_max, tot_bits, arg_bits, dump, exec);
}
OpcodeInstr* OpcodeInstr::mkext(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_instr_func_t dump,
exec_instr_func_t exec, compute_instr_len_func_t comp_len) {
return new OpcodeInstrExt(opcode, opc_bits, arg_bits, dump, exec, comp_len);
}
OpcodeInstr* OpcodeInstr::mkextrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
dump_instr_func_t dump, exec_instr_func_t exec,
compute_instr_len_func_t comp_len) {
return new OpcodeInstrExt(opcode_min, opcode_max, tot_bits, arg_bits, dump, exec, comp_len);
}
namespace instr {
using namespace std::placeholders;
dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix) {
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << (args & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix) {
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << (args & 255) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix) {
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix) {
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15)
<< suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix) {
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix) {
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s"
<< (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix) {
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << (args & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix) {
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << (int)(args & 255) + adj << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix) {
return [mask, prefix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << (args & mask) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix) {
return [prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << ((args >> 4) & 15) << interfix << (args & 15) << suffix;
return os.str();
};
}
dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix) {
return [add, prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string {
std::ostringstream os{prefix};
os << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix;
return os.str();
};
}
} // namespace instr
} // namespace vm

191
crypto/vm/opctable.h Normal file
View file

@ -0,0 +1,191 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/dispatch.h"
#include <functional>
#include <utility>
#include <vector>
#include <map>
namespace vm {
typedef std::function<int(const CellSlice&, unsigned, int)> compute_instr_len_func_t;
//typedef std::function<int(unsigned, int)> compute_arg_instr_len_func_t;
typedef std::function<std::string(CellSlice&, unsigned, int)> dump_instr_func_t;
typedef std::function<std::string(CellSlice&, unsigned)> dump_arg_instr_func_t;
typedef std::function<int(VmState* st, CellSlice&, unsigned, int)> exec_instr_func_t;
typedef std::function<int(VmState* st, unsigned)> exec_arg_instr_func_t;
typedef std::function<int(VmState* st)> exec_simple_instr_func_t;
enum { max_opcode_bits = 24 };
const unsigned top_opcode = (1U << max_opcode_bits);
class OpcodeInstr {
unsigned min_opcode, max_opcode;
public:
static constexpr unsigned gas_per_instr = 10, gas_per_bit = 1;
virtual ~OpcodeInstr() = default;
virtual int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const = 0;
virtual std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const;
virtual int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const;
OpcodeInstr(unsigned _min, unsigned _max) : min_opcode(_min), max_opcode(_max) {
}
OpcodeInstr(unsigned _opcode, unsigned _bits, bool);
unsigned get_opcode_min() const {
return min_opcode;
}
unsigned get_opcode_max() const {
return max_opcode;
}
std::pair<unsigned, unsigned> get_opcode_range() const {
return {min_opcode, max_opcode};
}
//static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_instr_func_t exec);
static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_simple_instr_func_t exec);
static OpcodeInstr* mkfixed(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_arg_instr_func_t dump,
exec_arg_instr_func_t exec);
static OpcodeInstr* mkfixedrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec);
static OpcodeInstr* mkext(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_instr_func_t dump,
exec_instr_func_t exec, compute_instr_len_func_t comp_len);
static OpcodeInstr* mkextrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len);
};
namespace instr {
dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix = "");
dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix = "");
dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix = "");
} // namespace instr
class OpcodeTable : public DispatchTable {
std::map<unsigned, const OpcodeInstr*> instructions;
std::vector<std::pair<unsigned, const OpcodeInstr*>> instruction_list;
std::string name;
Codepage codepage;
bool final;
public:
OpcodeTable(std::string _name, Codepage cp) : name(_name), codepage(cp), final(false) {
}
OpcodeTable(const OpcodeTable&) = delete;
OpcodeTable(OpcodeTable&&) = delete;
OpcodeTable& operator=(const OpcodeTable&) = delete;
OpcodeTable& operator=(OpcodeTable&&) = delete;
~OpcodeTable() override = default;
DispatchTable* finalize() override;
bool is_final() const override {
return final;
}
int dispatch(VmState* st, CellSlice& cs) const override;
std::string dump_instr(CellSlice& cs) const override;
int instr_len(const CellSlice& cs) const override;
bool insert_bool(const OpcodeInstr*);
OpcodeTable& insert(const OpcodeInstr*);
private:
const OpcodeInstr* lookup_instr(unsigned opcode, unsigned bits) const;
const OpcodeInstr* lookup_instr(const CellSlice& cs, unsigned& opcode, unsigned& bits) const;
};
class OpcodeInstrDummy : public OpcodeInstr {
public:
OpcodeInstrDummy() = delete;
OpcodeInstrDummy(unsigned _minopc, unsigned _maxopc) : OpcodeInstr(_minopc, _maxopc) {
}
~OpcodeInstrDummy() override = default;
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
};
class OpcodeInstrSimple : public OpcodeInstr {
unsigned char opc_bits;
std::string name;
exec_instr_func_t exec_instr;
public:
OpcodeInstrSimple() = delete;
OpcodeInstrSimple(unsigned opcode, unsigned _opc_bits, std::string _name, exec_instr_func_t exec);
~OpcodeInstrSimple() override = default;
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
};
class OpcodeInstrSimplest : public OpcodeInstr {
unsigned char opc_bits;
std::string name;
exec_simple_instr_func_t exec_instr;
public:
OpcodeInstrSimplest() = delete;
OpcodeInstrSimplest(unsigned opcode, unsigned _opc_bits, std::string _name, exec_simple_instr_func_t exec);
~OpcodeInstrSimplest() override = default;
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
};
class OpcodeInstrFixed : public OpcodeInstr {
unsigned char opc_bits, tot_bits;
std::string name;
dump_arg_instr_func_t dump_instr;
exec_arg_instr_func_t exec_instr;
public:
OpcodeInstrFixed() = delete;
OpcodeInstrFixed(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_arg_instr_func_t dump,
exec_arg_instr_func_t exec);
OpcodeInstrFixed(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec);
~OpcodeInstrFixed() override = default;
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
};
class OpcodeInstrExt : public OpcodeInstr {
unsigned char opc_bits, tot_bits;
dump_instr_func_t dump_instr;
exec_instr_func_t exec_instr;
compute_instr_len_func_t compute_instr_len;
public:
OpcodeInstrExt() = delete;
OpcodeInstrExt(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_instr_func_t dump,
exec_instr_func_t exec, compute_instr_len_func_t comp_len);
OpcodeInstrExt(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len);
~OpcodeInstrExt() override = default;
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
};
} // namespace vm

632
crypto/vm/stack.cpp Normal file
View file

@ -0,0 +1,632 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/box.hpp"
#include "vm/atom.h"
namespace td {
template class td::Cnt<std::string>;
template class td::Ref<td::Cnt<std::string>>;
template class td::Cnt<std::vector<vm::StackEntry>>;
template class td::Ref<td::Cnt<std::vector<vm::StackEntry>>>;
} // namespace td
namespace vm {
// from_object_t from_object{};
const char* exception_messages[(int)(Excno::total)] = {
"normal termination", "alternative termination", "stack underflow", "stack overflow", "integer overflow",
"integer out of range", "invalid opcode", "type check error", "cell overflow", "cell underflow",
"dictionary error", "unknown error", "fatal error"};
const char* get_exception_msg(Excno exc_no) {
if (exc_no >= Excno::none && exc_no < Excno::total) {
return exception_messages[static_cast<int>(exc_no)];
} else {
return "unknown vm exception";
}
}
static const char HEX_digits[] = "0123456789ABCDEF";
std::string str_to_hex(std::string data, std::string prefix) {
prefix.reserve(prefix.size() + data.size() * 2);
for (char c : data) {
prefix += HEX_digits[(c >> 4) & 15];
prefix += HEX_digits[c & 15];
}
return prefix;
}
std::string StackEntry::to_string() const {
std::ostringstream os;
dump(os);
return std::move(os).str();
}
void StackEntry::dump(std::ostream& os) const {
switch (tp) {
case t_null:
os << "(null)";
break;
case t_int:
os << dec_string(as_int());
break;
case t_cell:
os << "C{" << static_cast<Ref<Cell>>(ref)->get_hash().to_hex() << "}";
break;
case t_builder:
os << "BC{" << static_cast<Ref<CellBuilder>>(ref)->to_hex() << "}";
break;
case t_slice: {
os << "CS{";
static_cast<Ref<CellSlice>>(ref)->dump(os, 1, false);
os << '}';
break;
}
case t_string:
os << "\"" << as_string() << "\"";
break;
case t_bytes:
os << "BYTES:" << str_to_hex(as_bytes());
break;
case t_box: {
os << "Box{" << (const void*)&*ref << "}";
break;
}
case t_atom:
os << as_atom();
break;
case t_tuple: {
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
auto n = tuple.size();
if (!n) {
os << "[]";
} else if (n == 1) {
os << "[ ";
tuple[0].dump(os);
os << " ]";
} else {
os << "[ ";
for (const auto& entry : tuple) {
entry.dump(os);
os << ' ';
}
os << ']';
}
break;
}
case t_object: {
os << "Object{" << (const void*)&*ref << "}";
break;
}
default:
os << "???";
}
}
void StackEntry::print_list(std::ostream& os) const {
switch (tp) {
case t_null:
os << "()";
break;
case t_tuple: {
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
auto n = tuple.size();
if (!n) {
os << "[]";
} else if (n == 1) {
os << "[";
tuple[0].print_list(os);
os << "]";
} else if (n != 2) {
os << "[";
unsigned c = 0;
for (const auto& entry : tuple) {
if (c++) {
os << " ";
}
entry.print_list(os);
}
os << ']';
} else {
os << '(';
tuple[0].print_list(os);
tuple[1].print_list_tail(os);
}
break;
}
default:
dump(os);
}
}
void StackEntry::print_list_tail(std::ostream& os) const {
switch (tp) {
case t_null:
os << ')';
break;
case t_tuple: {
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
if (tuple.size() == 2) {
os << ' ';
tuple[0].print_list(os);
tuple[1].print_list_tail(os);
break;
}
}
// fall through
default:
os << " . ";
print_list(os);
os << ')';
}
}
StackEntry::StackEntry(Ref<Stack> stack_ref) : ref(std::move(stack_ref)), tp(t_stack) {
}
StackEntry::StackEntry(Ref<Continuation> cont_ref) : ref(std::move(cont_ref)), tp(t_vmcont) {
}
Ref<Continuation> StackEntry::as_cont() const & {
return as<Continuation, t_vmcont>();
}
Ref<Continuation> StackEntry::as_cont() && {
return move_as<Continuation, t_vmcont>();
}
StackEntry::StackEntry(Ref<Box> box_ref) : ref(std::move(box_ref)), tp(t_box) {
}
Ref<Box> StackEntry::as_box() const & {
return as<Box, t_box>();
}
Ref<Box> StackEntry::as_box() && {
return move_as<Box, t_box>();
}
StackEntry::StackEntry(Ref<Tuple> tuple_ref) : ref(std::move(tuple_ref)), tp(t_tuple) {
}
StackEntry::StackEntry(const std::vector<StackEntry>& tuple_components)
: ref(Ref<Tuple>{true, tuple_components}), tp(t_tuple) {
}
StackEntry::StackEntry(std::vector<StackEntry>&& tuple_components)
: ref(Ref<Tuple>{true, std::move(tuple_components)}), tp(t_tuple) {
}
Ref<Tuple> StackEntry::as_tuple() const & {
return as<Tuple, t_tuple>();
}
Ref<Tuple> StackEntry::as_tuple() && {
return move_as<Tuple, t_tuple>();
}
Ref<Tuple> StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) const & {
auto t = as<Tuple, t_tuple>();
if (t.not_null() && t->size() <= max_len && t->size() >= min_len) {
return t;
} else {
return {};
}
}
Ref<Tuple> StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) && {
auto t = move_as<Tuple, t_tuple>();
if (t.not_null() && t->size() <= max_len && t->size() >= min_len) {
return t;
} else {
return {};
}
}
StackEntry::StackEntry(Ref<Atom> atom_ref) : ref(std::move(atom_ref)), tp(t_atom) {
}
Ref<Atom> StackEntry::as_atom() const & {
return as<Atom, t_atom>();
}
Ref<Atom> StackEntry::as_atom() && {
return move_as<Atom, t_atom>();
}
const StackEntry& tuple_index(const Tuple& tup, unsigned idx) {
if (idx >= tup->size()) {
throw VmError{Excno::range_chk, "tuple index out of range"};
}
return (*tup)[idx];
}
StackEntry tuple_extend_index(const Ref<Tuple>& tup, unsigned idx) {
if (tup.is_null() || idx >= tup->size()) {
return {};
} else {
return tup->at(idx);
}
}
unsigned tuple_extend_set_index(Ref<Tuple>& tup, unsigned idx, StackEntry&& value, bool force) {
if (tup.is_null()) {
if (value.empty() && !force) {
return 0;
}
tup = Ref<Tuple>{true, idx + 1};
tup.unique_write().at(idx) = std::move(value);
return idx + 1;
}
if (tup->size() <= idx) {
if (value.empty() && !force) {
return 0;
}
auto& tuple = tup.write();
tuple.resize(idx + 1);
tuple.at(idx) = std::move(value);
return idx + 1;
} else {
tup.write().at(idx) = std::move(value);
return (unsigned)tup->size();
}
}
Stack::Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) {
push_from_stack(old_stack, copy_elem, skip_top);
}
Stack::Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) {
push_from_stack(old_stack, copy_elem, skip_top);
}
void Stack::push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) {
unsigned n = old_stack.depth();
if (skip_top > n || copy_elem > n - skip_top) {
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
}
stack.reserve(stack.size() + copy_elem);
auto it = old_stack.stack.cend() - skip_top;
std::copy(it - copy_elem, it, std::back_inserter(stack));
}
void Stack::push_from_stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) {
unsigned n = old_stack.depth();
if (skip_top > n || copy_elem > n - skip_top) {
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
}
stack.reserve(stack.size() + copy_elem);
auto it = old_stack.stack.cend() - skip_top;
std::move(it - copy_elem, it, std::back_inserter(stack));
}
void Stack::move_from_stack(Stack& old_stack, unsigned copy_elem) {
unsigned n = old_stack.depth();
if (copy_elem > n) {
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
}
LOG(DEBUG) << "moving " << copy_elem << " top elements to another stack\n";
stack.reserve(stack.size() + copy_elem);
auto it = old_stack.stack.cend();
std::move(it - copy_elem, it, std::back_inserter(stack));
old_stack.pop_many(copy_elem);
}
void Stack::pop_null() {
check_underflow(1);
if (!pop().empty()) {
throw VmError{Excno::type_chk, "not an null"};
}
}
td::RefInt256 Stack::pop_int() {
check_underflow(1);
td::RefInt256 res = pop().as_int();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not an integer"};
}
return res;
}
td::RefInt256 Stack::pop_int_finite() {
auto res = pop_int();
if (!res->is_valid()) {
throw VmError{Excno::int_ov};
}
return res;
}
bool Stack::pop_bool() {
return sgn(pop_int_finite()) != 0;
}
long long Stack::pop_long() {
return pop_int()->to_long();
}
long long Stack::pop_long_range(long long max, long long min) {
auto res = pop_long();
if (res > max || res < min) {
throw VmError{Excno::range_chk};
}
return res;
}
int Stack::pop_smallint_range(int max, int min) {
return (int)pop_long_range(max, min);
}
Ref<Cell> Stack::pop_cell() {
check_underflow(1);
auto res = pop().as_cell();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a cell"};
}
return res;
}
Ref<Cell> Stack::pop_maybe_cell() {
check_underflow(1);
auto tmp = pop();
if (tmp.empty()) {
return {};
}
auto res = std::move(tmp).as_cell();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a cell"};
}
return res;
}
Ref<CellBuilder> Stack::pop_builder() {
check_underflow(1);
auto res = pop().as_builder();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a cell builder"};
}
return res;
}
Ref<CellSlice> Stack::pop_cellslice() {
check_underflow(1);
auto res = pop().as_slice();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a cell slice"};
}
return res;
}
std::string Stack::pop_string() {
check_underflow(1);
auto res = pop().as_string_ref();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a string"};
}
return *res;
}
std::string Stack::pop_bytes() {
check_underflow(1);
auto res = pop().as_bytes_ref();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a bytes chunk"};
}
return *res;
}
Ref<Continuation> Stack::pop_cont() {
check_underflow(1);
auto res = pop().as_cont();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a continuation"};
}
return res;
}
Ref<Box> Stack::pop_box() {
check_underflow(1);
auto res = pop().as_box();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a box"};
}
return res;
}
Ref<Tuple> Stack::pop_tuple() {
check_underflow(1);
auto res = pop().as_tuple();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a tuple"};
}
return res;
}
Ref<Tuple> Stack::pop_tuple_range(unsigned max_len, unsigned min_len) {
check_underflow(1);
auto res = pop().as_tuple();
if (res.is_null() || res->size() > max_len || res->size() < min_len) {
throw VmError{Excno::type_chk, "not a tuple of valid size"};
}
return res;
}
Ref<Tuple> Stack::pop_maybe_tuple() {
check_underflow(1);
auto val = pop();
if (val.empty()) {
return {};
}
auto res = std::move(val).as_tuple();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not a tuple"};
}
return res;
}
Ref<Tuple> Stack::pop_maybe_tuple_range(unsigned max_len) {
check_underflow(1);
auto val = pop();
if (val.empty()) {
return {};
}
auto res = std::move(val).as_tuple();
if (res.is_null() || res->size() > max_len) {
throw VmError{Excno::type_chk, "not a tuple of valid size"};
}
return res;
}
Ref<Atom> Stack::pop_atom() {
check_underflow(1);
auto res = pop().as_atom();
if (res.is_null()) {
throw VmError{Excno::type_chk, "not an atom"};
}
return res;
}
void Stack::push_null() {
push({});
}
void Stack::push_int(td::RefInt256 val) {
if (!val->signed_fits_bits(257)) {
throw VmError{Excno::int_ov};
}
push(std::move(val));
}
void Stack::push_int_quiet(td::RefInt256 val, bool quiet) {
if (!val->signed_fits_bits(257)) {
if (!quiet) {
throw VmError{Excno::int_ov};
} else if (val->is_valid()) {
push(td::RefInt256{true});
return;
}
}
push(std::move(val));
}
void Stack::push_string(std::string str) {
push(std::move(str));
}
void Stack::push_string(td::Slice slice) {
push(slice.str());
}
void Stack::push_bytes(std::string str) {
push(std::move(str), true);
}
void Stack::push_bytes(td::Slice slice) {
push(slice.str(), true);
}
void Stack::push_cell(Ref<Cell> cell) {
push(std::move(cell));
}
void Stack::push_maybe_cell(Ref<Cell> cell) {
push_maybe(std::move(cell));
}
void Stack::push_builder(Ref<CellBuilder> cb) {
push(std::move(cb));
}
void Stack::push_smallint(long long val) {
push(td::RefInt256{true, val});
}
void Stack::push_bool(bool val) {
push_smallint(val ? -1 : 0);
}
void Stack::push_cont(Ref<Continuation> cont) {
push(std::move(cont));
}
void Stack::push_box(Ref<Box> box) {
push(std::move(box));
}
void Stack::push_tuple(Ref<Tuple> tuple) {
push(std::move(tuple));
}
void Stack::push_maybe_tuple(Ref<Tuple> tuple) {
if (tuple.not_null()) {
push(std::move(tuple));
} else {
push_null();
}
}
void Stack::push_tuple(const std::vector<StackEntry>& components) {
push(components);
}
void Stack::push_tuple(std::vector<StackEntry>&& components) {
push(std::move(components));
}
void Stack::push_atom(Ref<Atom> atom) {
push(std::move(atom));
}
Ref<Stack> Stack::split_top(unsigned top_cnt, unsigned drop_cnt) {
unsigned n = depth();
if (top_cnt > n || drop_cnt > n - top_cnt) {
return Ref<Stack>{};
}
Ref<Stack> new_stk = Ref<Stack>{true};
if (top_cnt) {
new_stk.unique_write().move_from_stack(*this, top_cnt);
}
if (drop_cnt) {
pop_many(drop_cnt);
}
return new_stk;
}
void Stack::dump(std::ostream& os, bool cr) const {
os << " [ ";
for (const auto& x : stack) {
os << x.to_string() << ' ';
}
os << "] ";
if (cr) {
os << std::endl;
}
}
void Stack::push_cellslice(Ref<CellSlice> cs) {
push(std::move(cs));
}
void Stack::push_maybe_cellslice(Ref<CellSlice> cs) {
push_maybe(std::move(cs));
}
} // namespace vm

496
crypto/vm/stack.hpp Normal file
View file

@ -0,0 +1,496 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <memory>
#include "common/refcnt.hpp"
#include "common/bigint.hpp"
#include "common/refint.h"
#include "common/bitstring.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/excno.hpp"
namespace td {
extern template class td::Cnt<std::string>;
extern template class td::Ref<td::Cnt<std::string>>;
} // namespace td
namespace vm {
using td::Cnt;
using td::Ref;
using td::RefAny;
const char* get_exception_msg(Excno exc_no);
std::string str_to_hex(std::string data, std::string prefix = "");
class StackEntry;
class Stack;
class Continuation;
class Box;
class Atom;
using Tuple = td::Cnt<std::vector<StackEntry>>;
struct from_object_t {};
constexpr from_object_t from_object{};
class StackEntry {
public:
enum Type {
t_null,
t_int,
t_cell,
t_builder,
t_slice,
t_vmcont,
t_tuple,
t_stack,
t_string,
t_bytes,
t_bitstring,
t_box,
t_atom,
t_object
};
private:
RefAny ref;
Type tp;
public:
StackEntry() : ref(), tp(t_null) {
}
~StackEntry() {
}
StackEntry(Ref<Cell> cell_ref) : ref(std::move(cell_ref)), tp(t_cell) {
}
StackEntry(Ref<CellBuilder> cb_ref) : ref(std::move(cb_ref)), tp(t_builder) {
}
StackEntry(Ref<CellSlice> cs_ref) : ref(std::move(cs_ref)), tp(t_slice) {
}
StackEntry(td::RefInt256 int_ref) : ref(std::move(int_ref)), tp(t_int) {
}
StackEntry(std::string str, bool bytes = false) : ref(), tp(bytes ? t_bytes : t_string) {
ref = Ref<Cnt<std::string>>{true, std::move(str)};
}
StackEntry(Ref<Stack> stack_ref);
StackEntry(Ref<Continuation> cont_ref);
StackEntry(Ref<Box> box_ref);
StackEntry(Ref<Tuple> tuple_ref);
StackEntry(const std::vector<StackEntry>& tuple_components);
StackEntry(std::vector<StackEntry>&& tuple_components);
StackEntry(Ref<Atom> atom_ref);
StackEntry(const StackEntry& se) : ref(se.ref), tp(se.tp) {
}
StackEntry(StackEntry&& se) noexcept : ref(std::move(se.ref)), tp(se.tp) {
se.tp = t_null;
}
template <class T>
StackEntry(from_object_t, Ref<T> obj_ref) : ref(std::move(obj_ref)), tp(t_object) {
}
StackEntry& operator=(const StackEntry& se) {
ref = se.ref;
tp = se.tp;
return *this;
}
StackEntry& operator=(StackEntry&& se) {
ref = std::move(se.ref);
tp = se.tp;
se.tp = t_null;
return *this;
}
StackEntry& clear() {
ref.clear();
tp = t_null;
return *this;
}
bool empty() const {
return tp == t_null;
}
bool is_tuple() const {
return tp == t_tuple;
}
bool is_atom() const {
return tp == t_atom;
}
bool is(int wanted) const {
return tp == wanted;
}
void swap(StackEntry& se) {
ref.swap(se.ref);
std::swap(tp, se.tp);
}
bool operator==(const StackEntry& other) const {
return tp == other.tp && ref == other.ref;
}
bool operator!=(const StackEntry& other) const {
return !(tp == other.tp && ref == other.ref);
}
Type type() const {
return tp;
}
private:
template <typename T, Type tag>
Ref<T> dynamic_as() const & {
return tp == tag ? static_cast<Ref<T>>(ref) : td::Ref<T>{};
}
template <typename T, Type tag>
Ref<T> dynamic_as() && {
return tp == tag ? static_cast<Ref<T>>(std::move(ref)) : td::Ref<T>{};
}
template <typename T, Type tag>
Ref<T> dynamic_move_as() & {
return tp == tag ? static_cast<Ref<T>>(std::move(ref)) : td::Ref<T>{};
}
template <typename T, Type tag>
Ref<T> as() const & {
return tp == tag ? Ref<T>{td::static_cast_ref(), ref} : td::Ref<T>{};
}
template <typename T, Type tag>
Ref<T> as() && {
return tp == tag ? Ref<T>{td::static_cast_ref(), std::move(ref)} : td::Ref<T>{};
}
template <typename T, Type tag>
Ref<T> move_as() & {
return tp == tag ? Ref<T>{td::static_cast_ref(), std::move(ref)} : td::Ref<T>{};
}
public:
template <typename T>
static StackEntry maybe(Ref<T> ref) {
if (ref.is_null()) {
return {};
} else {
return ref;
}
}
td::RefInt256 as_int() const & {
return as<td::CntInt256, t_int>();
}
td::RefInt256 as_int() && {
return move_as<td::CntInt256, t_int>();
}
Ref<Cell> as_cell() const & {
return as<Cell, t_cell>();
}
Ref<Cell> as_cell() && {
return move_as<Cell, t_cell>();
}
Ref<CellBuilder> as_builder() const & {
return as<CellBuilder, t_builder>();
}
Ref<CellBuilder> as_builder() && {
return move_as<CellBuilder, t_builder>();
}
Ref<CellSlice> as_slice() const & {
return as<CellSlice, t_slice>();
}
Ref<CellSlice> as_slice() && {
return move_as<CellSlice, t_slice>();
}
Ref<Continuation> as_cont() const &;
Ref<Continuation> as_cont() &&;
Ref<Cnt<std::string>> as_string_ref() const {
return as<Cnt<std::string>, t_string>();
}
Ref<Cnt<std::string>> as_bytes_ref() const {
return as<Cnt<std::string>, t_bytes>();
}
std::string as_string() const {
//assert(!as_string_ref().is_null());
return tp == t_string ? *as_string_ref() : "";
}
std::string as_bytes() const {
return tp == t_bytes ? *as_bytes_ref() : "";
}
Ref<Box> as_box() const &;
Ref<Box> as_box() &&;
Ref<Tuple> as_tuple() const &;
Ref<Tuple> as_tuple() &&;
Ref<Tuple> as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) const &;
Ref<Tuple> as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) &&;
Ref<Atom> as_atom() const &;
Ref<Atom> as_atom() &&;
template <class T>
Ref<T> as_object() const & {
return dynamic_as<T, t_object>();
}
template <class T>
Ref<T> as_object() && {
return dynamic_move_as<T, t_object>();
}
void dump(std::ostream& os) const;
void print_list(std::ostream& os) const;
void print_list_tail(std::ostream& os) const;
std::string to_string() const;
};
inline void swap(StackEntry& se1, StackEntry& se2) {
se1.swap(se2);
}
template <typename... Args>
Ref<Tuple> make_tuple_ref(Args&&... args) {
return td::make_cnt_ref<std::vector<vm::StackEntry>>(std::vector<vm::StackEntry>{std::forward<Args>(args)...});
}
const StackEntry& tuple_index(const Tuple& tup, unsigned idx);
StackEntry tuple_extend_index(const Ref<Tuple>& tup, unsigned idx);
unsigned tuple_extend_set_index(Ref<Tuple>& tup, unsigned idx, StackEntry&& value, bool force = false);
class Stack : public td::CntObject {
std::vector<StackEntry> stack;
public:
Stack() {
}
~Stack() override = default;
Stack(const std::vector<StackEntry>& _stack) : stack(_stack) {
}
Stack(std::vector<StackEntry>&& _stack) : stack(std::move(_stack)) {
}
Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top);
Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top);
td::CntObject* make_copy() const override {
std::cerr << "copy stack at " << (const void*)this << " (" << depth() << " entries)\n";
return new Stack{stack};
}
void push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top = 0);
void push_from_stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top = 0);
void move_from_stack(Stack& old_stack, unsigned copy_elem);
Ref<Stack> split_top(unsigned top_cnt, unsigned drop_cnt = 0);
StackEntry& push() {
stack.emplace_back();
return stack.back();
}
template <typename... Args>
StackEntry& push(Args&&... args) {
stack.emplace_back(args...);
return stack.back();
}
StackEntry& push(const StackEntry& se) {
stack.push_back(se);
return stack.back();
}
StackEntry& push(StackEntry&& se) {
stack.emplace_back(std::move(se));
return stack.back();
}
void pop(StackEntry& se) {
stack.back().swap(se);
stack.pop_back();
}
StackEntry pop() {
StackEntry res = std::move(stack.back());
stack.pop_back();
return res;
}
StackEntry pop_chk() {
check_underflow(1);
return pop();
}
void pop_many(int count) {
stack.resize(stack.size() - count);
}
void drop_bottom(int count) {
std::move(stack.cbegin() + count, stack.cend(), stack.begin());
pop_many(count);
}
StackEntry& operator[](int idx) { // NB: we sometimes use idx=-1
return stack[stack.size() - idx - 1];
}
const StackEntry& operator[](int idx) const {
return stack[stack.size() - idx - 1];
}
StackEntry& at(int idx) {
return stack.at(stack.size() - idx - 1);
}
const StackEntry& at(int idx) const {
return stack.at(stack.size() - idx - 1);
}
StackEntry fetch(int idx) const {
return stack[stack.size() - idx - 1];
}
StackEntry& tos() {
return stack.back();
}
const StackEntry& tos() const {
return stack.back();
}
bool is_empty() const {
return stack.empty();
}
int depth() const {
return (int)stack.size();
}
std::vector<StackEntry>::iterator top() {
return stack.end();
}
std::vector<StackEntry>::const_iterator top() const {
return stack.cend();
}
std::vector<StackEntry>::iterator from_top(int offs) {
return stack.end() - offs;
}
std::vector<StackEntry>::const_iterator from_top(int offs) const {
return stack.cend() - offs;
}
bool at_least(int req) const {
return depth() >= req;
}
template <typename... Args>
bool at_least(int req, Args... args) const {
return at_least(req) && at_least(args...);
}
bool more_than(int req) const {
return depth() > req;
}
template <typename... Args>
bool more_than(int req, Args... args) const {
return more_than(req) && more_than(args...);
}
void clear() {
stack.clear();
}
Stack& set_contents(const Stack& other_stack) {
stack = other_stack.stack;
return *this;
}
Stack& set_contents(Stack&& other_stack) {
stack = std::move(other_stack.stack);
return *this;
}
Stack& set_contents(Ref<Stack> ref) {
if (ref.is_null()) {
clear();
} else if (ref->is_unique()) {
set_contents(std::move(ref.unique_write()));
} else {
set_contents(*ref);
}
return *this;
}
template <typename... Args>
const Stack& check_underflow(Args... args) const {
if (!at_least(args...)) {
throw VmError{Excno::stk_und};
}
return *this;
}
template <typename... Args>
Stack& check_underflow(Args... args) {
if (!at_least(args...)) {
throw VmError{Excno::stk_und};
}
return *this;
}
template <typename... Args>
const Stack& check_underflow_p(Args... args) const {
if (!more_than(args...)) {
throw VmError{Excno::stk_und};
}
return *this;
}
template <typename... Args>
Stack& check_underflow_p(Args... args) {
if (!more_than(args...)) {
throw VmError{Excno::stk_und};
}
return *this;
}
Stack& reserve(int cnt) {
stack.reserve(cnt);
return *this;
}
void pop_null();
td::RefInt256 pop_int();
td::RefInt256 pop_int_finite();
bool pop_bool();
long long pop_long();
long long pop_long_range(long long max, long long min = 0);
int pop_smallint_range(int max, int min = 0);
Ref<Cell> pop_cell();
Ref<Cell> pop_maybe_cell();
Ref<CellBuilder> pop_builder();
Ref<CellSlice> pop_cellslice();
Ref<Continuation> pop_cont();
Ref<Box> pop_box();
Ref<Tuple> pop_tuple();
Ref<Tuple> pop_tuple_range(unsigned max_len = 255, unsigned min_len = 0);
Ref<Tuple> pop_maybe_tuple();
Ref<Tuple> pop_maybe_tuple_range(unsigned max_len = 255);
Ref<Atom> pop_atom();
std::string pop_string();
std::string pop_bytes();
void push_null();
void push_int(td::RefInt256 val);
void push_int_quiet(td::RefInt256 val, bool quiet = true);
void push_smallint(long long val);
void push_bool(bool val);
void push_string(std::string str);
void push_string(td::Slice slice);
void push_bytes(std::string str);
void push_bytes(td::Slice slice);
void push_cell(Ref<Cell> cell);
void push_maybe_cell(Ref<Cell> cell);
void push_maybe_cellslice(Ref<CellSlice> cs);
void push_builder(Ref<CellBuilder> cb);
void push_cellslice(Ref<CellSlice> cs);
void push_cont(Ref<Continuation> cont);
void push_box(Ref<Box> box);
void push_tuple(Ref<Tuple> tuple);
void push_tuple(const std::vector<StackEntry>& components);
void push_tuple(std::vector<StackEntry>&& components);
void push_maybe_tuple(Ref<Tuple> tuple);
void push_atom(Ref<Atom> atom);
template <typename T>
void push_object(Ref<T> obj) {
push({vm::from_object, std::move(obj)});
}
template <typename T, typename... Args>
void push_make_object(Args&&... args) {
push_object<T>(td::make_ref<T>(std::forward<Args>(args)...));
}
template <typename T>
void push_maybe(Ref<T> val) {
if (val.is_null()) {
push({});
} else {
push(std::move(val));
}
}
void dump(std::ostream& os, bool cr = true) const;
};
} // namespace vm
namespace td {
extern template class td::Cnt<std::vector<vm::StackEntry>>;
extern template class td::Ref<td::Cnt<std::vector<vm::StackEntry>>>;
} // namespace td

576
crypto/vm/stackops.cpp Normal file
View file

@ -0,0 +1,576 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/log.h"
#include "vm/stackops.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
namespace vm {
int exec_nop(VmState* st) {
VM_LOG(st) << "execute NOP\n";
return 0;
}
// basic stack manipulation primitives
int exec_swap(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute SWAP\n";
stack.check_underflow(2);
swap(stack[0], stack[1]);
return 0;
}
int exec_xchg0(VmState* st, unsigned args) {
int x = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHG s" << x;
stack.check_underflow_p(x);
swap(stack[0], stack[x]);
return 0;
}
int exec_xchg0_l(VmState* st, unsigned args) {
int x = args & 255;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHG s" << x;
stack.check_underflow_p(x);
swap(stack[0], stack[x]);
return 0;
}
int exec_xchg(VmState* st, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
if (!x || x >= y) {
throw VmError{Excno::inv_opcode, "invalid XCHG arguments"};
}
VM_LOG(st) << "execute XCHG s" << x << ",s" << y;
Stack& stack = st->get_stack();
stack.check_underflow_p(y);
swap(stack[x], stack[y]);
return 0;
}
std::string dump_xchg(CellSlice&, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
if (!x || x >= y) {
return "";
}
std::ostringstream os{"XCHG s"};
os << x << ",s" << y;
return os.str();
}
int exec_xchg1(VmState* st, unsigned args) {
int x = args & 15;
assert(x >= 2);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHG s1,s" << x;
stack.check_underflow_p(x);
swap(stack[1], stack[x]);
return 0;
}
int exec_dup(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DUP\n";
stack.check_underflow(1);
stack.push(stack.fetch(0));
return 0;
}
int exec_over(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute OVER\n";
stack.check_underflow(2);
stack.push(stack.fetch(1));
return 0;
}
int exec_push(VmState* st, unsigned args) {
int x = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSH s" << x;
stack.check_underflow_p(x);
stack.push(stack.fetch(x));
return 0;
}
int exec_push_l(VmState* st, unsigned args) {
int x = args & 255;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSH s" << x;
stack.check_underflow_p(x);
stack.push(stack.fetch(x));
return 0;
}
int exec_drop(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DROP\n";
stack.check_underflow(1);
stack.pop();
return 0;
}
int exec_nip(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute NIP\n";
stack.check_underflow(2);
stack.pop(stack[1]);
return 0;
}
int exec_pop(VmState* st, unsigned args) {
int x = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute POP s" << x;
stack.check_underflow_p(x);
stack.pop(stack[x]);
return 0;
}
int exec_pop_l(VmState* st, unsigned args) {
int x = args & 255;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute POP s" << x;
stack.check_underflow_p(x);
stack.pop(stack[x]);
return 0;
}
// compound stack manipulation primitives
int exec_xchg2(VmState* st, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHG2 s" << x << ",s" << y;
stack.check_underflow_p(x, y, 1);
swap(stack[1], stack[x]);
swap(stack[0], stack[y]);
return 0;
}
int exec_xcpu(VmState* st, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCPU s" << x << ",s" << y;
stack.check_underflow_p(x, y);
swap(stack[0], stack[x]);
stack.push(stack.fetch(y));
return 0;
}
int exec_puxc(VmState* st, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUXC s" << x << ",s" << y - 1;
stack.check_underflow_p(x).check_underflow(y);
stack.push(stack.fetch(x));
swap(stack[0], stack[1]);
swap(stack[0], stack[y]);
return 0;
}
int exec_push2(VmState* st, unsigned args) {
int x = (args >> 4) & 15, y = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSH2 s" << x << ",s" << y;
stack.check_underflow_p(x, y);
stack.push(stack.fetch(x));
stack.push(stack.fetch(y + 1));
return 0;
}
int exec_xchg3(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHG3 s" << x << ",s" << y << ",s" << z;
stack.check_underflow_p(x, y, z, 2);
swap(stack[2], stack[x]);
swap(stack[1], stack[y]);
swap(stack[0], stack[z]);
return 0;
}
int exec_xc2pu(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XC2PU s" << x << ",s" << y << ",s" << z;
stack.check_underflow_p(x, y, z, 1);
swap(stack[1], stack[x]);
swap(stack[0], stack[y]);
stack.push(stack.fetch(z));
return 0;
}
int exec_xcpuxc(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCPUXC s" << x << ",s" << y << ",s" << z - 1;
stack.check_underflow_p(x, y, 1).check_underflow(z);
swap(stack[1], stack[x]);
stack.push(stack.fetch(y));
swap(stack[0], stack[1]);
swap(stack[0], stack[z]);
return 0;
}
int exec_xcpu2(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCPU2 s" << x << ",s" << y << ",s" << z;
stack.check_underflow_p(x, y, z);
swap(stack[0], stack[x]);
stack.push(stack.fetch(y));
stack.push(stack.fetch(z + 1));
return 0;
}
int exec_puxc2(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUXC2 s" << x << ",s" << y - 1 << ",s" << z - 1;
stack.check_underflow_p(x, 1).check_underflow(y, z);
stack.push(stack.fetch(x));
swap(stack[2], stack[0]);
swap(stack[1], stack[y]);
swap(stack[0], stack[z]);
return 0;
}
int exec_puxcpu(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUXCPU s" << x << ",s" << y - 1 << ",s" << z - 1;
stack.check_underflow_p(x).check_underflow(y, z);
stack.push(stack.fetch(x));
swap(stack[0], stack[1]);
swap(stack[0], stack[y]);
stack.push(stack.fetch(z));
return 0;
}
int exec_pu2xc(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PU2XC s" << x << ",s" << y - 1 << ",s" << z - 2;
stack.check_underflow_p(x).check_underflow(y, z - 1);
stack.push(stack.fetch(x));
swap(stack[1], stack[0]);
stack.push(stack.fetch(y));
swap(stack[1], stack[0]);
swap(stack[0], stack[z]);
return 0;
}
int exec_push3(VmState* st, unsigned args) {
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PUSH3 s" << x << ",s" << y << ",s" << z;
stack.check_underflow_p(x, y, z);
stack.push(stack.fetch(x));
stack.push(stack.fetch(y + 1));
stack.push(stack.fetch(z + 2));
return 0;
}
// exotic stack manipulation primitives
int exec_blkswap(VmState* st, unsigned args) {
int x = ((args >> 4) & 15) + 1, y = (args & 15) + 1;
Stack& stack = st->get_stack();
VM_LOG(st) << "execute BLKSWAP " << x << ',' << y;
stack.check_underflow(x + y);
std::reverse(stack.from_top(x + y), stack.from_top(y));
std::reverse(stack.from_top(y), stack.top());
std::reverse(stack.from_top(x + y), stack.top());
return 0;
}
int exec_rot(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ROT\n";
stack.check_underflow(3);
swap(stack[1], stack[2]);
swap(stack[0], stack[1]);
return 0;
}
int exec_rotrev(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ROTREV\n";
stack.check_underflow(3);
swap(stack[0], stack[1]);
swap(stack[1], stack[2]);
return 0;
}
int exec_2swap(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute 2SWAP\n";
stack.check_underflow(4);
swap(stack[1], stack[3]);
swap(stack[0], stack[2]);
return 0;
}
int exec_2drop(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute 2DROP\n";
stack.check_underflow(2);
stack.pop();
stack.pop();
return 0;
}
int exec_2dup(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute 2DUP\n";
stack.check_underflow(2);
stack.push(stack.fetch(1));
stack.push(stack.fetch(1));
return 0;
}
int exec_2over(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute 2OVER\n";
stack.check_underflow(4);
stack.push(stack.fetch(3));
stack.push(stack.fetch(3));
return 0;
}
int exec_reverse(VmState* st, unsigned args) {
int x = ((args >> 4) & 15) + 2, y = (args & 15);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute REVERSE " << x << ',' << y;
stack.check_underflow(x + y);
std::reverse(stack.from_top(x + y), stack.from_top(y));
return 0;
}
int exec_blkdrop(VmState* st, unsigned args) {
int x = (args & 15);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute BLKDROP " << x;
stack.check_underflow(x);
stack.pop_many(x);
return 0;
}
int exec_blkpush(VmState* st, unsigned args) {
int x = ((args >> 4) & 15), y = (args & 15);
Stack& stack = st->get_stack();
VM_LOG(st) << "execute BLKPUSH " << x << ',' << y;
stack.check_underflow_p(y);
while (--x >= 0) {
stack.push(stack.fetch(y));
}
return 0;
}
int exec_pick(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute PICK\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow_p(x);
stack.push(stack.fetch(x));
return 0;
}
int exec_roll(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ROLL\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow_p(x);
while (--x >= 0) {
swap(stack[x], stack[x + 1]);
}
return 0;
}
int exec_rollrev(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ROLLREV\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow_p(x);
for (int i = 0; i < x; i++) {
swap(stack[i], stack[i + 1]);
}
return 0;
}
int exec_blkswap_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute BLKSWX\n";
stack.check_underflow(2);
int y = stack.pop_smallint_range(255);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x + y);
if (x > 0 && y > 0) {
std::reverse(stack.from_top(x + y), stack.from_top(y));
std::reverse(stack.from_top(y), stack.top());
std::reverse(stack.from_top(x + y), stack.top());
}
return 0;
}
int exec_reverse_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute REVX\n";
stack.check_underflow(2);
int y = stack.pop_smallint_range(255);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x + y);
std::reverse(stack.from_top(x + y), stack.from_top(y));
return 0;
}
int exec_drop_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DROPX\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x);
stack.pop_many(x);
return 0;
}
int exec_tuck(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute TUCK\n";
stack.check_underflow(2);
swap(stack[0], stack[1]);
stack.push(stack.fetch(1));
return 0;
}
int exec_xchg_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute XCHGX\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow_p(x);
swap(stack[0], stack[x]);
return 0;
}
int exec_depth(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute DEPTH\n";
stack.push_smallint(stack.depth());
return 0;
}
int exec_chkdepth(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute CHKDEPTH\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x);
return 0;
}
int exec_onlytop_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ONLYTOPX\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x);
int n = stack.depth(), d = n - x;
if (d > 0) {
for (int i = n - 1; i >= d; i--) {
stack[i] = std::move(stack[i - d]);
}
}
stack.pop_many(d);
return 0;
}
int exec_only_x(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ONLYX\n";
stack.check_underflow(1);
int x = stack.pop_smallint_range(255);
stack.check_underflow(x);
stack.pop_many(stack.depth() - x);
return 0;
}
void register_stack_ops(OpcodeTable& cp0) {
cp0.insert(OpcodeInstr::mksimple(0x00, 8, "NOP", exec_nop))
.insert(OpcodeInstr::mksimple(0x01, 8, "SWAP", exec_swap))
.insert(OpcodeInstr::mkfixedrange(0x02, 0x10, 8, 4, instr::dump_1sr("XCHG "), exec_xchg0))
.insert(OpcodeInstr::mkfixed(0x10, 8, 8, dump_xchg, exec_xchg))
.insert(OpcodeInstr::mkfixed(0x11, 8, 8, instr::dump_1sr_l("XCHG "), exec_xchg0_l))
.insert(OpcodeInstr::mkfixedrange(0x12, 0x20, 8, 4, instr::dump_1sr("XCHG s1,"), exec_xchg1))
.insert(OpcodeInstr::mksimple(0x20, 8, "DUP", exec_dup))
.insert(OpcodeInstr::mksimple(0x21, 8, "OVER", exec_over))
.insert(OpcodeInstr::mkfixedrange(0x22, 0x30, 8, 4, instr::dump_1sr("PUSH "), exec_push))
.insert(OpcodeInstr::mksimple(0x30, 8, "DROP", exec_drop))
.insert(OpcodeInstr::mksimple(0x31, 8, "NIP", exec_nip))
.insert(OpcodeInstr::mkfixedrange(0x32, 0x40, 8, 4, instr::dump_1sr("POP "), exec_pop))
.insert(OpcodeInstr::mkfixed(0x4, 4, 12, instr::dump_3sr("XCHG3 "), exec_xchg3))
.insert(OpcodeInstr::mkfixed(0x50, 8, 8, instr::dump_2sr("XCHG2 "), exec_xchg2))
.insert(OpcodeInstr::mkfixed(0x51, 8, 8, instr::dump_2sr("XCPU "), exec_xcpu))
.insert(OpcodeInstr::mkfixed(0x52, 8, 8, instr::dump_2sr_adj(1, "PUXC "), exec_puxc))
.insert(OpcodeInstr::mkfixed(0x53, 8, 8, instr::dump_2sr("PUSH2 "), exec_push2))
.insert(OpcodeInstr::mkfixed(0x540, 12, 12, instr::dump_3sr("XCHG3 "), exec_xchg3))
.insert(OpcodeInstr::mkfixed(0x541, 12, 12, instr::dump_3sr("XC2PU "), exec_xc2pu))
.insert(OpcodeInstr::mkfixed(0x542, 12, 12, instr::dump_3sr_adj(1, "XCPUXC "), exec_xcpuxc))
.insert(OpcodeInstr::mkfixed(0x543, 12, 12, instr::dump_3sr("XCPU2 "), exec_xcpu2))
.insert(OpcodeInstr::mkfixed(0x544, 12, 12, instr::dump_3sr_adj(0x11, "PUXC2 "), exec_puxc2))
.insert(OpcodeInstr::mkfixed(0x545, 12, 12, instr::dump_3sr_adj(0x11, "PUXCPU "), exec_puxcpu))
.insert(OpcodeInstr::mkfixed(0x546, 12, 12, instr::dump_3sr_adj(0x12, "PU2XC "), exec_pu2xc))
.insert(OpcodeInstr::mkfixed(0x547, 12, 12, instr::dump_3sr("PUSH3 "), exec_push3))
.insert(OpcodeInstr::mkfixed(0x55, 8, 8, instr::dump_2c_add(0x11, "BLKSWAP ", ","), exec_blkswap))
.insert(OpcodeInstr::mkfixed(0x56, 8, 8, instr::dump_1sr_l("PUSH "), exec_push_l))
.insert(OpcodeInstr::mkfixed(0x57, 8, 8, instr::dump_1sr_l("POP "), exec_pop_l))
.insert(OpcodeInstr::mksimple(0x58, 8, "ROT", exec_rot))
.insert(OpcodeInstr::mksimple(0x59, 8, "ROTREV", exec_rotrev))
.insert(OpcodeInstr::mksimple(0x5a, 8, "2SWAP", exec_2swap))
.insert(OpcodeInstr::mksimple(0x5b, 8, "2DROP", exec_2drop))
.insert(OpcodeInstr::mksimple(0x5c, 8, "2DUP", exec_2dup))
.insert(OpcodeInstr::mksimple(0x5d, 8, "2OVER", exec_2over))
.insert(OpcodeInstr::mkfixed(0x5e, 8, 8, instr::dump_2c_add(0x20, "REVERSE ", ","), exec_reverse))
.insert(OpcodeInstr::mkfixed(0x5f0, 12, 4, instr::dump_1c("BLKDROP "), exec_blkdrop))
.insert(OpcodeInstr::mkfixedrange(0x5f10, 0x6000, 16, 8, instr::dump_2c("BLKPUSH ", ","), exec_blkpush))
.insert(OpcodeInstr::mksimple(0x60, 8, "PICK", exec_pick))
.insert(OpcodeInstr::mksimple(0x61, 8, "ROLL", exec_roll))
.insert(OpcodeInstr::mksimple(0x62, 8, "ROLLREV", exec_rollrev))
.insert(OpcodeInstr::mksimple(0x63, 8, "BLKSWX", exec_blkswap_x))
.insert(OpcodeInstr::mksimple(0x64, 8, "REVX", exec_reverse_x))
.insert(OpcodeInstr::mksimple(0x65, 8, "DROPX", exec_drop_x))
.insert(OpcodeInstr::mksimple(0x66, 8, "TUCK", exec_tuck))
.insert(OpcodeInstr::mksimple(0x67, 8, "XCHGX", exec_xchg_x))
.insert(OpcodeInstr::mksimple(0x68, 8, "DEPTH", exec_depth))
.insert(OpcodeInstr::mksimple(0x69, 8, "CHKDEPTH", exec_chkdepth))
.insert(OpcodeInstr::mksimple(0x6a, 8, "ONLYTOPX", exec_onlytop_x))
.insert(OpcodeInstr::mksimple(0x6b, 8, "ONLYX", exec_only_x));
}
} // namespace vm

27
crypto/vm/stackops.h Normal file
View file

@ -0,0 +1,27 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_stack_ops(OpcodeTable& cp0);
} // namespace vm

675
crypto/vm/tonops.cpp Normal file
View file

@ -0,0 +1,675 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <functional>
#include "vm/tonops.h"
#include "vm/log.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
#include "vm/dict.h"
#include "Ed25519.h"
namespace vm {
namespace {
bool debug(const char* str) TD_UNUSED;
bool debug(const char* str) {
std::cerr << str;
return true;
}
bool debug(int x) TD_UNUSED;
bool debug(int x) {
if (x < 100) {
std::cerr << '[' << (char)(64 + x) << ']';
} else {
std::cerr << '[' << (char)(64 + x / 100) << x % 100 << ']';
}
return true;
}
} // namespace
#define DBG_START int dbg = 0;
#define DBG debug(++dbg)&&
#define DEB_START DBG_START
#define DEB DBG
int exec_set_gas_generic(VmState* st, long long new_gas_limit) {
if (new_gas_limit < st->gas_consumed()) {
throw VmNoGas{};
}
st->change_gas_limit(new_gas_limit);
return 0;
}
int exec_accept(VmState* st) {
VM_LOG(st) << "execute ACCEPT";
return exec_set_gas_generic(st, GasLimits::infty);
}
int exec_set_gas_limit(VmState* st) {
VM_LOG(st) << "execute SETGASLIMIT";
td::RefInt256 x = st->get_stack().pop_int_finite();
long long gas = 0;
if (x->sgn() > 0) {
gas = x->unsigned_fits_bits(63) ? x->to_long() : GasLimits::infty;
}
return exec_set_gas_generic(st, gas);
}
void register_basic_gas_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept))
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit));
}
void register_ton_gas_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
}
int exec_get_param(VmState* st, unsigned idx, const char* name) {
if (name) {
VM_LOG(st) << "execute " << name;
}
Stack& stack = st->get_stack();
auto tuple = st->get_c7();
auto t1 = tuple_index(*tuple, 0).as_tuple_range(255);
if (t1.is_null()) {
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
}
stack.push(tuple_index(*t1, idx));
return 0;
}
int exec_get_var_param(VmState* st, unsigned idx) {
idx &= 15;
VM_LOG(st) << "execute GETPARAM " << idx;
return exec_get_param(st, idx, nullptr);
}
int exec_get_config_dict(VmState* st) {
exec_get_param(st, 9, "CONFIGDICT");
st->get_stack().push_smallint(32);
return 0;
}
int exec_get_config_param(VmState* st, bool opt) {
VM_LOG(st) << "execute CONFIG" << (opt ? "OPTPARAM" : "PARAM");
Stack& stack = st->get_stack();
auto idx = stack.pop_int();
exec_get_param(st, 9, nullptr);
Dictionary dict{stack.pop_maybe_cell(), 32};
td::BitArray<32> key;
Ref<vm::Cell> value;
if (idx->export_bits(key.bits(), key.size(), true)) {
value = dict.lookup_ref(key);
}
if (opt) {
stack.push_maybe_cell(std::move(value));
} else if (value.not_null()) {
stack.push_cell(std::move(value));
stack.push_bool(true);
} else {
stack.push_bool(false);
}
return 0;
}
int exec_get_global_common(VmState* st, unsigned n) {
st->get_stack().push(tuple_extend_index(st->get_c7(), n));
return 0;
}
int exec_get_global(VmState* st, unsigned args) {
args &= 31;
VM_LOG(st) << "execute GETGLOB " << args;
return exec_get_global_common(st, args);
}
int exec_get_global_var(VmState* st) {
VM_LOG(st) << "execute GETGLOBVAR";
st->check_underflow(1);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_get_global_common(st, args);
}
int exec_set_global_common(VmState* st, unsigned idx) {
Stack& stack = st->get_stack();
auto x = stack.pop();
auto tuple = st->get_c7();
if (idx >= 255) {
throw VmError{Excno::range_chk, "tuple index out of range"};
}
auto tpay = tuple_extend_set_index(tuple, idx, std::move(x));
if (tpay > 0) {
st->consume_tuple_gas(tpay);
}
st->set_c7(std::move(tuple));
return 0;
}
int exec_set_global(VmState* st, unsigned args) {
args &= 31;
VM_LOG(st) << "execute SETGLOB " << args;
st->check_underflow(1);
return exec_set_global_common(st, args);
}
int exec_set_global_var(VmState* st) {
VM_LOG(st) << "execute SETGLOBVAR";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_set_global_common(st, args);
}
void register_ton_config_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mkfixedrange(0xf820, 0xf823, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
.insert(OpcodeInstr::mksimple(0xf823, 16, "NOW", std::bind(exec_get_param, _1, 3, "NOW")))
.insert(OpcodeInstr::mksimple(0xf824, 16, "BLOCKLT", std::bind(exec_get_param, _1, 4, "BLOCKLT")))
.insert(OpcodeInstr::mksimple(0xf825, 16, "LTIME", std::bind(exec_get_param, _1, 5, "LTIME")))
.insert(OpcodeInstr::mkfixedrange(0xf826, 0xf828, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
.insert(OpcodeInstr::mksimple(0xf828, 16, "MYADDR", std::bind(exec_get_param, _1, 8, "MYADDR")))
.insert(OpcodeInstr::mksimple(0xf829, 16, "CONFIGROOT", std::bind(exec_get_param, _1, 9, "CONFIGROOT")))
.insert(OpcodeInstr::mkfixedrange(0xf82a, 0xf830, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
.insert(OpcodeInstr::mksimple(0xf830, 16, "CONFIGDICT", exec_get_config_dict))
.insert(OpcodeInstr::mksimple(0xf832, 16, "CONFIGPARAM", std::bind(exec_get_config_param, _1, false)))
.insert(OpcodeInstr::mksimple(0xf833, 16, "CONFIGOPTPARAM", std::bind(exec_get_config_param, _1, true)))
.insert(OpcodeInstr::mksimple(0xf840, 16, "GETGLOBVAR", exec_get_global_var))
.insert(OpcodeInstr::mkfixedrange(0xf841, 0xf860, 16, 5, instr::dump_1c_and(31, "GETGLOB "), exec_get_global))
.insert(OpcodeInstr::mksimple(0xf860, 16, "SETGLOBVAR", exec_set_global_var))
.insert(OpcodeInstr::mkfixedrange(0xf861, 0xf880, 16, 5, instr::dump_1c_and(31, "SETGLOB "), exec_set_global));
}
int exec_compute_hash(VmState* st, int mode) {
VM_LOG(st) << "execute HASH" << (mode & 1 ? 'S' : 'C') << 'U';
Stack& stack = st->get_stack();
std::array<unsigned char, 32> hash;
if (!(mode & 1)) {
auto cell = stack.pop_cell();
hash = cell->get_hash().as_array();
} else {
auto cs = stack.pop_cellslice();
vm::CellBuilder cb;
CHECK(cb.append_cellslice_bool(std::move(cs)));
// TODO: use cb.get_hash() instead
hash = cb.finalize()->get_hash().as_array();
}
td::RefInt256 res{true};
CHECK(res.write().import_bytes(hash.data(), hash.size(), false));
stack.push_int(std::move(res));
return 0;
}
int exec_compute_sha256(VmState* st) {
VM_LOG(st) << "execute SHA256U";
Stack& stack = st->get_stack();
auto cs = stack.pop_cellslice();
if (cs->size() & 7) {
throw VmError{Excno::cell_und, "Slice does not consist of an integer number of bytes"};
}
auto len = (cs->size() >> 3);
unsigned char data[128], hash[32];
CHECK(len <= sizeof(data));
CHECK(cs->prefetch_bytes(data, len));
digest::hash_str<digest::SHA256>(hash, data, len);
td::RefInt256 res{true};
CHECK(res.write().import_bytes(hash, 32, false));
stack.push_int(std::move(res));
return 0;
}
int exec_ed25519_check_signature(VmState* st, bool from_slice) {
VM_LOG(st) << "execute CHKSIGN" << (from_slice ? 'S' : 'U');
Stack& stack = st->get_stack();
stack.check_underflow(3);
auto key_int = stack.pop_int();
auto signature_cs = stack.pop_cellslice();
unsigned char data[128], key[32], signature[64];
unsigned data_len;
if (from_slice) {
auto cs = stack.pop_cellslice();
if (cs->size() & 7) {
throw VmError{Excno::cell_und, "Slice does not consist of an integer number of bytes"};
}
data_len = (cs->size() >> 3);
CHECK(data_len <= sizeof(data));
CHECK(cs->prefetch_bytes(data, data_len));
} else {
auto hash_int = stack.pop_int();
data_len = 32;
if (!hash_int->export_bytes(data, data_len, false)) {
throw VmError{Excno::range_chk, "data hash must fit in an unsigned 256-bit integer"};
}
}
if (!signature_cs->prefetch_bytes(signature, 64)) {
throw VmError{Excno::cell_und, "Ed25519 signature must contain at least 512 data bits"};
}
if (!key_int->export_bytes(key, 32, false)) {
throw VmError{Excno::range_chk, "Ed25519 public key must fit in an unsigned 256-bit integer"};
}
td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})};
auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64});
stack.push_bool(res.is_ok());
return 0;
}
void register_ton_crypto_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xf900, 16, "HASHCU", std::bind(exec_compute_hash, _1, 0)))
.insert(OpcodeInstr::mksimple(0xf901, 16, "HASHSU", std::bind(exec_compute_hash, _1, 1)))
.insert(OpcodeInstr::mksimple(0xf902, 16, "SHA256U", exec_compute_sha256))
.insert(OpcodeInstr::mksimple(0xf910, 16, "CHKSIGNU", std::bind(exec_ed25519_check_signature, _1, false)))
.insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true)));
}
int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) {
if (len_bits == 4 && !sgnd) {
VM_LOG(st) << "execute LDGRAMS" << (quiet ? "Q" : "");
} else {
VM_LOG(st) << "execute LDVAR" << (sgnd ? "" : "U") << "INT" << (1 << len_bits) << (quiet ? "Q" : "");
}
Stack& stack = st->get_stack();
auto csr = stack.pop_cellslice();
td::RefInt256 x;
int len;
if (!(csr.write().fetch_uint_to(len_bits, len) && csr.unique_write().fetch_int256_to(len * 8, x, sgnd))) {
if (quiet) {
stack.push_bool(false);
} else {
throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"};
}
} else {
stack.push_int(std::move(x));
stack.push_cellslice(std::move(csr));
if (quiet) {
stack.push_bool(true);
}
}
return 0;
}
int exec_store_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) {
if (len_bits == 4 && !sgnd) {
VM_LOG(st) << "execute STGRAMS" << (quiet ? "Q" : "");
} else {
VM_LOG(st) << "execute STVAR" << (sgnd ? "" : "U") << "INT" << (1 << len_bits) << (quiet ? "Q" : "");
}
Stack& stack = st->get_stack();
stack.check_underflow(2);
auto x = stack.pop_int();
auto cbr = stack.pop_builder();
unsigned len = ((x->bit_size(sgnd) + 7) >> 3);
if (len >= (1u << len_bits)) {
throw VmError{Excno::range_chk};
}
if (!(cbr.write().store_long_bool(len, len_bits) && cbr.unique_write().store_int256_bool(*x, len * 8, sgnd))) {
if (quiet) {
stack.push_bool(false);
} else {
throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"};
}
} else {
stack.push_builder(std::move(cbr));
if (quiet) {
stack.push_bool(true);
}
}
return 0;
}
bool skip_maybe_anycast(CellSlice& cs) {
if (cs.prefetch_ulong(1) != 1) {
return cs.advance(1);
}
unsigned depth;
return cs.advance(1) // just$1
&& cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30)
&& depth >= 1 // { depth >= 1 }
&& cs.advance(depth); // rewrite_pfx:(bits depth) = Anycast;
}
bool skip_message_addr(CellSlice& cs) {
switch ((unsigned)cs.fetch_ulong(2)) {
case 0: // addr_none$00 = MsgAddressExt;
return true;
case 1: { // addr_extern$01
unsigned len;
return cs.fetch_uint_to(9, len) // len:(## 9)
&& cs.advance(len); // external_address:(bits len) = MsgAddressExt;
}
case 2: { // addr_std$10
return skip_maybe_anycast(cs) // anycast:(Maybe Anycast)
&& cs.advance(8 + 256); // workchain_id:int8 address:bits256 = MsgAddressInt;
}
case 3: { // addr_var$11
unsigned len;
return skip_maybe_anycast(cs) // anycast:(Maybe Anycast)
&& cs.fetch_uint_to(9, len) // addr_len:(## 9)
&& cs.advance(32 + len); // workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
}
default:
return false;
}
}
int exec_load_message_addr(VmState* st, bool quiet) {
VM_LOG(st) << "execute LDMSGADDR" << (quiet ? "Q" : "");
Stack& stack = st->get_stack();
auto csr = stack.pop_cellslice(), csr_copy = csr;
auto& cs = csr.write();
if (!(skip_message_addr(cs) && csr_copy.write().cut_tail(cs))) {
csr.clear();
if (quiet) {
stack.push_cellslice(std::move(csr_copy));
stack.push_bool(false);
} else {
throw VmError{Excno::cell_und, "cannot load a MsgAddress"};
}
} else {
stack.push_cellslice(std::move(csr_copy));
stack.push_cellslice(std::move(csr));
if (quiet) {
stack.push_bool(true);
}
}
return 0;
}
bool parse_maybe_anycast(CellSlice& cs, StackEntry& res) {
res = StackEntry{};
if (cs.prefetch_ulong(1) != 1) {
return cs.advance(1);
}
unsigned depth;
Ref<CellSlice> pfx;
if (cs.advance(1) // just$1
&& cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30)
&& depth >= 1 // { depth >= 1 }
&& cs.fetch_subslice_to(depth, pfx)) { // rewrite_pfx:(bits depth) = Anycast;
res = std::move(pfx);
return true;
}
return false;
}
bool parse_message_addr(CellSlice& cs, std::vector<StackEntry>& res) {
res.clear();
switch ((unsigned)cs.fetch_ulong(2)) {
case 0: // addr_none$00 = MsgAddressExt;
res.emplace_back(td::RefInt256{true, 0}); // -> (0)
return true;
case 1: { // addr_extern$01
unsigned len;
Ref<CellSlice> addr;
if (cs.fetch_uint_to(9, len) // len:(## 9)
&& cs.fetch_subslice_to(len, addr)) { // external_address:(bits len) = MsgAddressExt;
res.emplace_back(td::RefInt256{true, 1});
res.emplace_back(std::move(addr));
return true;
}
break;
}
case 2: { // addr_std$10
StackEntry v;
int workchain;
Ref<CellSlice> addr;
if (parse_maybe_anycast(cs, v) // anycast:(Maybe Anycast)
&& cs.fetch_int_to(8, workchain) // workchain_id:int8
&& cs.fetch_subslice_to(256, addr)) { // address:bits256 = MsgAddressInt;
res.emplace_back(td::RefInt256{true, 2});
res.emplace_back(std::move(v));
res.emplace_back(td::RefInt256{true, workchain});
res.emplace_back(std::move(addr));
return true;
}
break;
}
case 3: { // addr_var$11
StackEntry v;
int len, workchain;
Ref<CellSlice> addr;
if (parse_maybe_anycast(cs, v) // anycast:(Maybe Anycast)
&& cs.fetch_uint_to(9, len) // addr_len:(## 9)
&& cs.fetch_int_to(32, workchain) // workchain_id:int32
&& cs.fetch_subslice_to(len, addr)) { // address:(bits addr_len) = MsgAddressInt;
res.emplace_back(td::RefInt256{true, 3});
res.emplace_back(std::move(v));
res.emplace_back(td::RefInt256{true, workchain});
res.emplace_back(std::move(addr));
return true;
}
break;
}
}
return false;
}
int exec_parse_message_addr(VmState* st, bool quiet) {
VM_LOG(st) << "execute PARSEMSGADDR" << (quiet ? "Q" : "");
Stack& stack = st->get_stack();
auto csr = stack.pop_cellslice();
auto& cs = csr.write();
std::vector<StackEntry> res;
if (!(parse_message_addr(cs, res) && cs.empty_ext())) {
if (quiet) {
stack.push_bool(false);
} else {
throw VmError{Excno::cell_und, "cannot parse a MsgAddress"};
}
} else {
stack.push_tuple(std::move(res));
if (quiet) {
stack.push_bool(true);
}
}
return 0;
}
// replaces first bits of `addr` with those of `prefix`
Ref<CellSlice> do_rewrite_addr(Ref<CellSlice> addr, Ref<CellSlice> prefix) {
if (prefix.is_null() || !prefix->size()) {
return std::move(addr);
}
if (prefix->size() > addr->size()) {
return {};
}
if (prefix->size() == addr->size()) {
return std::move(prefix);
}
vm::CellBuilder cb;
if (!(addr.write().advance(prefix->size()) && cb.append_cellslice_bool(std::move(prefix)) &&
cb.append_cellslice_bool(std::move(addr)))) {
return {};
}
return vm::load_cell_slice_ref(cb.finalize());
}
int exec_rewrite_message_addr(VmState* st, bool allow_var_addr, bool quiet) {
VM_LOG(st) << "execute REWRITE" << (allow_var_addr ? "VAR" : "STD") << "ADDR" << (quiet ? "Q" : "");
Stack& stack = st->get_stack();
auto csr = stack.pop_cellslice();
auto& cs = csr.write();
std::vector<StackEntry> tuple;
if (!(parse_message_addr(cs, tuple) && cs.empty_ext())) {
if (quiet) {
stack.push_bool(false);
return 0;
}
throw VmError{Excno::cell_und, "cannot parse a MsgAddress"};
}
int t = (int)std::move(tuple[0]).as_int()->to_long();
if (t != 2 && t != 3) {
if (quiet) {
stack.push_bool(false);
return 0;
}
throw VmError{Excno::cell_und, "cannot parse a MsgAddressInt"};
}
auto addr = std::move(tuple[3]).as_slice();
auto prefix = std::move(tuple[1]).as_slice();
if (!allow_var_addr) {
if (addr->size() != 256) {
if (quiet) {
stack.push_bool(false);
return 0;
}
throw VmError{Excno::cell_und, "MsgAddressInt is not a standard 256-bit address"};
}
td::Bits256 rw_addr;
td::RefInt256 int_addr{true};
CHECK(addr->prefetch_bits_to(rw_addr) &&
(prefix.is_null() || prefix->prefetch_bits_to(rw_addr.bits(), prefix->size())) &&
int_addr.unique_write().import_bits(rw_addr, false));
stack.push(std::move(tuple[2]));
stack.push(std::move(int_addr));
} else {
addr = do_rewrite_addr(std::move(addr), std::move(prefix));
if (addr.is_null()) {
if (quiet) {
stack.push_bool(false);
return 0;
}
throw VmError{Excno::cell_und, "cannot rewrite address in a MsgAddressInt"};
}
stack.push(std::move(tuple[2]));
stack.push(std::move(addr));
}
if (quiet) {
stack.push_bool(true);
}
return 0;
}
void register_ton_currency_address_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xfa00, 16, "LDGRAMS", std::bind(exec_load_var_integer, _1, 4, false, false)))
.insert(OpcodeInstr::mksimple(0xfa01, 16, "LDVARINT16", std::bind(exec_load_var_integer, _1, 4, true, false)))
.insert(OpcodeInstr::mksimple(0xfa02, 16, "STGRAMS", std::bind(exec_store_var_integer, _1, 4, false, false)))
.insert(OpcodeInstr::mksimple(0xfa03, 16, "STVARINT16", std::bind(exec_store_var_integer, _1, 4, true, false)))
.insert(OpcodeInstr::mksimple(0xfa04, 16, "LDVARUINT32", std::bind(exec_load_var_integer, _1, 5, false, false)))
.insert(OpcodeInstr::mksimple(0xfa05, 16, "LDVARINT32", std::bind(exec_load_var_integer, _1, 5, true, false)))
.insert(OpcodeInstr::mksimple(0xfa06, 16, "STVARUINT32", std::bind(exec_store_var_integer, _1, 5, false, false)))
.insert(OpcodeInstr::mksimple(0xfa07, 16, "STVARINT32", std::bind(exec_store_var_integer, _1, 5, true, false)))
.insert(OpcodeInstr::mksimple(0xfa40, 16, "LDMSGADDR", std::bind(exec_load_message_addr, _1, false)))
.insert(OpcodeInstr::mksimple(0xfa41, 16, "LDMSGADDRQ", std::bind(exec_load_message_addr, _1, true)))
.insert(OpcodeInstr::mksimple(0xfa42, 16, "PARSEMSGADDR", std::bind(exec_parse_message_addr, _1, false)))
.insert(OpcodeInstr::mksimple(0xfa43, 16, "PARSEMSGADDRQ", std::bind(exec_parse_message_addr, _1, true)))
.insert(
OpcodeInstr::mksimple(0xfa44, 16, "REWRITESTDADDR", std::bind(exec_rewrite_message_addr, _1, false, false)))
.insert(
OpcodeInstr::mksimple(0xfa45, 16, "REWRITESTDADDRQ", std::bind(exec_rewrite_message_addr, _1, false, true)))
.insert(
OpcodeInstr::mksimple(0xfa46, 16, "REWRITEVARADDR", std::bind(exec_rewrite_message_addr, _1, true, false)))
.insert(
OpcodeInstr::mksimple(0xfa47, 16, "REWRITEVARADDRQ", std::bind(exec_rewrite_message_addr, _1, true, true)));
}
static constexpr int output_actions_idx = 5;
int install_output_action(VmState* st, Ref<Cell> new_action_head) {
// TODO: increase actions:uint16 and msgs_sent:uint16 in SmartContractInfo at first reference of c5
VM_LOG(st) << "installing an output action";
st->set_d(output_actions_idx, std::move(new_action_head));
return 0;
}
static inline Ref<Cell> get_actions(VmState* st) {
return st->get_d(output_actions_idx);
}
int exec_send_raw_message(VmState* st) {
VM_LOG(st) << "execute SENDRAWMSG";
Stack& stack = st->get_stack();
stack.check_underflow(2);
int f = stack.pop_smallint_range(255);
Ref<Cell> msg_cell = stack.pop_cell();
CellBuilder cb;
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
&& cb.store_long_bool(0x0ec3c86d, 32) // action_send_msg#0ec3c86d
&& cb.store_long_bool(f, 8) // mode:(## 8)
&& cb.store_ref_bool(std::move(msg_cell)))) {
throw VmError{Excno::cell_ov, "cannot serialize raw output message into an output action cell"};
}
return install_output_action(st, cb.finalize());
}
bool store_grams(CellBuilder& cb, td::RefInt256 value) {
int k = value->bit_size(false);
return k <= 15 * 8 && cb.store_long_bool((k + 7) >> 3, 4) && cb.store_int256_bool(*value, (k + 7) & -8, false);
}
int exec_reserve_raw(VmState* st, int mode) {
VM_LOG(st) << "execute RESERVERAW" << (mode & 1 ? "X" : "");
Stack& stack = st->get_stack();
stack.check_underflow(2);
int f = stack.pop_smallint_range(3);
td::RefInt256 x;
Ref<CellSlice> csr;
if (mode & 1) {
csr = stack.pop_cellslice();
} else {
x = stack.pop_int_finite();
if (td::sgn(x) < 0) {
throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"};
}
}
CellBuilder cb;
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
&& cb.store_long_bool(0x36e6b809, 32) // action_reserve_currency#36e6b809
&& cb.store_long_bool(f, 8) // mode:(## 8)
&& (mode & 1 ? cb.append_cellslice_bool(std::move(csr))
: (store_grams(cb, std::move(x)) && cb.store_bool_bool(false))))) {
throw VmError{Excno::cell_ov, "cannot serialize raw reserved currency amount into an output action cell"};
}
return install_output_action(st, cb.finalize());
}
int exec_set_code(VmState* st) {
VM_LOG(st) << "execute SETCODE";
auto code = st->get_stack().pop_cell();
CellBuilder cb;
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
&& cb.store_long_bool(0xad4de08e, 32) // action_set_code#ad4de08e
&& cb.store_ref_bool(std::move(code)))) { // new_code:^Cell = OutAction;
throw VmError{Excno::cell_ov, "cannot serialize new smart contract code into an output action cell"};
}
return install_output_action(st, cb.finalize());
}
void register_ton_message_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xfb00, 16, "SENDRAWMSG", exec_send_raw_message))
.insert(OpcodeInstr::mksimple(0xfb02, 16, "RESERVERAW", std::bind(exec_reserve_raw, _1, 0)))
.insert(OpcodeInstr::mksimple(0xfb03, 16, "RESERVERAWX", std::bind(exec_reserve_raw, _1, 1)))
.insert(OpcodeInstr::mksimple(0xfb04, 16, "SETCODE", exec_set_code));
}
void register_ton_ops(OpcodeTable& cp0) {
register_basic_gas_ops(cp0);
register_ton_gas_ops(cp0);
register_ton_config_ops(cp0);
register_ton_crypto_ops(cp0);
register_ton_currency_address_ops(cp0);
register_ton_message_ops(cp0);
}
} // namespace vm

27
crypto/vm/tonops.h Normal file
View file

@ -0,0 +1,27 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_ton_ops(OpcodeTable& cp0);
} // namespace vm

381
crypto/vm/tupleops.cpp Normal file
View file

@ -0,0 +1,381 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/log.h"
#include "vm/stackops.h"
#include "vm/opctable.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "vm/excno.hpp"
namespace vm {
int exec_push_null(VmState* st) {
VM_LOG(st) << "execute PUSHNULL";
st->get_stack().push({});
return 0;
}
int exec_is_null(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ISNULL";
stack.push_bool(stack.pop_chk().empty());
return 0;
}
int exec_null_swap_if(VmState* st, bool cond, int depth) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute NULL" << (depth ? "ROTR" : "SWAP") << (cond ? "IF" : "IFNOT");
stack.check_underflow(depth + 1);
auto x = stack.pop_int_finite();
if (!x->sgn() != cond) {
stack.push({});
for (int i = 0; i < depth; i++) {
swap(stack[i], stack[i + 1]);
}
}
stack.push_int(std::move(x));
return 0;
}
int exec_mktuple_common(VmState* st, unsigned n) {
Stack& stack = st->get_stack();
stack.check_underflow(n);
Ref<Tuple> ref{true};
auto& tuple = ref.unique_write();
tuple.reserve(n);
for (int i = n - 1; i >= 0; i--) {
tuple.push_back(std::move(stack[i]));
}
stack.pop_many(n);
st->consume_tuple_gas(n);
stack.push_tuple(std::move(ref));
return 0;
}
int exec_mktuple(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute TUPLE " << args;
return exec_mktuple_common(st, args);
}
int exec_mktuple_var(VmState* st) {
VM_LOG(st) << "execute TUPLEVAR";
unsigned args = st->get_stack().pop_smallint_range(255);
return exec_mktuple_common(st, args);
}
int exec_tuple_index_common(Stack& stack, unsigned n) {
auto tuple = stack.pop_tuple_range(255);
stack.push(tuple_index(*tuple, n));
return 0;
}
int exec_tuple_index(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute INDEX " << args;
return exec_tuple_index_common(st->get_stack(), args);
}
int exec_tuple_index_var(VmState* st) {
VM_LOG(st) << "execute INDEXVAR";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_tuple_index_common(st->get_stack(), args);
}
int exec_tuple_quiet_index_common(Stack& stack, unsigned n) {
stack.push(tuple_extend_index(stack.pop_maybe_tuple_range(255), n));
return 0;
}
int exec_tuple_quiet_index(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute INDEXQ " << args;
return exec_tuple_quiet_index_common(st->get_stack(), args);
}
int exec_tuple_quiet_index_var(VmState* st) {
VM_LOG(st) << "execute INDEXVARQ";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_tuple_quiet_index_common(st->get_stack(), args);
}
int do_explode_tuple(VmState* st, Ref<Tuple> tuple, unsigned n) {
auto& stack = st->get_stack();
if (tuple.is_unique()) {
auto& tw = tuple.unique_write();
for (unsigned i = 0; i < n; i++) {
stack.push(std::move(tw[i]));
}
} else {
const auto& t = *tuple;
for (unsigned i = 0; i < n; i++) {
stack.push(t[i]);
}
}
st->consume_tuple_gas(n);
return 0;
}
int exec_untuple_common(VmState* st, unsigned n) {
return do_explode_tuple(st, st->get_stack().pop_tuple_range(n, n), n);
}
int exec_untuple(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute UNTUPLE " << args;
return exec_untuple_common(st, args);
}
int exec_untuple_var(VmState* st) {
VM_LOG(st) << "execute UNTUPLEVAR";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(255);
return exec_untuple_common(st, args);
}
int exec_untuple_first_common(VmState* st, unsigned n) {
return do_explode_tuple(st, st->get_stack().pop_tuple_range(255, n), n);
}
int exec_untuple_first(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute UNPACKFIRST " << args;
return exec_untuple_first_common(st, args);
}
int exec_untuple_first_var(VmState* st) {
VM_LOG(st) << "execute UNPACKFIRSTVAR";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(255);
return exec_untuple_first_common(st, args);
}
int exec_explode_tuple_common(VmState* st, unsigned n) {
auto t = st->get_stack().pop_tuple_range(n);
unsigned l = (unsigned)(t->size());
do_explode_tuple(st, std::move(t), l);
st->get_stack().push_smallint(l);
return 0;
}
int exec_explode_tuple(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute EXPLODE " << args;
return exec_explode_tuple_common(st, args);
}
int exec_explode_tuple_var(VmState* st) {
VM_LOG(st) << "execute EXPLODEVAR";
st->check_underflow(2);
unsigned args = st->get_stack().pop_smallint_range(255);
return exec_explode_tuple_common(st, args);
}
int exec_tuple_set_index_common(VmState* st, unsigned idx) {
Stack& stack = st->get_stack();
auto x = stack.pop();
auto tuple = stack.pop_tuple_range(255);
if (idx >= tuple->size()) {
throw VmError{Excno::range_chk, "tuple index out of range"};
}
tuple.write()[idx] = std::move(x);
st->consume_tuple_gas(tuple);
stack.push(std::move(tuple));
return 0;
}
int exec_tuple_set_index(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute SETINDEX " << args;
st->check_underflow(2);
return exec_tuple_set_index_common(st, args);
}
int exec_tuple_set_index_var(VmState* st) {
VM_LOG(st) << "execute SETINDEXVAR";
st->check_underflow(3);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_tuple_set_index_common(st, args);
}
int exec_tuple_quiet_set_index_common(VmState* st, unsigned idx) {
Stack& stack = st->get_stack();
auto x = stack.pop();
auto tuple = stack.pop_maybe_tuple_range(255);
if (idx >= 255) {
throw VmError{Excno::range_chk, "tuple index out of range"};
}
auto tpay = tuple_extend_set_index(tuple, idx, std::move(x));
if (tpay > 0) {
st->consume_tuple_gas(tpay);
}
stack.push_maybe_tuple(std::move(tuple));
return 0;
}
int exec_tuple_quiet_set_index(VmState* st, unsigned args) {
args &= 15;
VM_LOG(st) << "execute SETINDEXQ " << args;
st->check_underflow(2);
return exec_tuple_quiet_set_index_common(st, args);
}
int exec_tuple_quiet_set_index_var(VmState* st) {
VM_LOG(st) << "execute SETINDEXVARQ";
st->check_underflow(3);
unsigned args = st->get_stack().pop_smallint_range(254);
return exec_tuple_quiet_set_index_common(st, args);
}
int exec_tuple_length(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute TLEN";
auto t = stack.pop_tuple_range(255);
stack.push_smallint((long long)(t->size()));
return 0;
}
int exec_tuple_length_quiet(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute QTLEN";
auto t = stack.pop_chk();
stack.push_smallint(t.is_tuple() ? (long long)(t.as_tuple()->size()) : -1LL);
return 0;
}
int exec_is_tuple(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute ISTUPLE";
stack.push_bool(stack.pop_chk().is_tuple());
return 0;
}
int exec_tuple_last(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute LAST";
auto t = stack.pop_tuple_range(255, 1);
stack.push(t->back());
return 0;
}
int exec_tuple_push(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute TPUSH";
stack.check_underflow(2);
auto x = stack.pop();
auto t = stack.pop_tuple_range(254);
t.write().push_back(std::move(x));
st->consume_tuple_gas(t);
stack.push(std::move(t));
return 0;
}
int exec_tuple_pop(VmState* st) {
Stack& stack = st->get_stack();
VM_LOG(st) << "execute TPOP";
auto t = stack.pop_tuple_range(255, 1);
auto x = std::move(t.write().back());
t.write().pop_back();
st->consume_tuple_gas(t);
stack.push(std::move(t));
stack.push(std::move(x));
return 0;
}
int exec_tuple_index2(VmState* st, unsigned args) {
unsigned i = (args >> 2) & 3, j = args & 3;
VM_LOG(st) << "execute INDEX2 " << i << "," << j;
Stack& stack = st->get_stack();
auto tuple = stack.pop_tuple_range(255);
auto t1 = tuple_index(*tuple, i).as_tuple_range(255);
if (t1.is_null()) {
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
}
stack.push(tuple_index(*t1, j));
return 0;
}
std::string dump_tuple_index2(CellSlice& cs, unsigned args) {
unsigned i = (args >> 2) & 3, j = args & 3;
std::ostringstream os;
os << "INDEX2 " << i << ',' << j;
return os.str();
}
int exec_tuple_index3(VmState* st, unsigned args) {
unsigned i = (args >> 4) & 3, j = (args >> 2) & 3, k = args & 3;
VM_LOG(st) << "execute INDEX3 " << i << "," << j << "," << k;
Stack& stack = st->get_stack();
auto tuple = stack.pop_tuple_range(255);
auto t1 = tuple_index(*tuple, i).as_tuple_range(255);
if (t1.is_null()) {
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
}
auto t2 = tuple_index(*t1, j).as_tuple_range(255);
if (t2.is_null()) {
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
}
stack.push(tuple_index(*t2, k));
return 0;
}
std::string dump_tuple_index3(CellSlice& cs, unsigned args) {
unsigned i = (args >> 4) & 3, j = (args >> 2) & 3, k = args & 3;
std::ostringstream os;
os << "INDEX3 " << i << ',' << j << ',' << k;
return os.str();
}
void register_tuple_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0x6d, 8, "PUSHNULL", exec_push_null))
.insert(OpcodeInstr::mksimple(0x6e, 8, "ISNULL", exec_is_null))
.insert(OpcodeInstr::mkfixed(0x6f0, 12, 4, instr::dump_1c("TUPLE "), exec_mktuple))
.insert(OpcodeInstr::mkfixed(0x6f1, 12, 4, instr::dump_1c("INDEX "), exec_tuple_index))
.insert(OpcodeInstr::mkfixed(0x6f2, 12, 4, instr::dump_1c("UNTUPLE "), exec_untuple))
.insert(OpcodeInstr::mkfixed(0x6f3, 12, 4, instr::dump_1c("UNPACKFIRST "), exec_untuple_first))
.insert(OpcodeInstr::mkfixed(0x6f4, 12, 4, instr::dump_1c("EXPLODE "), exec_explode_tuple))
.insert(OpcodeInstr::mkfixed(0x6f5, 12, 4, instr::dump_1c("SETINDEX "), exec_tuple_set_index))
.insert(OpcodeInstr::mkfixed(0x6f6, 12, 4, instr::dump_1c("INDEXQ "), exec_tuple_quiet_index))
.insert(OpcodeInstr::mkfixed(0x6f7, 12, 4, instr::dump_1c("SETINDEXQ "), exec_tuple_quiet_set_index))
.insert(OpcodeInstr::mksimple(0x6f80, 16, "TUPLEVAR", exec_mktuple_var))
.insert(OpcodeInstr::mksimple(0x6f81, 16, "INDEXVAR", exec_tuple_index_var))
.insert(OpcodeInstr::mksimple(0x6f82, 16, "UNTUPLEVAR", exec_untuple_var))
.insert(OpcodeInstr::mksimple(0x6f83, 16, "UNPACKFIRSTVAR", exec_untuple_first_var))
.insert(OpcodeInstr::mksimple(0x6f84, 16, "EXPLODEVAR", exec_explode_tuple_var))
.insert(OpcodeInstr::mksimple(0x6f85, 16, "SETINDEXVAR", exec_tuple_set_index_var))
.insert(OpcodeInstr::mksimple(0x6f86, 16, "INDEXVARQ", exec_tuple_quiet_index_var))
.insert(OpcodeInstr::mksimple(0x6f87, 16, "SETINDEXVARQ", exec_tuple_quiet_set_index_var))
.insert(OpcodeInstr::mksimple(0x6f88, 16, "TLEN", exec_tuple_length))
.insert(OpcodeInstr::mksimple(0x6f89, 16, "QTLEN", exec_tuple_length_quiet))
.insert(OpcodeInstr::mksimple(0x6f8a, 16, "ISTUPLE", exec_is_tuple))
.insert(OpcodeInstr::mksimple(0x6f8b, 16, "LAST", exec_tuple_last))
.insert(OpcodeInstr::mksimple(0x6f8c, 16, "TPUSH", exec_tuple_push))
.insert(OpcodeInstr::mksimple(0x6f8d, 16, "TPOP", exec_tuple_pop))
.insert(OpcodeInstr::mksimple(0x6fa0, 16, "NULLSWAPIF", std::bind(exec_null_swap_if, _1, true, 0)))
.insert(OpcodeInstr::mksimple(0x6fa1, 16, "NULLSWAPIFNOT", std::bind(exec_null_swap_if, _1, false, 0)))
.insert(OpcodeInstr::mksimple(0x6fa2, 16, "NULLROTRIF", std::bind(exec_null_swap_if, _1, true, 1)))
.insert(OpcodeInstr::mksimple(0x6fa3, 16, "NULLROTRIFNOT", std::bind(exec_null_swap_if, _1, false, 1)))
.insert(OpcodeInstr::mkfixed(0x6fb, 12, 4, dump_tuple_index2, exec_tuple_index2))
.insert(OpcodeInstr::mkfixed(0x6fc >> 2, 10, 6, dump_tuple_index3, exec_tuple_index3));
}
} // namespace vm

27
crypto/vm/tupleops.h Normal file
View file

@ -0,0 +1,27 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
namespace vm {
class OpcodeTable;
void register_tuple_ops(OpcodeTable& cp0);
} // namespace vm

39
crypto/vm/vmstate.h Normal file
View file

@ -0,0 +1,39 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refcnt.hpp"
#include "vm/cells.h"
#include "td/utils/Context.h"
namespace vm {
using td::Ref;
class VmStateInterface : public td::Context<VmStateInterface> {
public:
virtual ~VmStateInterface() = default;
virtual Ref<vm::Cell> load_library(
td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found
return {};
}
virtual void register_cell_load(){};
virtual void register_cell_create(){};
};
} // namespace vm