Skip to content

Commit

Permalink
Start implementing semantics for a Web3 RPC server (#427)
Browse files Browse the repository at this point in the history
* Skeleton code for Web3 RPC implementation

* Implement net_version in web3

* Remove example file

* Fix command for kevm interpret

* Add top cell and fix formatting

* rename web3-json-rpc.md to web3.md

* rename WEB3-JSON-RPC to WEB3

* Change control flow to handle multiple calls + optimize

* web3.md: Add eth_gasPrice

* web3.md: Add eth_blockNumber

* web3.md: Add eth_accounts

* web3.md: Add eth_getBalance

* data.md, web3.md: Add and use handlers for RPC value encodings

* data: separate names for different #unparseData

* evm, web3: move chainID cell to web3 configuration

* web3.md: Fix net_version result format

* web3.md: added eth_getStorageAt and eth_getCode

* kevm: add run_web3 command

* Makefile: add build-web3 target

* kevm: no passing CHAINID to kore-json.py, interpreter

* web3: formatting

* web3: no need to require data

* web3.md: Implement web3_clientVersion

* web3.md: Implement some getters for JSON sorts
  • Loading branch information
gtrepta authored and ehildenb committed Aug 13, 2019
1 parent a2d0dd0 commit 615db59
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 2 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export LUA_PATH

.PHONY: all clean clean-submodules distclean install uninstall \
deps all-deps llvm-deps haskell-deps repo-deps system-deps k-deps ocaml-deps plugin-deps libsecp256k1 libff \
build build-ocaml build-java build-node build-kore split-tests \
build build-ocaml build-java build-node build-llvm build-web3 split-tests \
defn java-defn ocaml-defn node-defn haskell-defn llvm-defn \
test test-all test-conformance test-slow-conformance test-all-conformance \
test-vm test-slow-vm test-all-vm test-bchain test-slow-bchain test-all-bchain \
Expand Down Expand Up @@ -166,13 +166,17 @@ build-node: $(node_kompiled)
build-haskell: $(haskell_kompiled)
build-llvm: $(llvm_kompiled)

build-web3: MAIN_MODULE=WEB3
build-web3: MAIN_DEFN_FILE=web3
build-web3: $(llvm_kompiled)

# Tangle definition from *.md files

concrete_tangle:=.k:not(.node):not(.symbolic),.standalone,.concrete
symbolic_tangle:=.k:not(.node):not(.concrete),.standalone,.symbolic
node_tangle:=.k:not(.standalone):not(.symbolic),.node,.concrete

k_files=driver.k data.k network.k evm.k krypto.k edsl.k evm-node.k
k_files=driver.k data.k network.k evm.k krypto.k edsl.k evm-node.k web3.k
EXTRA_K_FILES+=$(MAIN_DEFN_FILE).k
ALL_K_FILES:=$(k_files) $(EXTRA_K_FILES)

Expand Down
12 changes: 12 additions & 0 deletions data.md
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,18 @@ We need to interperet a `ByteArray` as a `String` again so that we can call `Kec
// -----------------------------------------------
rule #padByte( S ) => S requires lengthString(S) ==K 2
rule #padByte( S ) => "0" +String S requires lengthString(S) ==K 1
syntax String ::= #unparseQuantity( Int ) [function]
// ----------------------------------------------------
rule #unparseQuantity( I ) => "0x" +String Base2String(I, 16)
syntax String ::= #unparseData ( Int, Int ) [function]
| #unparseDataByteArray ( ByteArray ) [function]
// ----------------------------------------------------------------
rule #unparseData( _, 0 ) => "0x"
rule #unparseData( DATA, LENGTH ) => #unparseDataByteArray(#padToWidth(LENGTH,#asByteStack(DATA)))
rule #unparseDataByteArray( DATA ) => replaceFirst(Base2String(#asInteger(#asByteStack(1) ++ DATA), 16), "1", "0x")
```

Recursive Length Prefix (RLP)
Expand Down
8 changes: 8 additions & 0 deletions kevm
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ run_search() {
run_krun --search --pattern "$search_pattern" "$@"
}

run_web3() {
run_krun -cCHAINID="$cCHAINID" -pCHAINID='printf %s' "$@"
}

run_klab() {
local run_mode klab_log

Expand Down Expand Up @@ -140,6 +144,7 @@ if [[ "$run_command" == 'help' ]]; then
$0 kast [--backend (ocaml|java)] <pgm> <output format> <K arg>*
$0 prove [--backend (java|haskell)] <spec> <K arg>* -m <def_module>
$0 search [--backend (java|haskell)] <pgm> <pattern> <K arg>*
$0 web3 [--backend (llvm)] <pgm> <K arg>*
$0 klab-run <pgm> <K arg>*
$0 klab-prove <spec> <K arg>* -m <def_module>
$0 klab-view <spec>
Expand All @@ -149,6 +154,7 @@ if [[ "$run_command" == 'help' ]]; then
$0 kast : Parse an EVM program and output it in a supported format
$0 prove : Run an EVM K proof
$0 search : Search for a K pattern in an EVM program execution
$0 web3 : Run version of semantics which understand Web3 RPC (**UNDER CONSTRUCTION**)
$0 klab-(run|prove) : Run program or prove spec and dump StateLogs which KLab can read
$0 klab-view : View the statelog associated with a given program or spec
Expand Down Expand Up @@ -181,6 +187,7 @@ run_file="$1" ; shift

cMODE="\`${MODE:-NORMAL}\`(.KList)"
cSCHEDULE="\`${SCHEDULE:-BYZANTIUM}_EVM\`(.KList)"
cCHAINID="#token(\"${CHAINID:-28346}\",\"Int\")"

case "$run_command-$backend" in

Expand All @@ -190,6 +197,7 @@ case "$run_command-$backend" in
interpret-@(ocaml|llvm) ) run_interpret "$@" ;;
prove-@(java|haskell) ) run_prove "$@" ;;
search-@(java|haskell) ) run_search "$@" ;;
web3-@(llvm) ) run_web3 "$@" ;;
klab-@(run|prove)-java ) run_klab "${run_command#klab-}" "$@" ;;
klab-view-java ) view_klab "$@" ;;
*) $0 help ; fatal "Unknown command on backend: $run_command $backend" ;;
Expand Down
169 changes: 169 additions & 0 deletions web3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
Web3 RPC JSON Handler
====================

