1 Overview
This is an informal overview of Reach and the structure of a Reach program. The goal of this document is to give enough technical specifics to help you understand what Reach does, but it isn’t intended as either a tutorial or a reference. When you’re ready to really begin a project, you can start with one of those, or the workshop.
A recording of a live workshop that goes over this material is available on YouTube.
1.1 Decentralized applications
DApps are made of multiple agents interacting with each other through some backend consensus network, like Ethereum or Algorand. These agents act on behalf of principals that provide direction and authority through information. These principals might be humans or other autonomous agents or even committees and organizations with their own structure. The consensus network allows these agents to transfer and receive value in the form of network-specific tokens, like ETH or ALGO. The network also allows the creation of "contracts" that ensure that all agents follow the same rules as they take turns computing and publishing values and information. The details of these "contracts" are specific to each consensus network, but they are implicitly trusted by all agents and principals, because their operation can be independently verified to match the previously agreed-upon rules.
Participant backends are the agents acting on behalf of the principals.
Frontends are the technical representation of the interface between the participants and the principals.
A contract enforces the rules of the program, including the order of operation.
In Reach, a programmer only needs to specify the actions of participants—
1.2 A minimal Reach program
Let’s look at a simple Reach program where two principals, Alice and Bob, interact. In this DApp, Alice has some information that Bob might want and she has an amount of network tokens in mind that she’d like to trade for it.
You can look at the entire example program by visiting overview/index.rsh.
Get language support for Reach in your editor by visiting IDE/Text Editor Support.
The main part of the program looks like this:
1 'reach 0.1';
2
3 export const main =
4 Reach.App(
5 {},
6 [Participant('Alice', { request: UInt,
7 info: Bytes(128) }),
8 Participant('Bob', { want: Fun([UInt], Null),
9 got: Fun([Bytes(128)], Null) })],
10 (A, B) => {
.. // ...body...
30 } );
Line 1 specifies that this is a Reach program.
Line 3 defines the main export from program. main is the default used by Reach.
Line 4 specifies that it is an application.
Lines 6 and 7 specify the interface between Alice’s participant and frontend. In this case, Alice’s frontend must provide a number called request and a string called info.
Lines 8 and 9 specify the interface for Bob, which includes a function named want, that takes a number and returns
null
, as well as a function named got, that receives the information.Finally, line 10, binds these participants to the program identifiers
A
andB
.
The elided lines, 11 through 29, contain the body of the application, which we can divide into four parts.
.. // ...
11 A.only(() => {
12 const request = declassify(interact.request); });
13 A.publish(request);
14 commit();
.. // ...
Lines 11 and 12 specify that Alice takes a local step where she declassifies the amount of tokens requested. In Reach, all values from the frontend are secret until explicitly made public with declassify.
Line 13 has Alice joins the application by publishing that value and the logic of the program transitions to specifying what the contract does.
Line 14 has the contract commit to these values and continue the rest of the program.
At this point, Bob’s backend has learned the value of request
and can deliver it to Bob’s frontend for his approval. This happens next.
.. // ...
16 B.only(() => {
17 interact.want(request); });
18 B.pay(request);
19 commit();
.. // ...
Lines 16 and 17 has Bob perform that delivery.
interact.want
doesn’t explicitly return a boolean, because the frontend can not return if Bob doesn’t want to continue. A better version of this program might returnfalse
and have that communicated to Alice.Lines 18 and 19 have Bob join the application and submit a payment matching the appropriate amount and then the contract commits.
It’s now Alice’s turn again,
.. // ...
21 A.only(() => {
22 const info = declassify(interact.info); });
23 A.publish(info);
24 transfer(request).to(A);
25 commit();
.. // ...
Lines 21 and 22 specify that Alice declassifies the information.
Line 23 has her publish it.
Line 24 has the contract transfer the requested amount to her.
Line 25 commits the transactions on the consensus network.
The only thing left is for Bob’s backend to deliver the information to his frontend.
Line 27 and 28 do this.
Line 29 exits the program.
—
Reach programmers don’t need to think about details like contract storage, protocol diagrams, state validation, or network details; instead, they can focus exclusively on the business logic of their application.
1.3 Compile
After a Reach programmer writes this application in a file like overview/index.rsh, they could run
$ reach compile overview/index.rsh
and the build directory will contain a new file named index.main.mjs, which contains a JavaScript implementation of a backend for each participant, as well as the Ethereum bytecode for the contract.
If you are curious, you can take a look at this file by going to overview/build/index.main.mjs. The Ethereum bytecode is not readable, but if you understand Solidity, you may want to look at overview/build/index.main.sol to see the original Solidity source that it is compiled from. Reach can leave files like these in place when run with --intermediate-files.
For this 30 line application, the Reach compiler generated 885 lines of JavaScript code in two functions, one for Alice and one for Bob. Separately, it generated 146 lines of Solidity code to implement the contract. If a programmer wasn’t using Reach, they would have to write these 1031 lines in these three modules separately and keep them synchronized at every step of the development process.
Moreover, Reach doesn’t only work for Ethereum: it is blockchain agnostic and can be easily configured to use a different connector to target other consensus networks, like Algorand. Nor is Reach tied to JavaScript, it can be configured to target other backend languages, like Go.
1.4 Verify
Reach doesn’t just compile your program, it also verifies it and ensures that entire categories of errors don’t occur. For example, it always guarantees that the balance in the contract at the end of the program is zero. This is important, because if it were not true, then tokens would be locked away by the contract and inaccessible.
For this example program, it is obvious that when a single transfer of request
goes in at line 18 and a single transfer of request
goes out at line 24, then the balance is zero at the end of the program.
We could make a small tweak, however, to demonstrate things going wrong.
Let’s change the third step to leave a single unit in the balance:
.. // ...
21 A.only(() => {
22 const info = declassify(interact.info); });
23 A.publish(info);
24 transfer(request-1).to(A); // <--- Oops!
25 commit();
.. // ...
And then run the compiler
$ reach compile overview/index-error.rsh
It will print out a detailed error message showing the violation.
.. // ... |
2 Verifying for generic connector |
3 Verifying with mode = VM_Honest |
4 Verification failed: |
5 in VM_Honest mode |
6 of theorem TClaim CT_Assert |
7 msg: "balance assertion" |
8 at ./index-error.rsh:29:11:application |
9 |
10 // Violation witness |
11 const interact_Alice_request = 1; |
12 // ^ from interaction at ./index-error.rsh:4:12:application |
13 |
14 // Theorem formalization |
15 assert(0 == (interact_Alice_request - (interact_Alice_request - 1))); |
16 |
.. // ... |
Verification failures include a lot of information, such as a concrete counter-example showing values that could have been provided by frontends that would lead to the property failing to hold.
In this case, it reports that if Alice were to pass an interact.request
over 1
at the start of the program on line 4, then the balance of the contract would not be provably 0
at the end of the program.
—
Reach programmers don’t need to be worried about entire categories of errors, because the compiler automatically checks their code and ensures that those errors aren’t present. Of course, there’s a lot more to say about the details of automatic verification; indeed, it is one of the most powerful features of Reach, but we’ll leave it at that for now.
1.5 Interface
The backend produced by the Reach compiler isn’t an application on its own. In particular, each participant needs a frontend to interact with. In a real deployment, this interfacing code would be tied to a GUI, like a Web or smartphone app. Let’s look at a simple command-line version that demonstrates how it would work for testing on a private devnet.
You can look at the entire example interface program by visiting overview/index.mjs.
The program is just 23 lines long and the shell of it is quite simple:
1 import { loadStdlib } from '@reach-sh/stdlib';
2 import * as backend from './build/index.main.mjs';
3
4 (async () => {
5 const stdlib = await loadStdlib();
6
7 const accAlice = await stdlib.newTestAccount(stdlib.parseCurrency(5));
8 const accBob = await stdlib.newTestAccount(stdlib.parseCurrency(10));
9
10 const ctcAlice = accAlice.deploy(backend);
11 const ctcBob = accBob.attach(backend, ctcAlice.getInfo());
12
13 await Promise.all([
.. // ...
22 ]);
23 })();
Lines 1 and 2 import the Reach standard library loader and the compiled app backend.
Line 5 dynamically loads the appropriate network-specific Reach stdlib, based on the REACH_CONNECTOR_MODE environment variable. If unspecified, Reach’s ETH stdlib will be used by default. All of Reach’s network-specific stdlibs adhere to a common interface, which allows you to write programs which are network-agnostic.
Lines 7 and 8 initialize new test accounts for Alice and Bob.
Line 10 has Alice deploy the contact on the consensus network.
Line 11 has Bob attach to the contract. The value
ctcAlice
contains no secret information and could easily be printed out and shared with Bob outside of the consensus network.Lines 13 through 22 launch the backends and wait for their completion, we’ll look at the details in a moment.
This code will be very similar for all testing programs and demonstrates how straight-forward it is to scaffold a Reach application for testing.
Let’s look at initializing and interfacing each participant, starting with Alice.
.. // ...
14 backend.Alice(ctcAlice, {
15 request: stdlib.parseCurrency(5),
16 info: 'If you wear these, you can see beyond evil illusions.'
17 }),
.. // ...
Line 14 extracts the backend for Alice.
Line 14 also passes it the appropriate standard library and contract handle. It needs these to be able to interface with the consensus network.
Line 15 provides the
request
value.Line 16 provides the
info
value.
Let’s look at Bob next.
.. // ...
18 backend.Bob(ctcBob, {
19 want: (amt) => console.log(`Alice asked Bob for ${stdlib.formatCurrency(amt)}`),
20 got: (secret) => console.log(`Alice's secret is: ${secret}`),
21 }),
.. // ...
Line 18 initializes Bob just like Alice.
Line 19 provides his
want
function, which produces a log message and always accepts.Line 20 provides his
got
function, which displays the secret on the console as well.
—
Reach completely abstracts all the details of the chosen consensus network from the programmer, except for those directly impinging on business decisions, like the amounts of currency transacted. Reach allows programmers to focus on the business logic of their application at every stage, from the core application to the interfacing elements.
1.6 Execute
It’s now time to execute this test program and ensure that everything is working correctly. In this case, we’ve set up our application simply: there’s one Reach file for the application and one JavaScript file for the interface. This is a common practice, so Reach comes with a simple wrapper script to build and execute such applications. We just run:
$ reach run
And then Reach
compiles overview/index.rsh;
creates a temporary Node.js package;
builds a Docker image based on Reach’s standard image for the package; and,
runs the application connected to Reach’s standard private Ethereum devnet image. (If you add REACH_CONNECTOR_MODE=ETH-test-embedded-ganache to the command line, then Reach will use Ganache instead.)
On typical developer laptops, this entire process takes seconds and can be completely integrated into existing development IDEs, like VSCode, so Reach developers can compile, verify, build, launch, and test their Reach app with a single command.
—
Reach completely abstracts all the details of building and maintaining consensus network test environments and build scripts from the programmer, so they can focus exclusively on the business logic of their application. In fact, Reach works for multiple networks, so if we instead run
$ REACH_CONNECTOR_MODE=ALGO reach run
Then Reach will instead start up a private Algorand devnet image and use the Algorand connector. The developer does not need to change anything about their program, because Reach is entirely agnostic to the consensus network choice during deployment.
1.7 Web app
You can watch a 7-minute video on YouTube which demonstrates this section’s code in action and provides a brief explanation of how it works.
The previous execution uses Node.js to perform a test run at the command line. However, most Reach developers deploy their DApps via a Web application, as we describe below.
A Web deployment uses the exact same index.rsh file, but connected to a React-based index.js file. (It also uses some simple React views and css to go with it.) Let’s take a look at some snippets from the React index.js and compare with the Node.js index.mjs from before:
... // ...
7 import * as backend from './build/index.main.mjs';
8 import * as reach from '@reach-sh/stdlib/ETH';
... // ...
At the top of the file, we import the Reach-generated backend as backend
and the standard library as reach
.
... // ...
27 async componentDidMount() { // from mode: ConnectAccount
28 const acc = await reach.getDefaultAccount();
... // ...
We hook into the App component’s lifecycle event componentDidMount
in order to fetch the user’s account.
getDefaultAccount
automatically interacts with browser extensions, like MetaMask, to get the user’s
currently-selected account.
Reach is able to deploy contracts and send transactions to the consensus network by prompting the user through the extension’s API,
without the Reach programming having to do this in manually.
This is just like how in the Node.js deployment, the Reach programmer does need to decode the details of the underlying consensus network’s interaction API.
... // ...
71 async deploy() { // from mode: Deploy
72 const ctc = this.props.acc.deploy(backend);
73 this.setState({mode: 'EnterInfo', ctc});
74 const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
75 this.setState({ctcInfoStr});
76 }
... // ...
Our React component has a method called deploy
that actually deploys the contract on the network, using the same calls as in the test deployment:
on line 72 we call the acc.deploy
function,
and on line 74, we call the ctc.getInfo
function;
exactly as we did for the Node.js program.
... // ...
79 async runBackend() { // from mode: RunBackend
80 const {ctc, requestStandard, info} = this.state;
81 this.setState({mode: 'BackendRunning'});
82 const request = reach.parseCurrency(requestStandard);
83 await backend.Alice(ctc, {request, info});
84 this.setState({mode: 'BackendRan'});
85 }
... // ...
Similarly, we implement a runBackend
method that executes the Reach program as Alice using information gathered from the React UI.
... // ...
112 async runBackend(ctcInfoStr) { // from mode: RunBackend
113 const ctcInfo = JSON.parse(ctcInfoStr);
114 const ctc = this.props.acc.attach(backend, ctcInfo);
115 this.setState({mode: 'ApproveRequest'});
116 const interact = {
117 want: (request) => this.setState({mode: 'DisplayInfo', requestStandard: reach.formatCurrency(request, 4)}),
118 got: (info) => this.setState({info}),
119 };
120 await backend.Bob(ctc, interact);
121 }
... // ...
We implement a similar method in the Bob
component that runs the backend as Bob,
We specify Alice’s and Bob’s respective participant interact interfaces
just as we would in Node.js.
In the React program,
we have the ability to leverage Bob’s interact
functions as callbacks
that can update the React state
in order to display to, or harvest information from, the React user interface.
You can install the @reachsh/stdlib JavaScript library into your React project, or for convenience, instead of setting up a React project, you can simply use the command
$ reach react
This command runs your DApp with the React development server in a Docker container which has Reach and React JavaScript dependencies pre-installed, so it starts up much faster than building them yourself.
1.8 Next steps
In this overview, we’ve briefly described the structure and fundamental concepts of a Reach application. We’ve shown how to construct a simple program, compile it, connect an interface, test at the command-line, and deploy it using a React Web application. Since this is only a brief overview of what Reach can do, we left a lot out. But even so, it is should be clear why Reach is easiest and safest programming language for decentralized application development.
Furthermore, this program has many flaws and should not be used in practice. For example, it provides no protection to Bob in the event that Alice fails to deliver the information and makes no attempt to ensure that the information is what he wants. Reach allows you to abstract away the low-level details of your decentralized program and focus on these sorts of bigger picture issues. In the rest of the guide, we discuss design issues like this. For example,
Effectively using automatic verification to check your application;
Fortifying your application against non-participation;
Building interaction abstractions for related applications.
However, unless you’re ready to dive deep now, the next steps for you are to:
Work through the tutorial;
Join the Discord community.
Thanks for being part of Reach!