Ton

Форк
0
/
dns-auto-code.fc 
536 строк · 19.3 Кб
1
{-
2
  Adapted from original version written by:
3
  /------------------------------------------------------------------------\
4
  | Created for: Telegram (Open Network) Blockchain Contest                |
5
  |      Task 2: DNS Resolver (Automatically registering)                  |
6
  >------------------------------------------------------------------------<
7
  | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com)    |
8
  |         October 2019                                                   |
9
  \------------------------------------------------------------------------/
10
  Updated to actual DNS standard version by starlightduck in 2022
11
-}
12

13
;;===========================================================================;;
14
;; Custom ASM instructions                                                   ;;
15
;;===========================================================================;;
16

17
cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF";
18

19
;;===========================================================================;;
20
;; Utility functions                                                         ;;
21
;;===========================================================================;;
22

23
{-
24
  Data structure:
25
  Root cell: [OptRef<1b+1r?>:Hashmap<PfxDict:Slice->UInt<32b>,CatTable>:domains]
26
         [OptRef<1b+1r?>:Hashmap<UInt<160b>(Time|Hash128)->Slice(DomName)>:gc]
27
         [UInt<32b>:stdperiod] [Gram:PPReg] [Gram:PPCell] [Gram:PPBit]
28
         [UInt<32b>:lasthousekeeping] 
29
  <CatTable> := HashmapE 256 (~~16~~) ^DNSRecord
30
  
31
  STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value)
32
  #Zeros allows to simultaneously store, for example, com\0 and com\0google\0
33
  That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons)
34
  This will allow to resolve more specific requests to subdomains, and resort
35
      to parent domain next resolver lookup if subdomain is not found
36
  com\0goo\0 lookup will, for example look up \2com\0goo\0 and then
37
      \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat
38
-}
39

40
(cell, cell, cell, [int, int, int, int], int, int) load_data() inline_ref {
41
  slice cs = get_data().begin_parse();
42
  return (
43
    cs~load_ref(),        ;; control data
44
    cs~load_dict(),       ;; pfx tree: domains data and exp
45
    cs~load_dict(),       ;; gc auxillary with expiration and 128-bit hash slice
46
    [ cs~load_uint(30),   ;; length of this period of time in seconds
47
      cs~load_grams(),    ;; standard payment for registering a new subdomain
48
      cs~load_grams(),    ;; price paid for each cell (PPC)
49
      cs~load_grams() ],  ;;                 and bit  (PPB)
50
    cs~load_uint(32),     ;; next housekeeping to be done at
51
    cs~load_uint(32)      ;; last housekeeping done at
52
  );
53
}
54

55
(int, int, int, int) load_prices() inline_ref {
56
  slice cs = get_data().begin_parse();
57
  (cs~load_ref(), cs~load_dict(), cs~load_dict());
58
  return (cs~load_uint(30), cs~load_grams(), cs~load_grams(), cs~load_grams());
59
}
60

61
() store_data(cell ctl, cell dd, cell gc, prices, int nhk, int lhk) impure {
62
  var [sp, ppr, ppc, ppb] = prices;
63
  set_data(begin_cell()
64
      .store_ref(ctl) ;; control data
65
      .store_dict(dd) ;; domains data and exp
66
      .store_dict(gc) ;; keyed expiration time and 128-bit hash slice
67
      .store_uint(sp, 30) ;; standard period
68
      .store_grams(ppr) ;; price per registration
69
      .store_grams(ppc) ;; price per cell
70
      .store_grams(ppb) ;; price per bit
71
      .store_uint(nhk, 32) ;; next housekeeping
72
      .store_uint(lhk, 32) ;; last housekeeping
73
      .end_cell());
74
}
75

76
global var query_info;
77