```k
requires "evm.k"
module WEB3
imports EVM
imports EVM-DATA
configuration
<kevm-client>
<kevm/>
<blockchain>
<chainID> $CHAINID:Int </chainID>
</blockchain>
<web3request>
<jsonrpc> "" </jsonrpc>
<callid> 0 </callid>
<method> "" </method>
<params> [ .JSONList ] </params>
</web3request>
<web3result> .List </web3result>
</kevm-client>
syntax JSON ::= Int | Bool
| #getJSON ( JSONKey, JSON ) [function]
// -------------------------------------------------------
rule #getJSON( KEY, { KEY : J, _ } ) => J
rule #getJSON( _, { .JSONList } ) => { .JSONList }
rule #getJSON( KEY, { KEY2 : _, REST } ) => #getJSON( KEY, { REST } )
requires KEY =/=K KEY2
syntax Int ::= #getInt ( JSONKey, JSON ) [function]
// ---------------------------------------------------
rule #getInt( KEY, { KEY : VALUE:Int, _ } ) => VALUE
rule #getInt( _ , { .JSONList } ) => 0 // TODO: Need something better for nonexistent key/value
rule #getInt( KEY, { KEY2 : _, REST } ) => #getInt( KEY, { REST } )
requires KEY =/=K KEY2
syntax String ::= #getString ( JSONKey, JSON ) [function]
// ---------------------------------------------------------
rule #getString( KEY, { KEY : VALUE:String, _ } ) => VALUE
rule #getString( _ , { .JSONList } ) => "" // TODO: Need something better for nonexistent key/value
rule #getString( KEY, { KEY2 : _, REST } ) => #getString( KEY, { REST } )
requires KEY =/=K KEY2
syntax EthereumSimulation ::= List{JSON, " "}
// ---------------------------------------------
rule <k> J:JSON REST:EthereumSimulation => #loadRPCCall J ~> REST ... </k>
rule <k> J:JSON => #loadRPCCall J ... </k>
syntax KItem ::= "#loadRPCCall" JSON
// ------------------------------------
rule <k> #loadRPCCall J:JSON => #runRPCCall ... </k>
<jsonrpc> _ => #getString("jsonrpc", J) </jsonrpc>
<callid> _ => #getInt ("id" , J) </callid>
<method> _ => #getString("method" , J) </method>
<params> _ => #getJSON ("params" , J) </params>
syntax KItem ::= #sendResponse ( JSON )
// ---------------------------------------
rule <k> #sendResponse( J:JSON ) => . ... </k>
<web3result> ... ( .List => ListItem( J ) ) </web3result>
syntax KItem ::= "#runRPCCall"
// ------------------------------
rule <k> #runRPCCall => #net_version ... </k>
<method> "net_version" </method>
rule <k> #runRPCCall => #web3_clientVersion ... </k>
<method> "web3_clientVersion" </method>
rule <k> #runRPCCall => #eth_gasPrice ... </k>
<method> "eth_gasPrice" </method>
rule <k> #runRPCCall => #eth_blockNumber ... </k>
<method> "eth_blockNumber" </method>
rule <k> #runRPCCall => #eth_accounts ... </k>
<method> "eth_accounts" </method>
rule <k> #runRPCCall => #eth_getBalance ... </k>
<method> "eth_getBalance" </method>
rule <k> #runRPCCall => #eth_getStorageAt ... </k>
<method> "eth_getStorageAt" </method>
rule <k> #runRPCCall => #eth_getCode ... </k>
<method> "eth_getCode" </method>
syntax KItem ::= "#net_version"
// -------------------------------
rule <k> #net_version => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : Int2String( CHAINID ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<chainID> CHAINID </chainID>
syntax KItem ::= "#web3_clientVersion"
// -------------------------------
rule <k> #web3_clientVersion => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : "Firefly RPC/v0.0.1/kevm" } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
syntax KItem ::= "#eth_gasPrice"
// --------------------------------
rule <k> #eth_gasPrice => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : #unparseQuantity( PRICE ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<gasPrice> PRICE </gasPrice>
syntax KItem ::= "#eth_blockNumber"
// -----------------------------------
rule <k> #eth_blockNumber => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : #unparseQuantity( BLOCKNUM ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<number> BLOCKNUM </number>
syntax KItem ::= "#eth_accounts"
// --------------------------------
rule <k> #eth_accounts => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : [ #acctsToJArray( ACCTS ) ] } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<activeAccounts> ACCTS </activeAccounts>
syntax JSONList ::= #acctsToJArray ( Set ) [function]
// -----------------------------------------------------
rule #acctsToJArray( .Set ) => .JSONList
rule #acctsToJArray( SetItem( ACCT ) ACCTS:Set ) => #unparseData( ACCT, 20 ), #acctsToJArray( ACCTS )
syntax KItem ::= "#eth_getBalance"
// ----------------------------------
rule <k> #eth_getBalance ... </k>
<params> [ (DATA => #parseHexWord(DATA)), _ ] </params>
rule <k> #eth_getBalance => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : #unparseQuantity( ACCTBALANCE ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<params> [ DATA, TAG, .JSONList ] </params>
<account>
<acctID> DATA </acctID>
<balance> ACCTBALANCE </balance>
...
</account>
syntax KItem ::= "#eth_getStorageAt"
// ------------------------------------
rule <k> #eth_getStorageAt ... </k>
<params> [ (DATA => #parseHexWord(DATA)), QUANTITY:Int, _ ] </params>
rule <k> #eth_getStorageAt => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : #unparseQuantity( #lookup (STORAGE, QUANTITY) ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<params> [ DATA, QUANTITY, TAG, .JSONList ] </params>
<account>
<acctID> DATA </acctID>
<storage> STORAGE </storage>
...
</account>
syntax KItem ::= "#eth_getCode"
// -------------------------------
rule <k> #eth_getCode ... </k>
<params> [ (DATA => #parseHexWord(DATA)), _ ] </params>
rule <k> #eth_getCode => #sendResponse( { "id" : CALLID, "jsonrpc" : JSONRPC, "result" : #unparseDataByteArray( CODE ) } ) ... </k>
<jsonrpc> JSONRPC </jsonrpc>
<callid> CALLID </callid>
<params> [ DATA, TAG, .JSONList ] </params>
<account>
<acctID> DATA </acctID>
<code> CODE </code>
...
</account>
endmodule
```

0 comments on commit 615db59

Please sign in to comment.