1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-13 03:32:22 +00:00
ton/crypto/smartcont/dns-auto-code.fc
2020-02-15 20:03:17 +04:00

474 lines
16 KiB
Text

{-
Adapted from original version written by:
/------------------------------------------------------------------------\
| Created for: Telegram (Open Network) Blockchain Contest |
| Task 2: DNS Resolver (Automatically registering) |
>------------------------------------------------------------------------<
| Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) |
| October 2019 |
\------------------------------------------------------------------------/
-}
;;===========================================================================;;
;; Utility functions ;;
;;===========================================================================;;
{-
Data structure:
Root cell: [OptRef<1b+1r?>:Hashmap<PfxDict:Slice->UInt<32b>,CatTable>:domains]
[OptRef<1b+1r?>:Hashmap<UInt<64b>(Time|Hash32)->Slice(DomName)>:gc]
[UInt<32b>:stdperiod] [Gram:PPReg] [Gram:PPCell] [Gram:PPBit]
[UInt<32b>:lasthousekeeping]
<CatTable> := HashmapE 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
-}
(cell, cell, [int, int, int, int], int, int) load_data() inline_ref {
slice cs = get_data().begin_parse();
return (
cs~load_dict(), ;; pfx tree: domains data and exp
cs~load_dict(), ;; gc auxillary with expiry and 32 bit hash slice
[ cs~load_uint(32), ;; length of this period of time in seconds
cs~load_grams(), ;; standard payment for registering a new subdomain
cs~load_grams(), ;; price paid for each cell (PPC)
cs~load_grams() ], ;; and bit (PPB)
cs~load_uint(32), ;; next housekeeping to be done at
cs~load_uint(32) ;; last housekeeping done at
);
}
(int, int, int, int) load_prices() inline_ref {
slice cs = get_data().begin_parse();
(cs~load_dict(), cs~load_dict());
return (cs~load_uint(32), cs~load_grams(), cs~load_grams(), cs~load_grams());
}
() store_data(cell dd, cell gc, prices, int nhk, int lhk) impure {
var [sp, ppr, ppc, ppb] = prices;
set_data(begin_cell()
.store_dict(dd) ;; domains data and exp
.store_dict(gc) ;; keyed expiration time and 32 bit hash slice
.store_int(sp, 32) ;; standard period
.store_grams(ppr) ;; price per registration
.store_grams(ppc) ;; price per cell
.store_grams(ppb) ;; price per bit
.store_uint(nhk, 32) ;; next housekeeping
.store_uint(lhk, 32) ;; last housekeeping
.end_cell());
}
global var query_info;
() send_message(slice addr, int tag, int query_id,
int body, int grams, int mode) impure {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
;; src:MsgAddress -> 011000 0x18
var msg = begin_cell()
.store_uint (0x18, 6)
.store_slice(addr)
.store_grams(grams)
.store_uint (0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint (tag, 32)
.store_uint (query_id, 64);
if (body >= 0) {
msg~store_uint(body, 32);
}
send_raw_message(msg.end_cell(), mode);
}
() send_error(int error_code) impure {
var (addr, query_id, op) = query_info;
return send_message(addr, error_code, query_id, op, 0, 64);
}
() send_ok(int price) impure {
raw_reserve(price, 4);
var (addr, query_id, op) = query_info;
return send_message(addr, 0xef6b6179, query_id, op, 0, 128);
}
() housekeeping(cell dd, cell gc, prices, int nhk, int lhk) impure {
int n = now();
if (n < max(nhk, lhk + 60)) { ;; housekeeping cooldown: 1 minute
;; if housekeeping was done recently, or if next housekeeping is in the future, just save
return store_data(dd, gc, prices, nhk, lhk);
}
;; need to do some housekeeping - maybe remove entry with
;; least expiration but only if it is already expired
;; no iterating and deleting all to not put too much gas gc
;; burden on any random specific user request
;; over time it will do the garbage collection required
(int mkey, cell name, int found?) = gc.udict_get_min_ref?(64);
if (found?) { ;; no short circuit optimization, two nested ifs
nhk = (mkey >> 32);
if (nhk < n) {
slice sname = name.begin_parse();
(_, slice val, _, found?) = dd.pfxdict_get?(1023, sname);
if (found?) {
int exp = val.preload_uint(32);
if (exp <= n) {
dd~pfxdict_delete?(1023, sname);
}
}
gc~udict_delete?(64, mkey);
(mkey, _, found?) = gc.udict_get_min_ref?(64);
nhk = (found? ? mkey >> 32 : 0xffffffff);
}
}
store_data(dd, gc, prices, nhk, n);
}
int _calcprice(cell data, ppc, ppb) inline_ref { ;; only for internal calcs
var (_, bits, refs) = compute_data_size(data, 100); ;; 100 cells max
return ppc * refs + ppb * bits;
}
int check_owner(cell cat_table, int src_wc, int src_addr) inline_ref {
if (cat_table.null?()) { ;; domain not found: return notf | 2^31
return 0xee6f7466;
}
cell cown = cat_table.idict_get_ref(16, -2);
if (cown.null?()) { ;; no owner on this domain: no-2
return 0xee6f2d32;
}
var ERR_BAD2 = 0xe2616432;
slice sown = cown.begin_parse();
if (sown.slice_bits() < 16 + 3 + 8 + 256) { ;; bad owner record: bad2
return ERR_BAD2;
}
if (sown~load_uint(16 + 3) != 0x9fd3 * 8 + 4) {
return ERR_BAD2;
}
(int owner_wc, int owner_addr) = (sown~load_int(8), sown.preload_uint(256));
if ((owner_wc != src_wc) | (owner_addr != src_addr)) { ;; not owner: nown
return 0xee6f776e;
}
return 0; ;; ok
}
;;===========================================================================;;
;; Internal message handler (Code 0) ;;
;;===========================================================================;;
{-
Internal message cell structure:
8 4 2 1
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddressInt dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32
Internal message data structure:
[UInt<32b>:op] [UInt<64b>:query_id] [Ref<1r>:name]
(if not prolong: [Ref<1r>:value->CatTable])
-}
;; Must send at least GR$1 more for possible gas fees!
() recv_internal(int ct_bal, int msg_value, cell in_msg_cell, slice in_msg) impure {
;; this time very interested in internal messages
if (in_msg.slice_bits() < 32) {
return (); ;; simple transfer or short
}
slice cs = in_msg_cell.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) {
return (); ;; bounced messages
}
slice s_addr = cs~load_msg_addr();
(int src_wc, int src_addr) = s_addr.parse_std_addr();
int op = in_msg~load_uint(32);
ifnot (op) {
return (); ;; simple transfer with comment
}
int query_id = 0;
if (in_msg.slice_bits() >= 64) {
query_id = in_msg~load_uint(64);
}
query_info = (s_addr, query_id, op);
if (op & (1 << 31)) {
return (); ;; an answer to our query
}
int qt = (op == 0x72656764) * 1 + (op == 0x70726f6c) * 2 + (op == 0x75706464) * 4 + (op == 0x676f6763) * 8;
ifnot (qt) { ;; unknown query, return error
return send_error(0xffffffff);
}
qt = - qt;
(cell domdata, cell gc, [int, int, int, int] prices, int nhk, int lhk) = load_data();
if (qt == 8) { ;; 0x676f6763 -> GO, GC! go!!!
;; Manual garbage collection iteration
housekeeping(domdata, gc, prices, nhk, 1); ;; forced
return send_error(0xef6b6179);
}
slice name = null();
cell name_cell = in_msg~load_maybe_ref();
if (name_cell.null?()) {
int bytes = in_msg~load_uint(6);
name = in_msg~load_bits(bytes * 8);
} else {
name = name_cell.begin_parse();
}
(_, int name_last_byte) = name.slice_last(8).load_uint(8);
if (name_last_byte != 0) { ;; name must end with \0! no\0 error
return send_error(0xee6f5c30);
}
int zeros = 0;
slice cname = name;
repeat (cname.slice_bits() ^>> 3) {
int c = cname~load_uint(8);
zeros -= (c == 0);
}
;; if (zeros != 1) { ;; too much zero chars (overflow): ov\0
;; return send_error(0xef765c30); }
name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse();
(slice pfx, slice val, slice tail, int found?) = domdata.pfxdict_get?(1023, name);
int n = now();
cell cat_table = null();
int exp = 0;
if (found?) {
exp = val~load_uint(32);
if (n > exp) { ;; expired domains behave as not registered
found? = false;
} else {
cat_table = val.preload_ref();
}
}
;; ##########################################################################
int err = 0;
if (qt != 1) { ;; not a "register", check that domain exists and is controlled by correct smc
err = check_owner(cat_table, src_wc, src_addr);
}
if (err) {
return send_error(err);
}
;; ##########################################################################
;; load desired data (reuse old for a "prolong" operation)
cell data = null();
if (qt != 2) { ;; not a "prolong", load data dictionary
data = in_msg~load_ref();
;; basic integrity check of (client-provided) dictionary
ifnot (data.dict_empty?()) { ;; 1000 gas!
(int dmin, _, int minok) = idict_get_min?(data, 16);
(int dmax, _, int maxok) = idict_get_max?(data, 16);
throw_unless(31, minok & maxok & (dmin <= dmax));
}
} else {
data = cat_table;
}
;; compute action price
var [stdper, ppr, ppc, ppb] = prices;
int price = _calcprice(data, ppc, ppb) + (ppr & (qt != 4));
if (msg_value - (1 << 30) < price) { ;; gr<p: grams - GR$1 < price
return send_error(0xe7723c70);
}
;; load desired expiration unixtime
int req_expires_at = in_msg~load_uint(32);
;; ##########################################################################
if (qt == 2) { ;; 0x70726f6c -> prol | prolong domain
slice value = begin_cell().store_uint(exp + stdper, 32).store_ref(data).end_cell().begin_parse();
ifnot (domdata~pfxdict_set?(1023, name, value)) { ;; Set ERR | 2^31
return send_error(0xf3657272);
}
int sh_low = name.slice_hash() & ((1 << 32) - 1);
int gckeyO = (exp << 32) + sh_low;
int gckeyN = gckeyO + (stdper << 32);
gc~udict_delete?(64, gckeyO); ;; delete old gc entry, add new
gc~udict_set_ref(64, gckeyN, begin_cell().store_slice(name).end_cell());
housekeeping(domdata, gc, prices, nhk, lhk);
return send_ok(price);
}
;; ##########################################################################
if (qt == 1) { ;; 0x72656764 -> regd | register domain
if (found?) { ;; domain already exists: return alre | 2^31
return send_error(0xe16c7265);
}
int expires_at = n + stdper;
slice value = begin_cell().store_uint(expires_at, 32).store_ref(data).end_cell().begin_parse();
ifnot (domdata~pfxdict_set?(1023, name, value)) { ;; Set ERR | 2^31
return send_error(0xf3657272);
}
int gckey = (expires_at << 32) | (name.slice_hash() & ((1 << 32) - 1));
gc~udict_set_ref(64, gckey, begin_cell().store_slice(name).end_cell());
;; using ref requires additional cell, but using value (DICTUSET) may
;; cause problems with very long names or complex dictionaries
housekeeping(domdata, gc, prices, min(nhk, expires_at), lhk);
return send_ok(price);
}
;; ##########################################################################
if (qt == 4) { ;; 0x75706464 -> updd | update domain (data)
slice value = begin_cell().store_uint(exp, 32).store_ref(data).end_cell().begin_parse();
ifnot (domdata~pfxdict_set?(1023, name, value)) { ;; Set ERR | 2^31
return send_error(0xf3657272);
}
;; no need to update gc here
housekeeping(domdata, gc, prices, nhk, lhk);
return send_ok(price);
}
;; ##########################################################################
return (); ;; should NEVER reach this part of code!
}
;;===========================================================================;;
;; External message handler (Code -1) ;;
;;===========================================================================;;
() recv_external(slice in_msg) impure {
;; not interested at all! but need to init!
(cell dd, cell gc, var prices, int nhk, int lhk) = load_data();
ifnot (lhk) {
accept_message();
store_data(dd, gc, prices, 0, now());
}
}
;;===========================================================================;;
;; Getter methods ;;
;;===========================================================================;;
(int, cell, int, slice) dnsdictlookup(slice subdomain, int nowtime) inline_ref {
int bits = subdomain.slice_bits();
ifnot (bits) {
return (0, null(), 0, null()); ;; zero-length 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 (0, null(), 0, null()); ;; zero-length input, but with zero byte
}
(_, cell root) = get_data().begin_parse().load_dict();
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;
slice val = null();
slice tail = cname;
int exp = 0;
do {
slice pfxname = begin_cell().store_uint(zeros, 7)
.store_slice(subdomain).end_cell().begin_parse();
(pfx, val, tail, int succ) = root.pfxdict_get?(1023, pfxname);
if (succ) {
int exp = val~load_uint(32);
if (nowtime > exp) { ;; entry expired, skip
succ = false;
}
}
zeros = succ ^ (zeros - 1); ;; break on success
} until (zeros <= 0);
ifnot (zeros) {
return (0, null(), 0, null()); ;; failed to find entry in prefix dictionary
}
zeros = - zeros;
return (exp, val.preload_ref(), tail.slice_empty?(), pfx);
}
;;8m dns-record-value
(int, cell) dnsresolve(slice subdomain, int category) method_id {
(int exp, cell cat_table, int exact?, slice pfx) = dnsdictlookup(subdomain, now());
ifnot (exp) {
return (0, null());
}
ifnot (exact?) { ;; incomplete subdomain found, must return next resolver (-1)
category = -1;
}
int pfx_bits = pfx.slice_bits() - 7;
;; 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.idict_get_ref(16, category);
return (pfx_bits, cat_found);
}
}
;; getexpiration needs to know the current time to skip any possible expired
;; subdomains in the chain. it will return 0 if not found or expired.
int getexpirationx(slice subdomain, int nowtime) inline method_id {
(int exp, _, _, _) = dnsdictlookup(subdomain, nowtime);
return exp;
}
int getexpiration(slice subdomain) method_id {
return getexpirationx(subdomain, now());
}
int getstdperiod() method_id {
(int stdper, _, _, _) = load_prices();
return stdper;
}
int getppr() method_id {
(_, int ppr, _, _) = load_prices();
return ppr;
}
int getppc() method_id {
(_, _, int ppc, _) = load_prices();
return ppc;
}
int getppb() method_id {
( _, _, _, int ppb) = load_prices();
return ppb;
}
int calcprice(cell val) method_id { ;; only for external gets (not efficient)
(_, _, int ppc, int ppb) = load_prices();
return _calcprice(val, ppc, ppb);
}
int calcregprice(cell val) method_id { ;; only for external gets (not efficient)
(_, int ppr, int ppc, int ppb) = load_prices();
return ppr + _calcprice(val, ppc, ppb);
}