78
() send_message(slice addr, int tag,   int query_id, 
79
                int   body, int grams, int mode) impure {
80
  ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 
81
  ;;                src:MsgAddress -> 011000 0x18
82
  var msg = begin_cell()
83
    .store_uint (0x18, 6)
84
    .store_slice(addr)
85
    .store_grams(grams)
86
    .store_uint (0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
87
    .store_uint (tag, 32)
88
    .store_uint (query_id, 64);
89
  if (body >= 0) {
90
    msg~store_uint(body, 32);
91
  }
92
  send_raw_message(msg.end_cell(), mode);
93
}
94

95
() send_error(int error_code) impure {
96
  var (addr, query_id, op) = query_info;
97
  return send_message(addr, error_code, query_id, op, 0, 64);
98
}
99

100
() send_ok(int price) impure {
101
  raw_reserve(price, 4);
102
  var (addr, query_id, op) = query_info;
103
  return send_message(addr, 0xef6b6179, query_id, op, 0, 128);
104
}
105

106
() housekeeping(cell ctl, cell dd, cell gc, prices, int nhk, int lhk, int max_steps) impure {
107
  int n = now();
108
  if (n < max(nhk, lhk + 60)) { ;; housekeeping cooldown: 1 minute
109
    ;; if housekeeping was done recently, or if next housekeeping is in the future, just save
110
    return store_data(ctl, dd, gc, prices, nhk, lhk);
111
  }
112
  ;; need to do some housekeeping - maybe remove entry with
113
  ;;     least expiration but only if it is already expired
114
  ;; no iterating and deleting all to not put too much gas gc
115
  ;;     burden on any random specific user request
116
  ;; over time it will do the garbage collection required
117
  (int mkey, _, int found?) = gc.udict_get_min?(256);
118
  while (found? & max_steps) { ;; no short circuit optimization, two nested ifs
119
    nhk = (mkey >> (256 - 32));
120
    if (nhk < n) {
121
      int key = mkey % (1 << (256 - 32));
122
      (slice val, found?) = dd.udict_get?(256 - 32, key);
123
      if (found?) {
124
        int exp = val.preload_uint(32);
125
        if (exp <= n) {
126
          dd~udict_delete?(256 - 32, key);
127
        }
128
      }
129
      gc~udict_delete?(256, mkey);
130
      (mkey, _, found?) = gc.udict_get_min?(256);
131
      nhk = (found? ? mkey >> (256 - 32) : 0xffffffff);
132
      max_steps -= 1;
133
    } else {
134
      found? = false;
135
    }
136
  }
137
  store_data(ctl, dd, gc, prices, nhk, n);
138
}
139

140
int calcprice_internal(slice domain, cell data, ppc, ppb) inline_ref { ;; only for internal calcs
141
  var (_, bits, refs) = compute_data_size(data, 100);  ;; 100 cells max
142
  bits += slice_bits(domain) * 2 + (128 + 32 + 32);
143
  return ppc * (refs + 2) + ppb * bits;
144
}
145

146
int check_owner(cell cat_table, cell owner_info, int src_wc, int src_addr, int strict) inline_ref {
147
  if (strict & cat_table.null?()) { ;; domain not found: return notf | 2^31
148
    return 0xee6f7466;
149
  }
150
  if (owner_info.null?()) { ;; no owner on this domain: no-2 (in strict mode), ok else
151
    return strict & 0xee6f2d32;
152
  }
153
  var ERR_BAD2 = 0xe2616432;
154
  slice sown = owner_info.begin_parse();
155
  if (sown.slice_bits() < 16 + 3 + 8 + 256) { ;; bad owner record: bad2
156
    return ERR_BAD2;
157
  }
158
  if (sown~load_uint(16 + 3) != 0x9fd3 * 8 + 4) {
159
    return ERR_BAD2;
160
  }
161
  (int owner_wc, int owner_addr) = (sown~load_int(8), sown.preload_uint(256));
162
  if ((owner_wc != src_wc) | (owner_addr != src_addr)) { ;; not owner: nown
163
    return 0xee6f776e;
164
  }
165
  return 0;  ;; ok
166
}
167

168
;;===========================================================================;;
169
;; Internal message handler (Code 0)                                         ;;
170
;;===========================================================================;;
171

172
{-
173
  Internal message cell structure:
174
                   8             4           2            1
175
    int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
176
    src:MsgAddressInt dest:MsgAddressInt
177
    value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
178
    created_lt:uint64 created_at:uint32  
179
  Internal message data structure:
180
    [UInt<32b>:op] [UInt<64b>:query_id] [Ref<1r>:domain] 
181
    (if not prolong: [Ref<1r>:value->CatTable])
182
    
183
-}
184

185
;; Control operations: permitted only to the owner of this smartcontract
186
() perform_ctl_op(int op, int src_wc, int src_addr, slice in_msg) impure inline_ref {
187
  var (ctl, domdata, gc, prices, nhk, lhk) = load_data();
188
  var cs = ctl.begin_parse();
189
  if ((cs~load_int(8) != src_wc) | (cs~load_uint(256) != src_addr)) {
190
    return send_error(0xee6f776e);
191
  }
192
  if (op == 0x43685072) {  ;; ChPr = Change Prices
193
    var (stdper, ppr, ppc, ppb) = (in_msg~load_uint(32), in_msg~load_grams(), in_msg~load_grams(), in_msg~load_grams());
194
    in_msg.end_parse();
195
    ;; NB: stdper == 0 -> disable new actions
196
    store_data(ctl, domdata, gc, [stdper, ppr, ppc, ppb], nhk, lhk);
197
    return send_ok(0);
198
  }
199
  var (addr, query_id, op) = query_info;
200
  if (op == 0x4344656c) {  ;; CDel = destroy smart contract
201
    ifnot (domdata.null?()) {
202
      ;; domain dictionary not empty, force gc
203
      housekeeping(ctl, domdata, gc, prices, nhk, 1, -1);
204
    }
205
    (ctl, domdata, gc, prices, nhk, lhk) = load_data();
206
    ifnot (domdata.null?()) {
207
      ;; domain dictionary still not empty, error
208
      return send_error(0xee74656d);
209
    }
210
    return send_message(addr, 0xef6b6179, query_id, op, 0, 128 + 32);
211
  }
212
  if (op == 0x54616b65) { ;; Take = take grams from the contract
213
    var amount = in_msg~load_grams();
214
    return send_message(addr, 0xef6b6179, query_id, op, amount, 64);
215
  }
216
  return send_error(0xffffffff);
217
}
218

219
;; Must send at least GR$1 more for possible gas fees!
220
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
221
  ;; this time very interested in internal messages
222
  if (in_msg.slice_bits() < 32) { 
223
    return ();  ;; simple transfer or short
224
  }
225
  slice cs = in_msg_cell.begin_parse();
226
  int flags = cs~load_uint(4);
227
  if (flags & 1) { 
228
    return ();  ;; bounced messages
229
  }
230
  slice s_addr = cs~load_msg_addr();
231
  (int src_wc, int src_addr) = s_addr.parse_std_addr();
232
  int op = in_msg~load_uint(32);
233
  ifnot (op) { 
234
    return ();   ;; simple transfer with comment
235
  }
236
  int query_id = 0;
237
  if (in_msg.slice_bits() >= 64) { 
238
    query_id = in_msg~load_uint(64);
239
  }
240
  
241
  query_info = (s_addr, query_id, op);
242
  
243
  if (op & (1 << 31)) {
244
    return ();   ;; an answer to our query
245
  }
246
  if ((op >> 24) == 0x43) {
247
    ;; Control operations
248
    return perform_ctl_op(op, src_wc, src_addr, in_msg);
249
  }
250
  
251
  int qt = (op == 0x72656764) * 1 + (op == 0x70726f6c) * 2 + (op == 0x75706464) * 4 + (op == 0x676f6763) * 8;
252
  ifnot (qt) { ;; unknown query, return error
253
    return send_error(0xffffffff);
254
  }
255
  qt = - qt;
256
  
257
  (cell ctl, cell domdata, cell gc, [int, int, int, int] prices, int nhk, int lhk) = load_data();
258
  
259
  if (qt == 8) { ;; 0x676f6763 -> GO, GC! go!!!
260
    ;; Manual garbage collection iteration
261
    int max_steps = in_msg~load_int(32);   ;; -1 = infty
262
    housekeeping(ctl, domdata, gc, prices, nhk, 1, max_steps); ;; forced
263
    return send_error(0xef6b6179);
264
  }
265

266
  slice domain = null();
267
  cell domain_cell = in_msg~load_maybe_ref();
268
  int fail = 0;
269
  if (domain_cell.null?()) {
270
    int bytes = in_msg~load_uint(6);
271
    fail = (bytes == 0);
272
    domain = in_msg~load_bits(bytes * 8);
273
  } else {
274
    domain = domain_cell.begin_parse();
275
    var (bits, refs) = slice_bits_refs(domain);
276
    fail = (refs | ((bits - 8) & (7 - 128)));
277
  }
278

279
  ifnot (fail) {
280
    ;; domain must end with \0! no\0 error
281
    fail = domain.slice_last(8).preload_uint(8);
282
  }
283
  if (fail) {
284
    return send_error(0xee6f5c30);
285
  }
286
    
287
  int n = now();
288
  cell cat_table = cell owner_info = null();
289
  int key = int exp = int zeros = 0;
290
  slice tail = domain;
291
  repeat (tail.slice_bits() ^>> 3) {
292
    cat_table = null();
293
    int z = (tail~load_uint(8) == 0);
294
    zeros -= z;
295
    if (z) {
296
      key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32);
297
      var (val, found?) = domdata.udict_get?(256 - 32, key);
298
      if (found?) {
299
        exp = val~load_uint(32);
300
        if (exp >= n) {  ;; entry not expired
301
          cell cat_table = val~load_ref();
302
          val.end_parse();
303
          ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2
304
          var (cown, ok) = cat_table.udict_get_ref?(256, 0);
305
          if (ok) {
306
            owner_info = cown;
307
          }
308
        }
309
      }
310
    }
311
  }
