As promised, the giant pull request that does ALL the things, most notably:
Provide a server interface for an underlying Simulation.
Provide a client interface for transparently communicating with the server.
Separate each part into it's own library so the client can be imported into arbitrary test code and used easily while the server runs in a different process (or even a different machine).
Context on the package as a whole is below, and a suggested plan for reviewing this code in "bite-size" pieces is in the section titled "Recommended Review Order"
Relevant Component Packages
simulation
The Simulation struct, found in the eth2/simulation package, contains the interface for simulating an Eth2-like system. Currently, it has the following methods described in the repo's README:
CreateExecutionEnvironment
Adds an EE to the simulated Eth2 environment
GetExecutionEnvironment
Retrieves a previously-added EE
GetExecutionEnvironmentState
Retrieves the current EE state for a given shard and EE
CreateShardBlock
Creates a shard block by executing all given shard transactions, which run EE code and update EE state for the given shard.
GetShardBlock
Retrieve a shard block
GetShardState
Retrieve some internally stored state for the given shard
simulation_args
eth2/simulation_args contains all structs found in the external-facing interface of the Simulation.
As requested, these structs do not use any "internal" types found in the Simulation (eg. VariableList), and instead only use "standard" Rust types (eg. Vec).
This has the advantage of allowing users of the Simulation to not need to "know" about the specifics of any internal types -- making the Simulation interface easier to use and understand.
One might expect to find these structs inside the simulation package (the same package where Simulation struct is defined). By putting them in a separate package, it means the external-facing interface of Simulation can be imported by other packages that only care about the Simulation interface, but don't necessarily want to have a dependency on the entire eth2/simulation codebase.
simulation_server
The simulation_server package defines an HTTP / JSON server that "wraps" the eth2/simulation package.
The purpose of this server is simply to allow the simulation package to be accessible via network requests. This way, a potential user of the Simulation doesn't need to necessarily import or run their own copy of the eth2/simulation package, they can instead simply send HTTP requests to an already-running simulation_server instance (whether running in a separate process on their machine or running on a separate machine online).
simulation_client
The simulation_client package defines a client with the same external-facing interface as the already-described eth2/simulation::Simulation struct.
The major difference with this package is that instead of calling the Simulation directly, the SimulationClient instead sends an HTTP request to a SimulationServer instance, and handles all encoding/decoding "under the hood" so calling the SimulationClient "feels" just like calling Simulation directly.
As requested, this allows users to access a running Simulation in their code (via under-the-hood network requests to a running SimulationServer) without necessarily needing to run a Simulation instance themselves.
Dependency Graph of All Component Packages
Notice that simulation_client ONLY depends on simulation_args (ie. the interface definitions), and does not need to import the server code or actual simulation code.
Design Decision Worth Discussing
I decided to have SimulationServer expose a "non-standard" HTTP/JSON api.
The SimulationServer api currently only exposes POST endpoints that expect a JSON-serialized eth2/simulation_args argument struct as the data body:
Eg.
POST/create-execution-environment
data body: JSON-serialized eth2/simulation_args::CreateExecutionEnvironment
POST/get-execution-environment
data body: JSON-serialized eth2/simulation_args::GetExecutionEnvironment
Advantages of this approach:
Makes the SimulationServer interface much easier to predict.
Any methods on Simulation have a 1:1 mapping on SimulationServer, and use the same arguments.
Makes the Simulation, SimulationServer, and SimulationClient easier to update.
Adding a new interface method to all 3 components would simply require defining a new SimulationArgs struct and then adding it (via very easy-to-predict patterns) to the 3 components.
Updating the arguments of an existing method only requires modifying the corresponding SimulationArgs struct.
Makes the SimulationClient easier to update.
Under this approach, defining a SimulationClient method is extremely easy to do -- simply send a POST request to the name of the method with a JSON-serialized body containing the corresponding simulation_args struct.
TL; DR: Makes the SimulationServer and SimulationClient methods easier to understand, create, and modify.
Recommended Review Order
(with links and descriptions)
I believe the easiest way to review this code in bite-size pieces is to follow the review order below:
simply read the description of the file, open the link in a new tab, then review it. Once you've reviewed the file, you can close the tab and go to the next bullet point on the list.
I attempted to order everything so that there would be much less need to jump around within the "changed files" page. Once you get to a file, the description + context from reviewing the prior files should give most of the context necessary for reviewing that file.
Contains ssz-compliant types for common collections (eg. VariableList, FixedVector, etc).
Most of this code is already reviewed, the only new changes here are to update the Error type in this package so it can be wrapped with other errors.
In this case, the easiest way to implement the necessary methods was to just add the Snafu derive to the Error struct, but this could have been accomplished by manually implementing methods also (eg. std::fmt::Display)
eth2/simulation_args (public-facing arguments that define the incoming interface for the Simulation (and SimulationServer, SimulationClient))
All the types used for the interface of Simulation, SimulationServer, and SimulationClient are defined here.
Includes conversion methods for converting some of these "external" types to the "internal" representations (ie. the SSZ-compliant representations of types that are defined in eth2/types)
Also defines a public Error type, like most of the other packages in this repo.
Note that these structs are used to define the interface of methods in simulation.rs
Many of the changes here are related to the renaming of this package from notion-server to simulation_server.
Most of the other changes are related to making the SimulationServer parameterized by EthSpec, so if the spec changes in future, or if we want to be able to run this simulation with new spec-defined constants, it is easy to do.
Most of this is Sam's original API code, modified slightly to reflect the new package name and some of the new types.
Implements all the HTTP methods of the SimulationServer, each of which accepts POST requests with args in the JSON body (as discussed above in "Design Decision Worth Discussing")
Most of this is Sam's original server-instantiation code, modified slightly to reflect the new package name and to have a predictable default port (though it is still configurable via CLI argument).
Just an example of how one might interact with the SimulationClient in a binary.
Note that this package does NOT depend on SimulationServer or Simulation (or their containing packages)
Next Steps
A working example version of simulation_server is already up and running at https://example-e2-simulation-server.herokuapp.com.
You can test this out by instantiating a SimulationClient instance in your own code with the base_url above, and use SimulationClient to interact with it.
Check out the simulation_client_example binary for an example.
Disclaimer: there is no DB backing the Heroku test server, so state may be "reset" every so often if the server restarts -- this is intended as a proof of concept for now.
Decisions for future:
How to approach publishing more "permanent" packages for the above (currently, this repo borrows code from sigp/lighthouse repo and modifies it in some cases to be compatible with code here). Long term, we may want to instead use official sigp/lighthouse packages that are already compatible and are available on crates.io.
(most needed: package of SSZ-compliant types, so we can get rid of the local eth2/utils/ssz_types package.)
Overview
As promised, the giant pull request that does ALL the things, most notably:
Simulation
.Context on the package as a whole is below, and a suggested plan for reviewing this code in "bite-size" pieces is in the section titled "Recommended Review Order"
Relevant Component Packages
simulation
The
Simulation
struct, found in theeth2/simulation
package, contains the interface for simulating an Eth2-like system. Currently, it has the following methods described in the repo's README:CreateExecutionEnvironment
GetExecutionEnvironment
GetExecutionEnvironmentState
CreateShardBlock
GetShardBlock
GetShardState
simulation_args
eth2/simulation_args
contains all structs found in the external-facing interface of theSimulation
.As requested, these structs do not use any "internal" types found in the
Simulation
(eg.VariableList
), and instead only use "standard" Rust types (eg.Vec
). This has the advantage of allowing users of theSimulation
to not need to "know" about the specifics of any internal types -- making theSimulation
interface easier to use and understand.One might expect to find these structs inside the
simulation
package (the same package whereSimulation
struct is defined). By putting them in a separate package, it means the external-facing interface ofSimulation
can be imported by other packages that only care about theSimulation
interface, but don't necessarily want to have a dependency on the entireeth2/simulation
codebase.simulation_server
The
simulation_server
package defines an HTTP / JSON server that "wraps" theeth2/simulation
package.The purpose of this server is simply to allow the
simulation
package to be accessible via network requests. This way, a potential user of theSimulation
doesn't need to necessarily import or run their own copy of theeth2/simulation
package, they can instead simply send HTTP requests to an already-runningsimulation_server
instance (whether running in a separate process on their machine or running on a separate machine online).simulation_client
The
simulation_client
package defines a client with the same external-facing interface as the already-describedeth2/simulation::Simulation
struct.The major difference with this package is that instead of calling the
Simulation
directly, theSimulationClient
instead sends an HTTP request to aSimulationServer
instance, and handles all encoding/decoding "under the hood" so calling theSimulationClient
"feels" just like callingSimulation
directly.As requested, this allows users to access a running
Simulation
in their code (via under-the-hood network requests to a runningSimulationServer
) without necessarily needing to run aSimulation
instance themselves.Dependency Graph of All Component Packages
Notice that
simulation_client
ONLY depends onsimulation_args
(ie. the interface definitions), and does not need to import the server code or actual simulation code.Design Decision Worth Discussing
I decided to have
SimulationServer
expose a "non-standard" HTTP/JSON api. TheSimulationServer
api currently only exposesPOST
endpoints that expect a JSON-serializedeth2/simulation_args
argument struct as the data body:Eg.
POST
/create-execution-environment
eth2/simulation_args::CreateExecutionEnvironment
POST
/get-execution-environment
eth2/simulation_args::GetExecutionEnvironment
Advantages of this approach:
SimulationServer
interface much easier to predict.Simulation
have a 1:1 mapping onSimulationServer
, and use the same arguments.Simulation
,SimulationServer
, andSimulationClient
easier to update.SimulationArgs
struct and then adding it (via very easy-to-predict patterns) to the 3 components.SimulationArgs
struct.SimulationClient
easier to update.SimulationClient
method is extremely easy to do -- simply send a POST request to the name of the method with a JSON-serialized body containing the correspondingsimulation_args
struct.TL; DR: Makes the
SimulationServer
andSimulationClient
methods easier to understand, create, and modify.Recommended Review Order
(with links and descriptions)
I believe the easiest way to review this code in bite-size pieces is to follow the review order below: simply read the description of the file, open the link in a new tab, then review it. Once you've reviewed the file, you can close the tab and go to the next bullet point on the list.
I attempted to order everything so that there would be much less need to jump around within the "changed files" page. Once you get to a file, the description + context from reviewing the prior files should give most of the context necessary for reviewing that file.
eth2/simulation
(core logic, start here)Error
type for this package, defines the public-facing exports.eth2/types
(internal representation of data inside theSimulation
, contains spec- and ssz-compliant types)BeaconState
.ExecutionEnvironment
(not spec-compliant bc this is not yet in the spec).EthSpec
vs containing a hard-coded EE size limit.ShardBlock
.EthSpec
vs containing a hard-coded transaction limit.ShardState
and updated the struct to be parameterized byEthSpec
EeIndex
Slot
,Epoch
,Root
,ShardSlot
, etc.new
methods and a conversion method forRoot
.eth2/utils/ssz_types
Error
type in this package so it can be wrapped with other errors.Snafu
derive to theError
struct, but this could have been accomplished by manually implementing methods also (eg.std::fmt::Display
)eth2/simulation_args
(public-facing arguments that define the incoming interface for theSimulation
(andSimulationServer
,SimulationClient
))Simulation
,SimulationServer
, andSimulationClient
are defined here.eth2/types
)Error
type, like most of the other packages in this repo.simulation_server
notion-server
tosimulation_server
.SimulationServer
parameterized byEthSpec
, so if the spec changes in future, or if we want to be able to run this simulation with new spec-defined constants, it is easy to do.SimulationServer
, each of which accepts POST requests with args in the JSON body (as discussed above in "Design Decision Worth Discussing")Error
for this package was also modified slightly to fit with the slightly modified package structure.simulation_client
Error
type for this package.SimulationClient
methods.simulation_client_example
SimulationClient
in a binary.SimulationServer
orSimulation
(or their containing packages)Next Steps
simulation_server
is already up and running athttps://example-e2-simulation-server.herokuapp.com
.SimulationClient
instance in your own code with thebase_url
above, and useSimulationClient
to interact with it.simulation_client_example
binary for an example.sigp/lighthouse
repo and modifies it in some cases to be compatible with code here). Long term, we may want to instead use officialsigp/lighthouse
packages that are already compatible and are available on crates.io.eth2/utils/ssz_types
package.)