1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 19:22:37 +00:00
ton/crypto/smartcont/dns-manual-code.fc

361 lines
13 KiB
Text

{-
Originally created by:
/------------------------------------------------------------------------\
| Created for: Telegram (Open Network) Blockchain Contest |
| Task 3: DNS Resolver (Manually controlled) |
>------------------------------------------------------------------------<
| Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) |
| October 2019 |
\------------------------------------------------------------------------/
Updated to actual DNS standard version by starlightduck in 2022
-}
;;===========================================================================;;
;; Custom ASM instructions ;;
;;===========================================================================;;
cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF";
(cell, ()) pfxdict_set_ref(cell dict, int key_len, slice key, cell value) {
throw_unless(33, dict~pfxdict_set?(key_len, key, begin_cell().store_maybe_ref(value).end_cell().begin_parse()));
return (dict, ());
}
(slice, cell, slice, int) pfxdict_get_ref(cell dict, int key_len, slice key) inline_ref {
(slice pfx, slice val, slice tail, int succ) = dict.pfxdict_get?(key_len, key);
cell res = succ ? val~load_maybe_ref() : null();
return (pfx, res, tail, succ);
}
;;===========================================================================;;
;; Utility functions ;;
;;===========================================================================;;
(int, int, int, cell, cell) load_data() inline_ref {
slice cs = get_data().begin_parse();
var res = (cs~load_uint(32), cs~load_uint(64), cs~load_uint(256), cs~load_dict(), cs~load_dict());
cs.end_parse();
return res;
}
() store_data(int contract_id, int last_cleaned, int public_key, cell root, old_queries) impure {
set_data(begin_cell()
.store_uint(contract_id, 32)
.store_uint(last_cleaned, 64)
.store_uint(public_key, 256)
.store_dict(root)
.store_dict(old_queries)
.end_cell());
}
;;===========================================================================;;
;; Internal message handler (Code 0) ;;
;;===========================================================================;;
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
;; not interested at all
}
;;===========================================================================;;
;; External message handler (Code -1) ;;
;;===========================================================================;;
{-
External message structure:
[Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation]
[Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name)
x depends on operation
Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name
Inline [Name] structure: [UInt<6b>:length] [Bytes<lengthB>:data]
Operations (continuation of message):
00 Contract initialization message (only if seqno = 0) (x=-)
11 VSet: set specified value to specified subdomain->category (x=2)
[UInt<256b>:category] [Name<?>:subdomain] [Cell<1r>:value]
12 VDel: delete specified subdomain->category (x=2)
[UInt<256b>:category] [Name<?>:subdomain]
21 DSet: replace entire category dictionary of domain with provided (x=0)
[Name<?>:subdomain] [Cell<1r>:new_cat_table]
22 DDel: delete entire category dictionary of specified domain (x=0)
[Name<?>:subdomain]
31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-)
[Cell<1r>:new_domains_table]
32 TDel: nullify ENTIRE DOMAIN TABLE (x=-)
51 OSet: replace owner public key with a new one (x=-)
[UInt<256b>:new_public_key]
-}
() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666);
(cell, slice) process_op(cell root, slice ops) inline_ref {
int op = ops~load_uint(6);
if (op < 10) {
ifnot (op) {
;; 00 Noop: No operation
return (root, ops);
}
if (op == 1) {
;; 01 SMsg: Send Message
var mode = ops~load_uint(8);
send_raw_message(ops~load_ref(), mode);
return (root, ops);
}
if (op == 9) {
;; 09 CodeUpgrade
var new_code = ops~load_ref();
set_code(new_code);
var old_code = get_c3();
set_c3(new_code.begin_parse().bless());
after_code_upgrade(root, ops, old_code);
throw(0);
return (root, ops);
}
throw(45);
return (root, ops);
}
int cat = 0;
if (op < 20) {
;; for operations with codes 10..19 category is required
cat = ops~load_uint(256); ;; update: category length now u256 instead of i16
}
slice name = null(); ;; any slice value
cell cat_table = null();
if (op < 30) {
;; for operations with codes 10..29 name is required
int is_name_ref = (ops~load_uint(1) == 1);
if (is_name_ref) {
;; name is stored in separate referenced cell
name = ops~load_ref().begin_parse();
} else {
;; name is stored inline
int name_len = ops~load_uint(6) * 8;
name = ops~load_bits(name_len);
}
;; at least one character not counting \0
throw_unless(38, name.slice_bits() >= 16);
;; name shall end with \0
int name_last_byte = name.slice_last(8).preload_uint(8);
throw_if(40, name_last_byte);
;; count zero separators
int zeros = 0;
slice cname = name;
repeat (cname.slice_bits() ^>> 3) {
int c = cname~load_uint(8);
zeros -= (c == 0);
}
;; throw_unless(39, zeros == 1);
name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse();
}
;; operation with codes 10..19 manipulate category dict
;; lets try to find it and store into a variable
;; operations with codes 20..29 replace / delete dict, no need
if (op < 20) {
;; lets resolve the name here so as not to duplicate the code
(slice pfx, cell val, slice tail, int succ) =
root.pfxdict_get_ref(1023, name);
if (succ) {
;; must match EXACTLY to prevent accident changes
throw_unless(35, tail.slice_empty?());
cat_table = val;
}
;; otherwise cat_table is null which is reasonable for actions
}
;; 11 VSet: set specified value to specified subdomain->category
if (op == 11) {
cell new_value = ops~load_maybe_ref();
cat_table~udict_set_get_ref(256, cat, new_value); ;; update: category length now u256 instead of i16
root~pfxdict_set_ref(1023, name, cat_table);
return (root, ops);
}
;; 12 VDel: delete specified subdomain->category value
if (op == 12) {
if (cat_table~udict_delete?(256, cat)) { ;; update: category length now u256 instead of i16
root~pfxdict_set_ref(1023, name, cat_table);
}
return (root, ops);
}
;; 21 DSet: replace entire category dictionary of domain with provided
if (op == 21) {
cell new_cat_table = ops~load_maybe_ref();
root~pfxdict_set_ref(1023, name, new_cat_table);
return (root, ops);
}
;; 22 DDel: delete entire category dictionary of specified domain
if (op == 22) {
root~pfxdict_delete?(1023, name);
return (root, ops);
}
;; 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell
if (op == 31) {
cell new_tree_root = ops~load_maybe_ref();
;; no sanity checks cause they would cost immense gas
return (new_tree_root, ops);
}
;; 32 TDel: nullify ENTIRE DOMAIN TABLE
if (op == 32) {
return (null(), ops);
}
throw(44); ;; invalid operation
return (null(), ops);
}
cell process_ops(cell root, slice ops) inline_ref {
var stop = false;
root~touch();
ops~touch();
do {
(root, ops) = process_op(root, ops);
if (ops.slice_data_empty?()) {
if (ops.slice_refs()) {
ops = ops~load_ref().begin_parse();
} else {
stop = true;
}
}
} until (stop);
return root;
}
() recv_external(slice in_msg) impure {
;; Load data
(int contract_id, int last_cleaned, int public_key, cell root, cell old_queries) = load_data();
;; validate signature and seqno
slice signature = in_msg~load_bits(512);
int shash = slice_hash(in_msg);
var (query_contract, query_id) = (in_msg~load_uint(32), in_msg~load_uint(64));
var bound = (now() << 32);
throw_if(35, query_id < bound);
(_, var found?) = old_queries.udict_get?(64, query_id);
throw_if(32, found?);
throw_unless(34, contract_id == query_contract);
throw_unless(35, check_signature(shash, signature, public_key));
accept_message(); ;; message is signed by owner, sanity not guaranteed yet
int op = in_msg.preload_uint(6);
if (op == 51) {
in_msg~skip_bits(6);
public_key = in_msg~load_uint(256);
} else {
root = process_ops(root, in_msg);
}
bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
old_queries~udict_set_builder(64, query_id, begin_cell());
var queries = old_queries;
do {
var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64);
f~touch();
if (f) {
f = (i < bound);
}
if (f) {
old_queries = old_queries';
last_cleaned = i;
}
} until (~ f);
store_data(contract_id, last_cleaned, public_key, root, old_queries);
}
() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666) {
}
{-
Data structure:
Root cell: [UInt<32b>:seqno] [UInt<256b>:owner_public_key]
[OptRef<1b+1r?>:Hashmap<PfxDict:Slice->CatTable>:domains]
<CatTable> := HashmapE 256 (~~16~~) ^DNSRecord
STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value)
#Zeros allows to simultaneously store, for example, com\0 and com\0google\0
That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons)
This will allow to resolve more specific requests to subdomains, and resort
to parent domain next resolver lookup if subdomain is not found
com\0goo\0 lookup will, for example look up \2com\0goo\0 and then
\1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat
-}
;;===========================================================================;;
;; Getter methods ;;
;;===========================================================================;;
;; Retrieve contract id (in case several contracts are managed with the same private key)
int get_contract_id() method_id {
return get_data().begin_parse().preload_uint(32);
}
int get_public_key() method_id {
var cs = get_data().begin_parse();
cs~load_uint(32 + 64);
return cs.preload_uint(256);
}
;;8m dns-record-value
(int, cell) dnsresolve(slice subdomain, int category) method_id {
int bits = subdomain.slice_bits();
ifnot (bits) {
;; return (0, null()); ;; zero-length input
throw(30); ;; update: throw exception for empty input
}
throw_if(30, bits & 7); ;; malformed input (~ 8n-bit)
int name_last_byte = subdomain.slice_last(8).preload_uint(8);
if (name_last_byte) {
subdomain = begin_cell().store_slice(subdomain) ;; append zero byte
.store_uint(0, 8).end_cell().begin_parse();
bits += 8;
}
if (bits == 8) {
return (8, null()); ;; zero-length input, but with zero byte
;; update: return 8 as resolved, but with no data
}
int name_first_byte = subdomain.preload_uint(8);
if (name_first_byte == 0) {
;; update: remove prefix \0
subdomain~load_uint(8);
bits -= 8;
}
(_, _, _, cell root, _) = load_data();
slice cname = subdomain;
int zeros = 0;
repeat (bits >> 3) {
int c = cname~load_uint(8);
zeros -= (c == 0);
}
;; can't move these declarations lower, will cause errors!
slice pfx = cname;
cell val = null();
slice tail = cname;
do {
slice pfxname = begin_cell().store_uint(zeros, 7)
.store_slice(subdomain).end_cell().begin_parse();
(pfx, val, tail, int succ) = root.pfxdict_get_ref(1023, pfxname);
zeros = succ ^ (zeros - 1); ;; break on success
} until (zeros <= 0);
ifnot (zeros) {
return (0, null()); ;; failed to find entry in prefix dictionary
}
zeros = - zeros;
ifnot (tail.slice_empty?()) { ;; if we have tail then len(pfx) < len(subdomain)
;; incomplete subdomain found, must return next resolver
category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff
;; update: next resolver is now sha256("dns_next_resolver") instead of -1
}
int pfx_bits = pfx.slice_bits() - 7;
cell cat_table = val;
;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain
;; COUNTING the zero byte (if structurally correct: no multiple-ZB keys)
;; which corresponds to 8m, m=one plus the number of bytes in the subdomain found)
if (category == 0) {
return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0
} else {
cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16
return (pfx_bits, cat_found);
}
}