312
  
313
  if (zeros > 4) { ;; too much zero chars (overflow): ov\0
314
    return send_error(0xef765c30);
315
  }
316

317
  ;; ##########################################################################
318
  
319
  int err = check_owner(cat_table, owner_info, src_wc, src_addr, qt != 1);
320
  if (err) {
321
    return send_error(err);
322
  }
323

324
  ;; ##########################################################################
325
  
326
  ;; load desired data (reuse old for a "prolong" operation)
327
  cell data = null();
328
  
329
  if (qt != 2) {  ;; not a "prolong", load data dictionary
330
    data = in_msg~load_ref();
331
    ;; basic integrity check of (client-provided) dictionary
332
    ifnot (data.dict_empty?()) { ;; 1000 gas!
333
      ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2
334
      var (oinfo, ok) = data.udict_get_ref?(256, 0);
335
      if (ok) {
336
        var cs = oinfo.begin_parse();
337
        throw_unless(31, cs.slice_bits() >= 16 + 3 + 8 + 256);
338
        throw_unless(31, cs.preload_uint(19) == 0x9fd3 * 8 + 4);
339
      }
340
      (_, _, int minok) = data.udict_get_min?(256); ;; update: category length now u256 instead of i16
341
      (_, _, int maxok) = data.udict_get_max?(256); ;; update: category length now u256 instead of i16
342
      throw_unless(31, minok & maxok);
343
    }
344
  } else {
345
    data = cat_table;
346
  }
