:: gaze: azimuth statistics :: :: general flow: :: - receive events :: - process events whose timestamp is known :: - request timestamps for unknown block numbers (if not already running) :: - receive timestamps, process events :: /- eth-watcher /+ *ethereum, *azimuth, default-agent, verb :: => |% +$ state-0 $: %0 :: qued: event logs waiting on block timestamp, oldest first :: time: timstamps of block numbers :: seen: events sorted by timestamp, newest first :: days: stats by day, newest first :: running=(unit @ta) qued=loglist time=(map @ud @da) seen=(list [wen=@da wat=event]) days=(list [day=@da sat=stats]) == :: +$ loglist loglist:eth-watcher +$ event $% [%azimuth who=ship dif=diff-point] [%invite by=ship of=ship gift=ship to=address] == :: +$ stats $: spawned=(list @p) activated=(list @p) transfer-p=(list @p) transferred=(list @p) configured=(list @p) breached=(list @p) request=(list @p) sponsor=(list @p) management-p=(list @p) voting-p=(list @p) spawn-p=(list @p) invites-senders=(list @p) == :: +$ card card:agent:gall :: ++ node-url 'http://eth-mainnet.urbit.org:8545' ++ refresh-rate ~h1 ++ timeout-time ~h2 -- :: =| state-0 =* state - :: %+ verb | ^- agent:gall =< |_ =bowl:gall +* this . do ~(. +> bowl) def ~(. (default-agent this %|) bowl) bec byk.bowl(r da+now.bowl) :: ++ on-init ^- (quip card _this) [setup-cards:do this] :: ++ on-save !>(state) ++ on-load |= old=vase ^- (quip card _this) [~ this(state !<(state-0 old))] :: ++ on-poke |= [=mark =vase] ^- (quip card _this) ?> ?=(%noun mark) =/ =noun !<(noun vase) |- ^- [cards=(list card) =_this] ?+ noun ~|([dap.bowl %unknown-poke noun] !!) %reconnect :_ this :~ leave-eth-watcher:do watch-eth-watcher:do == :: %reload :- cards:$(noun %reconnect) this(qued ~, seen ~, days ~) :: %rewatch :_ this:$(noun %reset) :~ leave-eth-watcher:do clear-eth-watcher:do setup-eth-watcher:do await-eth-watcher:do == :: %export [export:do this] :: %debug ~& latest=(turn (scag 5 seen) head) ~& oldest=(turn (slag (sub (max 5 (lent seen)) 5) seen) head) ~& :- 'order is' =- ?:(sane 'sane' 'insane') %+ roll seen |= [[this=@da *] last=@da sane=?] :- this ?: =(*@da last) & (lte this last) ~& time=~(wyt by time) ~& qued=(lent qued) ~& days=(lent days) [~ this] == :: ++ on-agent |= [=wire =sign:agent:gall] ^- (quip card _this) ?+ -.sign (on-agent:def wire sign) %kick ?. =(/watcher wire) [~ this] [[watch-eth-watcher:do]~ this] :: %fact ?+ wire (on-agent:def wire sign) [%watcher ~] ?. ?=(%eth-watcher-diff p.cage.sign) (on-agent:def wire sign) =^ cards state %- handle-eth-watcher-diff:do !<(diff:eth-watcher q.cage.sign) [cards this] :: [%timestamps @ ~] ?+ p.cage.sign (on-agent:def wire sign) %thread-fail =+ !<([=term =tang] q.cage.sign) =/ =tank leaf+"{(trip dap.bowl)} thread failed; will retry" %- (slog tank leaf+ tang) =^ cards state request-timestamps:do [cards this] :: %thread-done =^ cards state %- save-timestamps:do !<((list [@ud @da]) q.cage.sign) [cards this] == == == :: ++ on-arvo |= [=wire =sign-arvo] ^- (quip card _this) ?+ +<.sign-arvo ~|([dap.bowl %strange-arvo-sign +<.sign-arvo] !!) %wake ?: =(/export wire) [[wait-export:do export:do] this] ?: =(/watch wire) [[watch-eth-watcher:do]~ this] ~& [dap.bowl %strange-wake wire] [~ this] == :: ++ on-peek on-peek:def ++ on-watch on-watch:def ++ on-leave on-leave:def ++ on-fail on-fail:def -- :: |_ =bowl:gall ++ bec byk.bowl(r da+now.bowl) ++ setup-cards ^- (list card) :~ wait-export setup-eth-watcher :: we punt on subscribing to the eth-watcher for a little while. :: this way we get a %history diff containing all past events, :: instead of so many individual %log diffs that we bail meme. :: (to repro, replace this with `watch-eth-watcher`) :: await-eth-watcher == :: ++ wait |= [=wire =@dr] ^- card [%pass wire %arvo %b %wait (add now.bowl dr)] :: ++ wait-export (wait /export refresh-rate) :: ++ to-eth-watcher |= [=wire =task:agent:gall] ^- card [%pass wire %agent [our.bowl %eth-watcher] task] :: ++ setup-eth-watcher %+ to-eth-watcher /setup :+ %poke %eth-watcher-poke !> ^- poke:eth-watcher :+ %watch /[dap.bowl] :* node-url | refresh-rate timeout-time public:mainnet-contracts ~ ~[azimuth delegated-sending]:mainnet-contracts ~ ~ == :: :: see also comment in +setup-cards ++ await-eth-watcher (wait /watch ~m30) :: ++ watch-eth-watcher %+ to-eth-watcher /watcher [%watch /logs/[dap.bowl]] :: ++ leave-eth-watcher %+ to-eth-watcher /watcher [%leave ~] :: ++ clear-eth-watcher %+ to-eth-watcher /clear :+ %poke %eth-watcher-poke !> ^- poke:eth-watcher [%clear /logs/[dap.bowl]] :: ++ poke-spider |= [=wire =cage] ^- card [%pass wire %agent [our.bowl %spider] %poke cage] :: ++ watch-spider |= [=wire =sub=path] ^- card [%pass wire %agent [our.bowl %spider] %watch sub-path] :: :: +handle-eth-watcher-diff: process new logs, clear state on rollback :: :: processes logs for which we know the timestamp :: adds timestamp-less logs to queue :: ++ handle-eth-watcher-diff |= =diff:eth-watcher ^- (quip card _state) =^ logs state ^- [loglist _state] ?- -.diff %history ~& [%got-history (lent loglist.diff)] [loglist.diff state(qued ~, seen ~)] %logs ~& %got-log [loglist.diff state] %disavow ~& %disavow-unimplemented [~ state] == %- process-logs %+ skip logs |= =event-log:rpc %- is-lockup-block block-number:(need mined.event-log) :: :: +is-lockup-block: whether the block contains lockup/ignorable transactions :: :: this is the stupid dumb equivalent to actually identifying lockup :: transactions procedurally, which is still in git history, but didn't :: work quite right for unidentified reasons :: ++ is-lockup-block |= num=@ud ^- ? %+ roll ^- (list [@ud @ud]) :~ [7.050.978 7.051.038] == |= [[start=@ud end=@ud] in=_|] ?: in & &((gte num start) (lte num end)) :: :: +request-timestamps: request block timestamps for the logs as necessary :: :: will come back as a thread result :: ++ request-timestamps ^- (quip card _state) ?~ qued [~ state] ?^ running [~ state] =/ tid=@ta %+ scot %ta :((cury cat 3) dap.bowl '_' (scot %uv eny.bowl)) :_ state(running `tid) :~ (watch-spider /timestamps/[tid] /thread-result/[tid]) :: %+ poke-spider /timestamps/[tid] :- %spider-start =- !>([~ `tid bec %eth-get-timestamps -]) !> ^- [@t (list @ud)] :- node-url =- ~(tap in -) %- ~(gas in *(set @ud)) ^- (list @ud) %+ turn qued |= log=event-log:rpc block-number:(need mined.log) == :: :: +save-timestamps: store timestamps into state :: ++ save-timestamps |= timestamps=(list [@ud @da]) ^- (quip card _state) =. time (~(gas by time) timestamps) =. running ~ (process-logs ~) :: :: +process-logs: handle new incoming logs :: ++ process-logs |= new=loglist :: oldest first ^- (quip card _state) =. qued (weld qued new) ?~ qued [~ state] =- %_ request-timestamps qued (flop rest) :: oldest first seen (weld logs seen) :: newest first days (count-events (flop logs)) :: oldest first == %+ roll `loglist`qued |= [log=event-log:rpc [rest=loglist logs=(list [wen=@da wat=event])]] :: to ensure logs are processed in sane order, :: stop processing as soon as we skipped one :: ?^ rest [[log rest] logs] =/ tim=(unit @da) %- ~(get by time) block-number:(need mined.log) ?~ tim [[log rest] logs] :- rest =+ ven=(event-log-to-event log) ?~ ven logs [[u.tim u.ven] logs] :: :: +event-log-to-event: turn raw log into gaze noun :: ++ event-log-to-event |= log=event-log:rpc ^- (unit event) ?: =(azimuth:mainnet-contracts address.log) =+ (event-log-to-point-diff log) ?~ - ~ `azimuth+u ?: =(delegated-sending:mainnet-contracts address.log) ?. .= i.topics.log 0x4763.8e3c.ddee.2204.81e4.c3f9.183d.639c. 0efe.a7f0.5fcd.2df4.1888.5572.9f71.5419 ~ =/ [of=@ pool=@] ~| t.topics.log %+ decode-topics:abi:ethereum t.topics.log ~[%uint %uint] =/ [by=@ gift=@ to=@] ~| data.log %+ decode-topics:abi:ethereum %+ rash data.log =- ;~(pfix (jest '0x') -) %+ stun [3 3] (bass 16 (stun [64 64] hit)) ~[%uint %uint %address] `invite+[by of gift to] ~ :: :: +count-events: add events to the daily stats :: ++ count-events |= logs=_seen :: oldest first ^+ days =/ head=[day=@da sat=stats] ?^ days i.days *[@da stats] =+ tail=?~(days ~ t.days) |- :: when done, store updated head, but only if it's set :: ?~ logs ?: =(*[@da stats] head) tail [head tail] =* log i.logs :: calculate day for current event, set head if unset :: =/ day=@da (sub wen.log (mod wen.log ~d1)) =? day.head =(*@da day.head) day :: same day as head, so add to it :: ?: =(day day.head) %_ $ sat.head (count-event wat.log sat.head) logs t.logs == ~| [%weird-new-day old=day.head new=day] ?> (gth day day.head) :: newer day than head of days, so start new head :: %_ $ tail [head tail] head [day *stats] == :: :: +count-event: add event to the stats, if it's relevant :: ++ count-event |= [eve=event sat=stats] ^- stats ?- -.eve %invite sat(invites-senders [by.eve invites-senders.sat]) :: %azimuth ?+ -.dif.eve sat %spawned sat(spawned [who.dif.eve spawned.sat]) %activated sat(activated [who.eve activated.sat]) %transfer-proxy ?: =(0x0 new.dif.eve) sat sat(transfer-p [who.eve transfer-p.sat]) %owner sat(transferred [who.eve transferred.sat]) %keys sat(configured [who.eve configured.sat]) %continuity sat(breached [who.eve breached.sat]) %escape ?~ new.dif.eve sat sat(request [who.eve request.sat]) %sponsor ?. has.new.dif.eve sat sat(sponsor [who.eve sponsor.sat]) %management-proxy sat(management-p [who.eve management-p.sat]) %voting-proxy sat(voting-p [who.eve voting-p.sat]) %spawn-proxy sat(spawn-p [who.eve spawn-p.sat]) == == :: :: :: +export: periodically export data :: ++ export ^- (list card) :~ (export-move %days (export-days days)) (export-move %months (export-months days)) (export-move %events export-raw) == :: :: +export-move: %info move to write exported .txt :: ++ export-move |= [nom=@t dat=(list @t)] ^- card =- [%pass /export/[nom] %arvo %c %info -] %+ foal:space:userlib /(scot %p our.bowl)/home/(scot %da now.bowl)/gaze-exports/[nom]/txt [%txt !>(dat)] :: :: +peek-x: accept gall scry :: :: %/days/txt: per day, digest stats :: %/months/txt: per month, digest stats :: %/raw/txt: all observed events :: ++ peek-x ::TODO |= pax=path ^- (unit (unit (pair mark *))) ?~ pax ~ ?: =(%days i.pax) :^ ~ ~ %txt (export-days days) ?: =(%months i.pax) :^ ~ ~ %txt (export-months days) ?: =(%raw i.pax) ``txt+export-raw ~ :: :: +export-months: generate a csv of stats per month :: ++ export-months |= =_days %- export-days ^+ days %+ roll (flop days) |= [[day=@da sat=stats] mos=(list [mod=@da sat=stats])] ^+ mos =/ mod=@da %- year =+ (yore day) -(d.t 1) ?~ mos [mod sat]~ ?: !=(mod mod.i.mos) [[mod sat] mos] :_ t.mos :- mod ::TODO this is hideous. can we make a wet gate do this? :* (weld spawned.sat spawned.sat.i.mos) (weld activated.sat activated.sat.i.mos) (weld transfer-p.sat transfer-p.sat.i.mos) (weld transferred.sat transferred.sat.i.mos) (weld configured.sat configured.sat.i.mos) (weld breached.sat breached.sat.i.mos) (weld request.sat request.sat.i.mos) (weld sponsor.sat sponsor.sat.i.mos) (weld management-p.sat management-p.sat.i.mos) (weld voting-p.sat voting-p.sat.i.mos) (weld spawn-p.sat spawn-p.sat.i.mos) (weld invites-senders.sat invites-senders.sat.i.mos) == :: :: +export-days: generate a csv of stats per day :: ++ export-days |= =_days :- %- crip ;: weld "date," "spawned," "activated," "transfer proxy," "transferred," "transferred (unique)," "configured," "configured (unique)," "escape request," "sponsor change," "invites," "invites (unique senders)" == |^ ^- (list @t) %+ turn days |= [day=@da stats] %- crip ;: weld (scow %da day) "," (count spawned) "," (count activated) "," (count transfer-p) "," (unique transferred) "," (unique configured) "," (count request) "," (count sponsor) "," (unique invites-senders) == :: ++ count |* l=(list) (num (lent l)) :: ++ unique |* l=(list) ;: weld (count l) "," (num ~(wyt in (~(gas in *(set)) l))) == :: ++ num (d-co:co 1) -- :: :: +export-raw: generate a csv of individual transactions :: ++ export-raw :- %- crip ;: weld "date," "point," "event," "field 1,field2,field3" == |^ ^- (list @t) %+ turn seen :: (cork tail event-to-row crip) |= [wen=@da =event] (crip "{(scow %da wen)},{(event-to-row event)}") :: ++ event-to-row |= =event ?- -.event %azimuth (point-diff-to-row +.event) %invite (invite-to-row +.event) == :: ++ point-diff-to-row |= [who=ship dif=diff-point] ^- tape %+ weld "{(pon who)}," ?- -.dif %full "full," %owner "owner,{(adr new.dif)}" %activated "activated," %spawned "spawned,{(pon who.dif)}" %keys "keys,{(num life.dif)}" %continuity "breached,{(num new.dif)}" %sponsor "sponsor,{(spo has.new.dif)},{(pon who.new.dif)}" %escape "escape-req,{(req new.dif)}" %management-proxy "management-p,{(adr new.dif)}" %voting-proxy "voting-p,{(adr new.dif)}" %spawn-proxy "spawn-p,{(adr new.dif)}" %transfer-proxy "transfer-p,{(adr new.dif)}" == :: ++ invite-to-row |= [by=ship of=ship ship to=address] "{(pon by)},invite,{(pon of)},{(adr to)}" :: ++ num (d-co:co 1) ++ pon (cury scow %p) ++ adr |=(a=@ ['0' 'x' ((x-co:co (mul 2 20)) a)]) ++ spo |=(h=? ?:(h "escaped to" "detached from")) ++ req |=(r=(unit @p) ?~(r "canceled" (pon u.r))) -- --