consensus.hoon 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /= dk /apps/dumbnet/lib/types
  2. /= sp /common/stark/prover
  3. /= mine /common/pow
  4. /= dumb-transact /common/tx-engine
  5. /= * /common/zoon
  6. :: this library is where _every_ update to the consensus state
  7. :: occurs, no matter how minor.
  8. |_ [c=consensus-state:dk =blockchain-constants:dumb-transact]
  9. +* t ~(. dumb-transact blockchain-constants)
  10. +| %genesis
  11. ::
  12. :: +set-genesis-seal: set .genesis-seal
  13. ++ set-genesis-seal
  14. |= [height=page-number:t msg-hash=@t]
  15. ^- consensus-state:dk
  16. ~> %slog.[0 leaf+"setting genesis seal."]
  17. =/ seal (new:genesis-seal:t height msg-hash)
  18. c(genesis-seal seal)
  19. ::
  20. ++ add-btc-data
  21. |= =btc-hash:t
  22. ^- consensus-state:dk
  23. c(btc-data `btc-hash)
  24. ::
  25. +| %checks-and-computes
  26. ++ inputs-in-heaviest-balance
  27. |= raw=raw-tx:t
  28. ^- ?
  29. (inputs-in-balance raw get-cur-balance-names)
  30. ::
  31. ++ inputs-in-balance
  32. |= [raw=raw-tx:t balance=(z-set nname:t)]
  33. ^- ?
  34. :: set of inputs required by tx that are not in balance
  35. =/ in-balance=(z-set nname:t)
  36. (~(dif z-in (inputs-names:raw-tx:t raw)) balance)
  37. :: %.y: all inputs in .raw are in balance
  38. :: %.n: input(s) in .raw not in balance
  39. =(*(z-set nname:t) in-balance)
  40. ::
  41. ++ get-cur-height
  42. ^- page-number:t
  43. height:(~(got z-by blocks.c) (need heaviest-block.c))
  44. ::
  45. ++ get-cur-balance
  46. ^- (z-map nname:t nnote:t)
  47. ?~ heaviest-block.c
  48. ::~& >> "no known blocks, balance is empty"
  49. *(z-map nname:t nnote:t)
  50. (~(got z-by balance.c) u.heaviest-block.c)
  51. ::
  52. ++ get-cur-balance-names
  53. ^- (z-set nname:t)
  54. ~(key z-by get-cur-balance)
  55. ::
  56. :: +compute-target: find the new target
  57. ::
  58. :: this is supposed to be mathematically identical to
  59. :: https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp
  60. ::
  61. :: note that this works differently from what you might expect.
  62. :: we/bitcoin compute "target" where the larger the number is,
  63. :: the easier the block is to find. difficulty is just a human
  64. :: friendly form to read target in. that's why this appears
  65. :: backwards, where e.g. an epoch that takes 2x as long as the
  66. :: desired duration results in doubling the target.
  67. ++ compute-target
  68. |= [bid=block-id:t prev-target=bignum:bignum:t]
  69. ^- bignum:bignum:t
  70. (compute-target-raw (compute-epoch-duration bid) prev-target)
  71. ::
  72. :: +compute-target-raw: helper for +compute-target
  73. ::
  74. :: makes it easier for unit tests. we currently do not use
  75. :: bignum arithmetic due to lack of testing and it not yet
  76. :: being necessary. once consensus logic starts being run
  77. :: in the zkvm, we will need to change to bignum arithmetic.
  78. ++ compute-target-raw
  79. |= [epoch-dur=@ prev-target-bn=bignum:bignum:t]
  80. ^- bignum:bignum:t
  81. =/ prev-target-atom=@ (merge:bignum:t prev-target-bn)
  82. =/ capped-epoch-dur=@
  83. ?: (lth epoch-dur quarter-ted:t)
  84. quarter-ted:t
  85. ?: (gth epoch-dur quadruple-ted:t)
  86. quadruple-ted:t
  87. epoch-dur
  88. =/ next-target-atom=@
  89. %+ div
  90. (mul prev-target-atom capped-epoch-dur)
  91. target-epoch-duration:t
  92. =/ next-target-bn=bignum:bignum:t
  93. ?: (gth next-target-atom max-target-atom:t)
  94. max-target:t
  95. (chunk:bignum:t next-target-atom)
  96. ::~& >> "previous target: {(scow %ud prev-target-atom)}"
  97. ::~& >> "epoch duration: {(scow %ud epoch-dur)}"
  98. ::~& >> "new target: {(scow %ud next-target-atom)}"
  99. next-target-bn
  100. ::
  101. :: +compute-epoch-duration: computes the duration of an epoch in seconds
  102. ::
  103. :: to mitigate certain types of "time warp" attacks, the timestamp we mark
  104. :: as the end of an epoch is the median time of the last 11 blocks in the
  105. :: epoch. this also happens to be the min timestamp for the first block
  106. :: in the following epoch, which is already kept track of in
  107. :: .min-timestamps, where the value at a given block-id is the min
  108. :: timestamp of block that has that block-id as its parent. thus
  109. :: the duration of a given epoch is the difference between the minimum timestamp
  110. :: of the first block of the next epoch and the first block of the current
  111. :: epoch.
  112. ++ compute-epoch-duration
  113. |= last-block=block-id:t
  114. ^- @
  115. =/ prev-last-block=block-id:t
  116. (~(got z-by epoch-start.c) last-block)
  117. =/ epoch-start=@
  118. (~(got z-by min-timestamps.c) prev-last-block)
  119. =/ epoch-end=@
  120. (~(got z-by min-timestamps.c) last-block)
  121. ~| "time warp attack: negative epoch duration"
  122. (sub epoch-end epoch-start)
  123. ::
  124. :: +check-size: check on page size, requires all raw-tx
  125. ++ check-size
  126. |= [p=pending-state:dk pag=page:t]
  127. ^- ?
  128. (lte (compute-size:page:t pag raw-txs.p) max-block-size:t)
  129. ::
  130. +| %page-handling
  131. ++ add-page
  132. |= [pag=page:t acc=tx-acc:t now=@da]
  133. ^- consensus-state:dk
  134. :: update balance
  135. ::
  136. =? balance.c !=(~ balance.acc)
  137. :: if balance.acc is empty, this would still add the following to balance.c,
  138. :: so we do it conditionally.
  139. (~(put z-by balance.c) digest.pag balance.acc)
  140. =/ coinbases=(list coinbase:t)
  141. %+ turn ~(tap z-in ~(key z-by coinbase.pag))
  142. |= =lock:t
  143. (new:coinbase:t pag lock)
  144. =. balance.c
  145. %+ roll coinbases
  146. |= [=coinbase:t bal=_balance.c]
  147. (~(put z-bi bal) digest.pag name.coinbase coinbase)
  148. :: update txs
  149. ::
  150. =. txs.c
  151. %+ roll ~(tap z-in txs.acc)
  152. |= [=tx:t txs=_txs.c]
  153. (~(put z-bi txs) digest.pag id.tx tx)
  154. :: update blocks
  155. ::
  156. =. blocks.c
  157. (~(put z-by blocks.c) digest.pag (to-local-page:page:t pag))
  158. ::
  159. :: update epoch map. the first block-id in an epoch maps to its parent,
  160. :: and each subsequent block maps to the same block-id as the first. this is helpful
  161. :: bookkeeping to avoid a length pointer chase of parent of parent of...
  162. :: when reaching the end of an epoch and needing to compute its length.
  163. =. epoch-start.c
  164. ?: =(*page-number:t height.pag)
  165. :: genesis block is also considered the last block of the "0th" epoch.
  166. (~(put z-by epoch-start.c) digest.pag digest.pag)
  167. ?: =(0 epoch-counter.pag)
  168. (~(put z-by epoch-start.c) digest.pag parent.pag)
  169. %- ~(put z-by epoch-start.c)
  170. :- digest.pag
  171. (~(got z-by epoch-start.c) parent.pag)
  172. =. min-timestamps.c (update-min-timestamps now pag)
  173. ::
  174. =. targets.c
  175. ?: =(+(epoch-counter.pag) blocks-per-epoch:t)
  176. :: last block of an epoch means update to target
  177. %- ~(put z-by targets.c)
  178. :- digest.pag
  179. (compute-target digest.pag target.pag)
  180. ?: =(height.pag *page-number:t) :: genesis block
  181. %- ~(put z-by targets.c)
  182. [digest.pag target.pag]
  183. :: target remains the same throughout an epoch
  184. %- ~(put z-by targets.c)
  185. :- digest.pag
  186. (~(got z-by targets.c) parent.pag)
  187. :: note we do not update heaviest-block here, since that is conditional
  188. :: and the effects emitted depend on whether we do it.
  189. c
  190. ::
  191. :: +validate-page-without-txs-da: helper for urbit time
  192. ++ validate-page-without-txs-da
  193. |= [pag=page:t now=@da]
  194. (validate-page-without-txs pag (time-in-secs:page:t now))
  195. ::
  196. :: +validate-page-without-txs: with parent, without raw-txs
  197. ::
  198. :: performs every check that can be done on a page when you
  199. :: know its parent, except for validating the powork or digest,
  200. :: but don't have all of the raw-txs. not to be performed on
  201. :: genesis block, which has its own check. this check should
  202. :: be performed before adding a block to pending state.
  203. ++ validate-page-without-txs
  204. |= [pag=page:t now-secs=@]
  205. ^- (reason:dk ~)
  206. =/ par=page:t (to-page:local-page:t (~(got z-by blocks.c) parent.pag))
  207. :: this is already checked in +heard-block but is done here again
  208. :: to avoid a footgun
  209. ?. (check-digest:page:t pag)
  210. [%.n %page-digest-invalid-2]
  211. ::
  212. =/ check-epoch-counter=?
  213. ?& (lth epoch-counter.pag blocks-per-epoch:t)
  214. ?| ?& =(0 epoch-counter.pag)
  215. :: epoch-counter is zero-indexed so we decrement
  216. =(epoch-counter.par (dec blocks-per-epoch:t))
  217. == :: start of an epoch
  218. :: counter is one greater than its parent's counter.
  219. =(epoch-counter.pag +(epoch-counter.par))
  220. ==
  221. ==
  222. ?. check-epoch-counter
  223. [%.n %page-epoch-invalid]
  224. ::
  225. =/ check-pow-hash=?
  226. ?. check-pow-flag:t
  227. :: this case only happens during testing
  228. ::~& "skipping pow hash check for {(trip (to-b58:hash:t digest.pag))}"
  229. %.y
  230. %- check-target:mine
  231. :_ target.pag
  232. (digest-to-atom:tip5:t (hash-proof:t (need pow.pag)))
  233. ?. check-pow-hash
  234. [%.n %pow-target-check-failed]
  235. ::
  236. =/ check-timestamp=?
  237. ?& %+ gte timestamp.pag
  238. (~(got z-by min-timestamps.c) parent.pag)
  239. ::
  240. (lte timestamp.pag (add now-secs max-future-timestamp:t))
  241. ==
  242. ?. check-timestamp
  243. [%.n %page-timestamp-invalid]
  244. ::
  245. :: check target
  246. ?. =(target.pag (~(got z-by targets.c) parent.pag))
  247. [%.n %page-target-invalid]
  248. ::
  249. :: check height
  250. ?. =(height.pag +(height.par))
  251. [%.n %page-height-invalid]
  252. ::
  253. =/ check-heaviness=?
  254. .= accumulated-work.pag
  255. %- chunk:bignum:t
  256. %+ add
  257. (merge:bignum:t accumulated-work.par)
  258. (merge:bignum:t (compute-work:page:t target.pag))
  259. ?. check-heaviness
  260. [%.n %page-heaviness-invalid]
  261. ::
  262. =/ check-split-length=?
  263. (lte (lent ~(key z-by coinbase.pag)) max-coinbase-split:t)
  264. ?. check-split-length
  265. [%.n %split-too-large]
  266. =/ check-msg-length=?
  267. (lth (lent msg.pag) 20)
  268. ?. check-msg-length
  269. [%.n %msg-too-large]
  270. ::
  271. [%.y ~]
  272. ::
  273. :: +validate-page-with-txs: to be run after all txs gathered
  274. ::
  275. :: note that this does _not_ repeat earlier validation steps,
  276. :: namely that done by +validate-page-withouts-txs and checking
  277. :: the powork. it returns ~ if any of the checks fail, and
  278. :: a $tx-acc otherwise, which is the datum needed to add the
  279. :: page to consensus state.
  280. ++ validate-page-with-txs
  281. |= [p=pending-state:dk pag=page:t]
  282. ^- (reason:dk tx-acc:t)
  283. =/ digest-b58=tape (trip (to-b58:hash:t digest.pag))
  284. ?. (check-size p pag)
  285. ::~& >>> "block {digest-b58} is too large"
  286. [%.n %block-too-large]
  287. =/ raw-tx-set=(set raw-tx:t)
  288. (~(run z-in tx-ids.pag) |=(=tx-id:t (~(got z-by raw-txs.p) tx-id)))
  289. =/ raw-tx-list=(list raw-tx:t) ~(tap z-in raw-tx-set)
  290. =| tx-list=(list tx:t)
  291. =. tx-list
  292. |-
  293. ?~ raw-tx-list tx-list
  294. =/ utx=(unit tx:t) (mole |.((new:tx:t i.raw-tx-list height.pag)))
  295. ?~ utx :: exit early b/c raw-tx failed to convert
  296. ~
  297. %= $
  298. tx-list [u.utx tx-list]
  299. raw-tx-list t.raw-tx-list
  300. ==
  301. ?: ?&(=(~ tx-list) !=(~ raw-tx-list))
  302. :: failed to build a raw-tx, so the page is invalid
  303. [%.n %raw-txs-failed-to-build]
  304. :: initialize balance transfer accumulator with parent block's balance
  305. =/ acc=tx-acc:t
  306. (new:tx-acc:t (~(get z-by balance.c) parent.pag))
  307. :: test to see that the input notes for all transactions
  308. :: exist in the parent block's balance, that they are not
  309. :: over- or underspent, and that the resulting
  310. :: output notes are valid as well. a lot is going
  311. :: on here - this is a load-bearing chunk of code in the
  312. :: transaction engine.
  313. ::
  314. =/ balance-transfer=(unit tx-acc:t)
  315. |-
  316. ?~ tx-list
  317. (some acc)
  318. =/ new-acc=(unit tx-acc:t)
  319. (process:tx-acc:t acc i.tx-list height.pag)
  320. ?~ new-acc ~ :: tx failed to process
  321. $(acc u.new-acc, tx-list t.tx-list)
  322. ::
  323. ?~ balance-transfer
  324. :: balance transfer failed
  325. ::~& >>> "block {digest-b58} invalid"
  326. [%.n %balance-transfer-failed]
  327. ::
  328. :: check that the coinbase split adds up to emission+fees
  329. =/ total-split=coins:t
  330. %+ roll ~(val z-by coinbase.pag)
  331. |=([c=coins:t s=coins:t] (add c s))
  332. =/ emission-and-fees=coins:t
  333. (add (emission-calc:coinbase:t height.pag) fees.u.balance-transfer)
  334. ?. =(emission-and-fees total-split)
  335. [%.n %improper-split]
  336. ::~& > "block {digest-b58} txs validated"
  337. [%.y u.balance-transfer]
  338. ::
  339. :: +update-heaviest: set new heaviest block if it is so
  340. ++ update-heaviest
  341. |= pag=page:t
  342. ^- consensus-state:dk
  343. =/ digest-b58=cord (to-b58:hash:t digest.pag)
  344. ::~> %slog.[0 leaf+"checking if block {digest-b58} is heaviest"]
  345. ?: =(~ heaviest-block.c)
  346. :: if we have no heaviest block, this must be genesis block.
  347. ~| "received non-genesis block before genesis block"
  348. ?> =(*page-number:t height.pag)
  349. c(heaviest-block (some digest.pag))
  350. :: > rather than >= since we take the first heaviest block we've heard
  351. ?: %+ compare-heaviness:page:t pag
  352. (~(got z-by blocks.c) (need heaviest-block.c))
  353. =/ print-var
  354. %- trip
  355. ^- @t
  356. %^ cat 3
  357. digest-b58
  358. ' is new heaviest block'
  359. ~> %slog.[0 leaf+print-var]
  360. c(heaviest-block (some digest.pag))
  361. =/ print-var
  362. %- trip
  363. ^- @t
  364. %^ cat 3
  365. digest-b58
  366. ' is NOT new heaviest block'
  367. ~> %slog.[0 leaf+print-var]
  368. c
  369. ::
  370. :: +get-elders: get list of ancestor block IDs up to 24 deep
  371. :: (ordered newest->oldest)
  372. ++ get-elders
  373. |= [d=derived-state:dk bid=block-id:t]
  374. ^- (unit [page-number:t (list block-id:t)])
  375. =/ block (~(get z-by blocks.c) bid)
  376. ?~ block
  377. ~
  378. =/ pag=page:t (to-page:local-page:t u.block)
  379. =/ height=page-number:t height.pag
  380. =/ ids=(list block-id:t) [bid ~]
  381. =/ count 1
  382. |-
  383. ?: =(height *page-number:t) `[height (flop ids)]
  384. ?: =(24 count) `[height (flop ids)]
  385. =/ prev-height=page-number:t (dec height)
  386. =/ prev-id=(unit block-id:t) (~(get z-by heaviest-chain.d) prev-height)
  387. ?~ prev-id
  388. :: if prev-id is null, something is wrong
  389. ~
  390. $(height prev-height, ids [u.prev-id ids], count +(count))
  391. ::
  392. +| %timestamp
  393. :: +update-min-timestamps: sets min timestamp of children of .id
  394. ::
  395. ++ update-min-timestamps
  396. |= [now=@da pag=page:t]
  397. ^- (z-map block-id:t @)
  398. =/ min-timestamp=@
  399. :: get timestamps of up to N=min-past-blocks prior blocks.
  400. =| prev-timestamps=(list @)
  401. =/ b=@ (dec min-past-blocks:t) :: iteration counter
  402. =/ cur-block=page:t pag
  403. |-
  404. =. prev-timestamps [timestamp.cur-block prev-timestamps]
  405. ?: ?| =(0 b) :: we've looked back +min-past-blocks blocks
  406. ::
  407. :: we've reached genesis block
  408. =(*page-number:t height.cur-block)
  409. ==
  410. :: return median of timestamps
  411. (median:t prev-timestamps)
  412. %= $
  413. b (dec b)
  414. cur-block (to-page:local-page:t (~(got z-by blocks.c) parent.cur-block))
  415. ==
  416. ::
  417. (~(put z-by min-timestamps.c) digest.pag min-timestamp)
  418. --