347

348
  ;; load prices
349
  var [stdper, ppr, ppc, ppb] = prices;
350
  ifnot (stdper) {  ;; smart contract disabled by owner, no new actions
351
    return send_error(0xd34f4646);
352
  }
353

354
  ;; compute action price
355
  int price = calcprice_internal(domain, data, ppc, ppb) + (ppr & (qt != 4));
356
  if (msg_value - (1 << 30) < price) { ;; gr<p: grams - GR$1 < price
357
    return send_error(0xe7723c70);
358
  }
359
  
360
  ;; load desired expiration unixtime
361
  int req_expires_at = in_msg~load_uint(32);
362

363
  ;; ##########################################################################
364
  if (qt == 2) { ;; 0x70726f6c -> prol | prolong domain
365
    if (exp > n + stdper) {  ;; does not expire soon, cannot prolong
366
      return send_error(0xf365726f);
367
    }
368
    domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp + stdper, 32).store_ref(data));
369

370
    int gckeyO = (exp << (256 - 32)) + key;
371
    int gckeyN = gckeyO + (stdper << (256 - 32));
372
    gc~udict_delete?(256, gckeyO); ;; delete old gc entry, add new
373
    gc~udict_set_builder(256, gckeyN, begin_cell());
374
    
375
    housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1);
376
    return send_ok(price);
377
  }
378
  
379
  ;; ##########################################################################
380
  if (qt == 1) { ;; 0x72656764 -> regd | register domain
381
    ifnot (cat_table.null?()) { ;; domain already exists: return alre | 2^31
382
      return send_error(0xe16c7265);
383
    }
384
    int expires_at = n + stdper;
385
    domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(expires_at, 32).store_ref(data));
386

387
    int gckey = (expires_at << (256 - 32)) | key;
388
    gc~udict_set_builder(256, gckey, begin_cell());
389

390
    housekeeping(ctl, domdata, gc, prices, min(nhk, expires_at), lhk, 1);
391
    return send_ok(price);
392
  }
393

394
  ;; ##########################################################################
395
  if (qt == 4) { ;; 0x75706464 -> updd | update domain (data)
396
    domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp, 32).store_ref(data));
397
    housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1);
398
    return send_ok(price);
399
  }
400
  ;; ##########################################################################
401
  
402
  return (); ;; should NEVER reach this part of code!
403
}
404

405
;;===========================================================================;;
406
;; External message handler (Code -1)                                        ;;
407
;;===========================================================================;;
408

409
() recv_external(slice in_msg) impure {
410
  ;; only for initialization
411
  (cell ctl, cell dd, cell gc, var prices, int nhk, int lhk) = load_data();
412
  ifnot (lhk) {
413
    accept_message();
414
    return store_data(ctl, dd, gc, prices, 0xffffffff, now());
415
  }
416
}
417

