::  eth-watcher: ethereum event log collector
::
/-  spider, *eth-watcher
/+  strandio, ethio, azimuth
=,  ethereum-types
=,  jael
::
::  Main loop: get updates since last checked
::
|=  args=vase
|^
=+  !<([~ pup=watchpup] args)
=/  m  (strand:strandio ,vase)
^-  form:m
;<  =latest=block                 bind:m  (get-latest-block:ethio url.pup)
;<  pup=watchpup   bind:m         (zoom pup number.id.latest-block)
=|  vows=disavows
;<  pup=watchpup   bind:m         (fetch-batches pup)
::?.  eager.pup
  (pure:m !>([vows pup]))
:: |-  ^-  form:m
:: =*  loop  $
:: ?:  (gth number.pup number.id.latest-block)
::   (pure:m !>([vows pup]))
:: ;<  =block  bind:m                (get-block-by-number:ethio url.pup number.pup)
:: ;<  [=new=disavows pup=watchpup]  bind:m  (take-block pup block)
:: %=  loop
::   pup   pup
::   vows  (weld vows new-disavows)
:: ==
::
::  Process a block, detecting and handling reorgs
::
++  take-block
  |=  [pup=watchpup =block]
  =/  m  (strand:strandio ,[disavows watchpup])
  ^-  form:m
  ::  if this next block isn't direct descendant of our logs, reorg happened
  ?:  &(?=(^ blocks.pup) !=(parent-hash.block hash.id.i.blocks.pup))
    (rewind pup block)
  =/  contracts  (weld contracts.pup batchers.pup)
  ;<  =new=loglist  bind:m  ::  oldest first
    (get-logs-by-hash:ethio url.pup hash.id.block contracts topics.pup)
  %-  pure:m
  :-  ~
  %_  pup
    number        +(number.id.block)
    pending-logs  (~(put by pending-logs.pup) number.id.block new-loglist)
    blocks        [block blocks.pup]
  ==
::
::  Reorg detected, so rewind until we're back in sync
::
++  rewind
  ::  block: wants to be head of blocks.pup, but might not match
  |=  [pup=watchpup =block]
  =/  m  (strand:strandio ,[disavows watchpup])
  =*  blocks  blocks.pup
  =|  vows=disavows
  |-  ^-  form:m
  =*  loop  $
  ::  if we have no further history to rewind, we're done
  ?~  blocks
    (pure:m (flop vows) pup(blocks [block blocks]))
  ::  if target block is directly after "latest", we're done
  ?:  =(parent-hash.block hash.id.i.blocks)
    (pure:m (flop vows) pup(blocks [block blocks]))
  ::  next-block: the new target block
  ;<  =next=^block  bind:m
    (get-block-by-number:ethio url.pup number.id.i.blocks)
  =.  pending-logs.pup  (~(del by pending-logs.pup) number.id.i.blocks)
  =.  vows  [id.block vows]
  loop(block next-block, blocks t.blocks)
::
::  Zoom forward to near a given block number.
::
::    Zooming doesn't go forward one block at a time.  As a
::    consequence, it cannot detect and handle reorgs.  Only use it
::    at a safe distance -- 100 blocks ago is probably sufficient.
::
++  zoom
  |=  [pup=watchpup =latest=number:block]
  =/  m  (strand:strandio ,watchpup)
  ^-  form:m
  ~&  >  [%zoom-start number.pup latest-number]
  =/  zoom-margin=number:block  0  :: TODO: 30!
  =/  zoom-step=number:block  100.000
  ?:  (lth latest-number (add number.pup zoom-margin))
    (pure:m pup)
  =/  up-to-number=number:block
    (min (add 1.000.000 number.pup) (sub latest-number zoom-margin))
  |-
  ~&  >  [%zooming number.pup up-to-number]
  =*  loop  $
  ?:  (gth number.pup up-to-number)
    (pure:m pup(blocks ~))
  =/  to-number=number:block
    =;  step
      (min up-to-number (add number.pup step))
    ::  Between "launch" (6.784.800) and "public" (7.033.765) blocks,
    ::  there are a lot events belonging to all the pre-ethereum ships
    ::  being established on-chain. By reducing the step, we avoid crashing.
    ::
    ?:  ?|  &((gte number.pup 6.951.132) (lth number.pup 6.954.242))
            &((gte number.pup 7.011.857) (lth number.pup 7.021.881))
        ==
      50
    ?:  ?&  (gte number.pup launch:mainnet-contracts:azimuth)
            (lth number.pup public:mainnet-contracts:azimuth)
        ==
      500
    zoom-step
  ;<  =loglist  bind:m  ::  oldest first
    %:  get-logs-by-range:ethio
      url.pup
      (weld contracts.pup batchers.pup)
      topics.pup
      number.pup
      to-number
    ==
  =?  pending-logs.pup  ?=(^ loglist)
    (~(put by pending-logs.pup) to-number loglist)
  loop(number.pup +(to-number))
::  Fetch input for any logs in batchers.pup
::
++  fetch-batches
  |=  pup=watchpup
  =/  m  (strand:strandio ,watchpup)
  =|  res=(list [number:block loglist])
  =/  pending=(list [=number:block =loglist])  ~(tap by pending-logs.pup)
  |-  ^-  form:m
  =*  loop  $
  ?~  pending
    (pure:m pup(pending-logs (malt res)))
  ;<  logs=(list event-log:rpc:ethereum)  bind:m
    (fetch-inputs pup loglist.i.pending)
  =.  res  [[number.i.pending logs] res]
  loop(pending t.pending)
::  Fetch inputs for a list of logs
::
++  fetch-inputs
  |=  [pup=watchpup logs=(list event-log:rpc:ethereum)]
  =/  m  (strand:strandio ,(list event-log:rpc:ethereum))
  =|  res=(list event-log:rpc:ethereum)
  |-  ^-  form:m
  =*  loop  $
  ?~  logs
    (pure:m (flop res))
  ;<  log=event-log:rpc:ethereum  bind:m  (fetch-input pup i.logs)
  =.  res  [log res]
  loop(logs t.logs)
::  Fetch input for a log
::
++  fetch-input
  |=  [pup=watchpup log=event-log:rpc:ethereum]
  =/  m  (strand:strandio ,event-log:rpc:ethereum)
  ^-  form:m
  ?~  mined.log
    (pure:m log)
  ?^  input.u.mined.log
    (pure:m log)
  ?.  (lien batchers.pup |=(=@ux =(ux address.log)))
    (pure:m log)
  ;<  res=transaction-result:rpc:ethereum  bind:m
    (get-tx-by-hash:ethio url.pup transaction-hash.u.mined.log)
  (pure:m log(input.u.mined `(data-to-hex input.res)))
::
++  data-to-hex
  |=  data=@t
  ?~  data  *@ux
  ?:  =(data '0x')  *@ux
  (hex-to-num:ethereum data)
--