/=  dcon  /apps/dumbnet/lib/consensus
/=  dk  /apps/dumbnet/lib/types
/=  dumb-transact  /common/tx-engine
/=  *  /common/zoon
::
|_  [p=pending-state:dk bc=blockchain-constants:dumb-transact]
+*  t  ~(. dumb-transact bc)
+|  %logic
::  +find-ready-blocks: blocks for which .id was the last missing tx
++  find-ready-blocks
  ^-  (z-set block-id:t)
  ::  set of all pending blocks
  =/  p-bids=(z-set block-id:t)  ~(key z-by pending-blocks.p)
  ::  set of pending blocks still waiting on txs
  =/  w-bids=(z-set block-id:t)  ~(key z-by block-tx.p)
  (~(dif z-in p-bids) w-bids)
::
++  inputs-in-spent-by
  |=  raw=raw-tx:t
  ^-  ?
  =/  inputs-names=(z-set nname:t)  (inputs-names:raw-tx:t raw)
  =/  spent-by-names=(z-set nname:t)  ~(key z-by spent-by.p)
  =/  common-names=(z-set nname:t)  (~(int z-in spent-by-names) inputs-names)
  ::  %.y: inputs are present in spent-by
  ::  %.n: inputs are not present in spent-by
  !=(*(z-set nname:t) common-names)
::
++  refresh-after-new-block
  |=  [c=consensus-state:dk retain=(unit @)]
  ^-  pending-state:dk
  ?~  retain
    ::  never drop transactions
    p
  ?:  =(0 u.retain)
    ::  never retain anything
    %_  p
      spent-by  *(z-map nname:t tx-id:t)
      heard-at  *(z-map tx-id:t page-number:t)
      raw-txs   *(z-map tx-id:t raw-tx:t)
    ==
  ::
  ::  enumerate last N block heights inclusive of current block height
  =/  cur-height=page-number:t
    ~(get-cur-height dcon c bc)
  =/  min-height=page-number:t
    ?:  (lth cur-height u.retain)  0
    ::  add 1 b/c range is inclusive of cur-height
    ::  so retain=1 means min-height=cur-height
    +((sub cur-height u.retain))
  =/  heard-kvs=(list [tx-id:t page-number:t])
    ~(tap z-by heard-at.p)
  ::
  =/  keep-drop=[k=(list [tx-id:t page-number:t]) d=(list raw-tx:t)]
    %+  roll  heard-kvs
    |=  $:  [tid=tx-id:t num=page-number:t]
            [keep=(list [tx-id:t page-number:t]) drop=(list raw-tx:t)]
        ==
    =/  raw=raw-tx:t  (~(got z-by raw-txs.p) tid)
    ?:  (lth num min-height)
      ::  tx is old, drop it
      [keep [raw drop]]
    ?.  (~(inputs-in-heaviest-balance dcon c bc) raw)
      ::  input(s) in tx not in balance, discard
      [keep [raw drop]]
    ::  tx should stay in heard-at
    [[[tid num] keep] drop]
  ::
  ::  new heard-at map from k.keep-drop
  =.  heard-at.p
    (~(gas z-by *(z-map tx-id:t page-number:t)) k.keep-drop)
  ::
  ::  remove d.keep-drop from spent-by map
  =.  p
    %+  roll  d.keep-drop
    |=  [raw=raw-tx:t pen=_p]
    (remove-inputs-from-spent-by raw)
  ::
  ::  remove d.keep-drop from raw-txs map. we cannot just
  ::  make a new map with k.keep-drop since raw-txs also includes
  ::  txs for pending blocks
  =.  p
    %+  roll  d.keep-drop
    |=  [raw=raw-tx:t pen=_p]
    (remove-raw-tx id.raw)
  ::
  p
+|  %getters-and-setters
::
::  +add-tx-not-in-pending-block:
::
::    these transactions are treated differently from transactions
::    that are in a pending block. we track their inputs and when
::    we heard them. if we hear another transaction using some
::    of the same inputs, we discard it. if we heard the transaction
::    sufficiently long ago, we drop it from pending state.
++  add-tx-not-in-pending-block
  |=  [raw=raw-tx:t cur-height=page-number:t]
  ^-  pending-state:dk
  =.  p  (add-raw-tx raw)
  =.  p  (add-inputs-to-spent-by raw)
  =.  p  (tx-heard-at id.raw cur-height)
  p
::
::  +add-tx-in-pending-block:
::
::    when a tx is in a pending block, we ignore whether it uses
::    inputs that have been spent in the heaviest balance, or
::    when we heard it.
++  add-tx-in-pending-block
  |=  raw=raw-tx:t
  ^-  pending-state:dk
  =.  p  (add-raw-tx raw)
  ::  this one needs to go before remove-tx-from-tx-block because
  ::  the keys it needs to inspect are gotten from .tx-block.
  =.  p  (remove-tx-from-block-tx id.raw)
  =.  p  (remove-tx-from-tx-block id.raw)
  p