418
;;===========================================================================;;
419
;; Getter methods                                                            ;;
420
;;===========================================================================;;
421

422
(int, cell, int, slice) dnsdictlookup(slice domain, int nowtime) inline_ref {
423
  (int bits, int refs) = domain.slice_bits_refs();
424
  throw_if(30, refs | (bits & 7)); ;; malformed input (~ 8n-bit)
425
  ifnot (bits) {
426
    ;; return (0, null(), 0, null());  ;; zero-length input
427
    throw(30); ;; update: throw exception for empty input
428
  }
429

430
  int domain_last_byte = domain.slice_last(8).preload_uint(8);
431
  if (domain_last_byte) {
432
    domain = begin_cell().store_slice(domain) ;; append zero byte
433
                         .store_uint(0, 8).end_cell().begin_parse();
434
    bits += 8;
435
  }
436
  if (bits == 8) {
437
    return (0, null(), 8, null()); ;; zero-length input, but with zero byte
438
    ;; update: return 8 as resolved, but with no data
439
  }
440
  int domain_first_byte = domain.preload_uint(8);
441
  if (domain_first_byte == 0) {
442
    ;; update: remove prefix \0
443
    domain~load_uint(8);
444
    bits -= 8;
445
  }
446
  var ds = get_data().begin_parse();
447
  (_, cell root) = (ds~load_ref(), ds~load_dict());
448
  
449
  slice val = null();
450
  int tail_bits = -1;
451
  slice tail = domain;
452

453
  repeat (bits >> 3) {
454
    if (tail~load_uint(8) == 0) {
455
      var key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32);
456
      var (v, found?) = root.udict_get?(256 - 32, key);
457
      if (found?) {
458
        if (v.preload_uint(32) >= nowtime) {  ;; entry not expired
459
          val = v;
460
          tail_bits = tail.slice_bits();
461
        }
462
      }
463
    }
464
  }
465

466
  if (val.null?()) {
467
    return (0, null(), 0, null()); ;; failed to find entry in subdomain dictionary
468
  }
469

470
  return (val~load_uint(32), val~load_ref(), tail_bits == 0, domain.skip_last_bits(tail_bits));
471
}
472

473
;;8m  dns-record-value
474
(int, cell) dnsresolve(slice domain, int category) method_id {
475
  (int exp, cell cat_table, int exact?, slice pfx) = dnsdictlookup(domain, now());
476
  ifnot (exp) {
477
    return (exact?, null()); ;; update: reuse exact? to return 8 for \0
478
  }
479
  ifnot (exact?) { ;; incomplete subdomain found, must return next resolver (-1)
480
    category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff
481
    ;; update: next resolver is now sha256("dns_next_resolver") instead of -1
482
  }
483
  
484
  int pfx_bits = pfx.slice_bits();
485
  
486
  ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain 
487
  ;;   COUNTING the zero byte (if structurally correct: no multiple-ZB keys)
488
  ;;   which corresponds to 8m, m=one plus the number of bytes in the subdomain found)
489
  ifnot (category) {
490
    return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0
491
  } else {
492
    cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16
493
    return (pfx_bits, cat_found);
494
  }
495
}
496

497
;; getexpiration needs to know the current time to skip any possible expired
498
;;     subdomains in the chain. it will return 0 if not found or expired.
499
int getexpirationx(slice domain, int nowtime) inline method_id {
500
  (int exp, _, _, _) = dnsdictlookup(domain, nowtime);
501
  return exp;
502
}
503

504
int getexpiration(slice domain) method_id {
505
  return getexpirationx(domain, now());
506
}
507

508
int getstdperiod() method_id {
509
  (int stdper, _, _, _) = load_prices();
510
  return stdper;
511
}
512

513
int getppr() method_id {
514
  (_, int ppr, _, _) = load_prices();
515
  return ppr;
516
}
517

518
int getppc() method_id {
519
  (_, _, int ppc, _) = load_prices();
520
  return ppc;
521
}
522

523
int getppb() method_id {
524
  ( _, _, _, int ppb) = load_prices();
525
  return ppb;
526
}
527

528
int calcprice(slice domain, cell val) method_id { ;; only for external gets (not efficient)
529
  (_, _, int ppc, int ppb) = load_prices();
530
  return calcprice_internal(domain, val, ppc, ppb);
531
}
532

533
int calcregprice(slice domain, cell val) method_id { ;; only for external gets (not efficient)
534
  (_, int ppr, int ppc, int ppb) = load_prices();
535
  return ppr + calcprice_internal(domain, val, ppc, ppb);
536
}
537

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.