shrub/app/btc-provider.hoon

290 lines
7.6 KiB
Plaintext
Raw Normal View History

2020-10-20 17:13:52 +03:00
:: btc-provider.hoon
:: Proxy that serves a BTC full node and ElectRS address indexer
::
2020-10-28 16:20:24 +03:00
:: Subscriptions: none
2020-11-07 18:51:31 +03:00
:: To Subscribers: /clients
2020-10-28 16:20:24 +03:00
:: current connection state
:: results/errors of RPC calls
::
2020-11-09 12:53:30 +03:00
/- btc
/+ *btc-provider, dbug, default-agent, elib=electrum-rpc
|%
+$ versioned-state
$% state-0
==
::
2020-10-22 10:36:25 +03:00
+$ state-0 [%0 =host-info whitelist=(set ship)]
::
+$ card card:agent:gall
::
--
%- agent:dbug
=| state-0
=* state -
^- agent:gall
=<
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
hc ~(. +> bowl)
::
++ on-init
^- (quip card _this)
~& > '%btc-provider initialized successfully'
2020-11-07 18:51:31 +03:00
`this(host-info [*credentials connected=%.n clients=*(set ship)], whitelist *(set ship))
++ on-save
^- vase
!>(state)
++ on-load
|= old-state=vase
^- (quip card _this)
2020-10-20 17:13:52 +03:00
~& > '%btc-provider recompiled successfully '
`this(state !<(versioned-state old-state))
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?> ?|((team:title our.bowl src.bowl) (is-client:hc src.bowl))
=^ cards state
?+ mark (on-poke:def mark vase)
2020-11-07 14:56:11 +03:00
%btc-provider-command
?> (team:title our.bowl src.bowl)
(handle-command:hc !<(command vase))
%btc-provider-action
(handle-action:hc !<(action vase))
==
[cards this]
++ on-watch
|= pax=path
^- (quip card _this)
2020-11-07 18:51:31 +03:00
?> ?=([%clients *] pax)
?. (is-whitelisted:hc src.bowl)
~& >>> "btc-provider: blocked client {<src.bowl>}"
[~[[%give %kick ~ ~]] this]
2020-10-24 19:13:36 +03:00
~& > "btc-provider: added client {<src.bowl>}"
2020-11-10 14:09:59 +03:00
:- ~[(send-status ?:(connected.host-info %connected %disconnected))]
this(clients.host-info (~(put in clients.host-info) src.bowl))
::
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-agent on-agent:def
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
2020-11-09 13:48:03 +03:00
:: check for connectivity every 30 seconds
2020-11-07 18:51:31 +03:00
::
2020-11-07 14:56:11 +03:00
?: ?=([%ping-timer *] wire)
2020-11-10 16:29:50 +03:00
[do-ping:hc this]
=^ cards state
?+ +<.sign-arvo (on-arvo:def wire sign-arvo)
%http-response
2020-11-07 18:51:31 +03:00
(handle-rpc-response:hc wire client-response.sign-arvo)
==
[cards this]
::
++ on-fail on-fail:def
--
:: helper core
|_ =bowl:gall
2020-11-07 18:51:31 +03:00
++ handle-command
|= comm=command
^- (quip card _state)
?- -.comm
%set-credentials
2020-11-10 16:29:50 +03:00
:- do-ping
state(host-info [creds.comm connected=%.n clients=*(set ship)])
2020-11-07 18:51:31 +03:00
::
%whitelist-clients
`state(whitelist (~(uni in whitelist) clients.comm))
==
2020-11-09 13:48:03 +03:00
:: if not connected, only %ping action is allowed
2020-11-07 18:51:31 +03:00
::
++ handle-action
|= act=action
2020-11-09 12:53:30 +03:00
^- (quip card _state)
2020-11-09 15:29:30 +03:00
?. ?|(connected.host-info =(-.body.act %ping))
2020-11-07 14:56:11 +03:00
~& >>> "Not connected to RPC"
2020-11-09 15:29:30 +03:00
[~[(send-update [%| %not-connected 500])] state]
2020-10-22 10:36:25 +03:00
=/ ract=action:rpc
2020-11-09 15:29:30 +03:00
?- -.body.act
2020-11-09 12:53:30 +03:00
%address-info
2020-11-09 15:29:30 +03:00
[%erpc %get-address-utxos address.body.act]
2020-11-07 18:51:31 +03:00
::
%ping
[%brpc %get-block-count ~]
2020-10-20 17:13:52 +03:00
==
2020-11-09 12:53:30 +03:00
[~[(req-card act ract)] state]
++ req-card
|= [act=action ract=action:rpc]
=| out=outbound-config:iris
=/ req=request:http
(gen-request host-info ract)
[%pass (mk-wire act ract) %arvo %i %request req out]
2020-11-09 15:29:30 +03:00
:: wire structure: /action-tas/rpc-action-tas/req-id/(address, if rpc-action %erpc)/now
2020-11-09 12:53:30 +03:00
::
++ mk-wire
|= [act=action ract=action:rpc]
^- wire
=/ addr=path
?:(?=(%erpc -.ract) /(address-to-cord:elib address.ract) /)
2020-11-09 15:29:30 +03:00
%- zing
:~ /[-.body.act]/[-.ract]/[req-id.act]
addr
/[(scot %da now.bowl)]
==
:: Handles HTTP responses from RPC servers. Parses for errors, then handles response.
:: For actions that require collating multiple RPC calls, uses req-card to call out
:: to RPC again if more information is required.
2020-11-07 18:51:31 +03:00
::
++ handle-rpc-response
2020-10-20 17:13:52 +03:00
|= [=wire response=client-response:iris]
^- (quip card _state)
?. ?=(%finished -.response) `state
2020-11-07 12:14:34 +03:00
=* status status-code.response-header.response
2020-11-09 12:53:30 +03:00
:: handle error types: connection errors, RPC errors (in order)
2020-11-07 12:14:34 +03:00
::
=^ conn-err state
(connection-error status)
?^ conn-err
~& >>> conn-err
2020-11-10 14:09:59 +03:00
:_ state(connected.host-info %.n)
~[(send-status %disconnected) (send-update [%| u.conn-err])]
2020-10-20 17:13:52 +03:00
=/ rpc-resp=response:rpc:jstd
(get-rpc-response response)
2020-11-07 12:14:34 +03:00
?. ?=([%result *] rpc-resp)
[~[(send-update [%| [%rpc-error ~]])] state]
2020-11-09 12:53:30 +03:00
:: no error, switch on wire to handle RPC data
2020-11-07 12:14:34 +03:00
::
2020-10-23 20:35:04 +03:00
?+ wire ~|("Unexpected HTTP response" !!)
2020-11-09 15:29:30 +03:00
[%address-info %erpc @ @ *]
[(handle-address-info wire rpc-resp) state]
2020-11-09 12:53:30 +03:00
::
2020-11-07 18:51:31 +03:00
[%ping %brpc *]
2020-11-10 14:09:59 +03:00
:- ~[(send-status %connected)]
state(connected.host-info %.y)
2020-10-20 17:13:52 +03:00
==
2020-11-07 12:14:34 +03:00
::
2020-11-09 15:29:30 +03:00
++ handle-address-info
|= [=wire rpc-resp=response:rpc:jstd]
^- (list card)
=/ req-id=@t +>-.wire
=/ addr=address:btc (address-from-cord:elib +>+<.wire)
=/ eresp (parse-response:electrum-rpc:elib rpc-resp)
:~ ?- -.eresp
%get-address-utxos
?: =(0 ~(wyt in utxos.eresp))
(req-card [req-id %address-info addr] [%erpc %get-address-history addr])
(send-update [%& req-id %address-info addr utxos.eresp %.y])
::
%get-address-history
%- send-update
:* %& req-id %address-info addr *(set utxo:btc)
?:(=(0 ~(wyt in txs.eresp)) %.n %.y)
==
==
==
::
2020-11-07 12:14:34 +03:00
++ connection-error
2020-10-23 20:35:04 +03:00
|= status=@ud
2020-11-07 12:14:34 +03:00
^- [(unit error) _state]
?+ status [`[%http-error status] state]
%200
[~ state]
%400
[`[%bad-request status] state]
2020-11-07 18:51:31 +03:00
%401
[`[%no-auth status] state(connected.host-info %.n)]
2020-11-07 12:14:34 +03:00
%502
[`[%not-connected status] state(connected.host-info %.n)]
2020-11-07 12:14:34 +03:00
%504
[`[%not-connected status] state(connected.host-info %.n)]
==
::
2020-11-10 14:09:59 +03:00
++ send-status
|= =status ^- card
2020-11-10 16:29:50 +03:00
[%give %fact ~[/clients] %btc-provider-status !>(status)]
2020-10-23 20:35:04 +03:00
++ send-update
2020-11-09 15:29:30 +03:00
|= =update
^- card
2020-11-09 12:53:30 +03:00
~& >> "send-update: {<update>}"
2020-11-10 16:29:50 +03:00
[%give %fact ~[/clients] %btc-provider-update !>(update)]
2020-11-07 12:14:34 +03:00
::
++ is-whitelisted
|= user=ship ^- ?
?| (~(has in whitelist) user)
=(our.bowl user)
==
2020-11-10 16:29:50 +03:00
::
++ is-client
|= user=ship ^- ?
2020-10-22 10:36:25 +03:00
(~(has in clients.host-info) user)
2020-11-10 16:29:50 +03:00
::
++ start-ping-timer
|= interval=@dr ^- card
[%pass /ping-timer %arvo %b %wait (add now.bowl interval)]
::
++ do-ping
^- (list card)
:~ :* %pass /ping/[(scot %da now.bowl)] %agent
[our.bowl %btc-provider] %poke
%btc-provider-action !>([%blank-id %ping ~])
==
(start-ping-timer ~s30)
==
2020-11-09 13:48:03 +03:00
:: RPC JSON helper gates
:: TODO: move these to /lib
::
++ httr-to-rpc-response
|= hit=httr:eyre
^- response:rpc:jstd
~| hit
=/ jon=json (need (de-json:html q:(need r.hit)))
?. =(%2 (div p.hit 100))
2020-11-07 12:14:34 +03:00
(parse-rpc-error jon)
=, dejs-soft:format
^- response:rpc:jstd
=; dere
=+ res=((ar dere) jon)
?~ res (need (dere jon))
[%batch u.res]
|= jon=json
^- (unit response:rpc:jstd)
=/ res=[id=(unit @t) res=(unit json) err=(unit json)]
%. jon
=, dejs:format
=- (ou -)
:~ ['id' (uf ~ (mu so))]
['result' (uf ~ (mu same))]
['error' (uf ~ (mu same))]
==
?: ?=([^ * ~] res)
`[%result [u.id.res ?~(res.res ~ u.res.res)]]
~| jon
2020-11-07 12:14:34 +03:00
`(parse-rpc-error jon)
::
2020-10-19 15:57:13 +03:00
++ get-rpc-response
|= response=client-response:iris
^- response:rpc:jstd
?> ?=(%finished -.response)
%- httr-to-rpc-response
%+ to-httr:iris
response-header.response
full-file.response
::
2020-11-07 12:14:34 +03:00
++ parse-rpc-error
|= =json
^- response:rpc:jstd
:- %error
?~ json ['' '' '']
%. json
=, dejs:format
=- (ou -)
:~ =- ['id' (uf '' (cu - (mu so)))]
|*(a=(unit) ?~(a '' u.a))
:- 'error'
=- (uf ['' ''] -)
=- (cu |*(a=(unit) ?~(a ['' ''] u.a)) (mu (ou -)))
:~ ['code' (uf '' no)]
['message' (uf '' so)]
== ==
--