::
++  tx-heard-at
  |=  [id=tx-id:t height=page-number:t]
  ^-  pending-state:dk
  p(heard-at (~(put z-by heard-at.p) id height))
::
++  add-inputs-to-spent-by
  |=  raw=raw-tx:t
  ^-  pending-state:dk
  =/  inputs-names=(list nname:t)
    ~(tap z-in (inputs-names:raw-tx:t raw))
  =/  new-entries=(list [nname:t tx-id:t])
    (turn inputs-names |=(n=nname:t n^id.raw))
  p(spent-by (~(gas z-by spent-by.p) new-entries))
::
++  remove-inputs-from-spent-by
  |=  raw=raw-tx:t
  ^-  pending-state:dk
  =/  inputs-names=(list nname:t)
    ~(tap z-in (inputs-names:raw-tx:t raw))
  =.  spent-by.p
    %+  roll  inputs-names
    |=  [nom=nname:t spb=_spent-by.p]
    (~(del z-by spb) nom)
  p
::
::  +find-pending-tx-ids: pending tx-ids for pending blocks
++  find-pending-tx-ids
  ^-  (z-set tx-id:t)
  %+  roll  ~(val z-by block-tx.p)
  |=  [tx-ids=(z-set tx-id:t) all-tx-ids=(z-set tx-id:t)]
  (~(uni z-in all-tx-ids) tx-ids)
::
++  remove-tx-from-tx-block
  |=  id=tx-id:t
  ^-  pending-state:dk
  p(tx-block (~(del z-by tx-block.p) id))
::
++  remove-tx-from-block-tx
  |=  tid=tx-id:t
  ^-  pending-state:dk
  =/  block-vals=(unit (z-set block-id:t))
    (~(get z-by tx-block.p) tid)
  ?~  block-vals
    ::  no blocks waiting on this tx, so do nothing
    p
  =/  block-list=(list block-id:t)
    ~(tap z-in u.block-vals)
  =.  block-tx.p
    %+  roll  block-list
    |=  [bid=block-id:t blt=_block-tx.p]
    (~(del z-ju blt) bid tid)
  p
::
++  add-pending-block
  |=  pag=page:t
  ^-  [(list tx-id:t) pending-state:dk]
  ::  find missing txs
  =/  missing-txs=(list tx-id:t)
    ~(tap z-in (~(dif z-in tx-ids.pag) ~(key z-by raw-txs.p)))
  ::  return missing txs and new pending state
  :-  missing-txs
  =;  pen=pending-state:dk
    ::  add to list of pending blocks if there are missing txs
    =?  pending-blocks.pen
      !=(~ missing-txs)
    (~(put z-by pending-blocks.pen) digest.pag (to-local-page:page:t pag))
    pen
  %+  roll  missing-txs
  |=  [tid=tx-id:t pen=_p]
  ::  block requires these txs to be complete
  =.  block-tx.pen
    (~(put z-ju block-tx.pen) digest.pag tid)
  ::  add block to set of blocks that require this tx
  =.  tx-block.pen
    (~(put z-ju tx-block.pen) tid digest.pag)
  pen
::
++  remove-pending-block
  |=  bid=block-id:t
  ^-  pending-state:dk
  =.  pending-blocks.p  (~(del z-by pending-blocks.p) bid)
  ::  get the txs that were needed for this block. if it was a valid
  ::  block, this will be empty already, but if it was invalid we
  ::  want to clean up the state.
  =/  tx-vals=(unit (z-set tx-id:t))
                        (~(get z-by block-tx.p) bid)
  ?~  tx-vals
    ::  no txs needed by block so just delete from block-tx
    =.  block-tx.p      (~(del z-by block-tx.p) bid)
    p
  =/  tx-list=(list tx-id:t)
    ~(tap z-in u.tx-vals)
  =.  block-tx.p        (~(del z-by block-tx.p) bid)
  ::  remove the block-id from this map.
  =.  tx-block.p
    %+  roll  tx-list
    |=  [tid=tx-id:t txb=_tx-block.p]
    (~(del z-ju txb) tid bid)
  p
::
++  add-raw-tx
  |=  raw=raw-tx:t
  ^-  pending-state:dk
  p(raw-txs (~(put z-by raw-txs.p) id.raw raw))
::
++  remove-raw-tx
  |=  tid=tx-id:t
  ^-  pending-state:dk
  p(raw-txs (~(del z-by raw-txs.p) tid))
--
