Morra Game Using Reach
Overview
Table of Contents
1. Background
2. Reach overview
3. Building the dApp, Backend and Frontend
4. Reach dApp Life Cycle
5. Install Reach and Command Line Interface
6. Initialize dApp
7. Environment Variables
8. Frontend Support in JavaScript
9. Accounts
10. Contracts
11. Sample dApp Solution
12. Verification
13. Backend
14. Frontend
15. Auditing
16. Mathematical proofs
17. Cryptographic Commitment Scheme
18. Timeouts and nonparticipation
19. Logging on the backend
20. Platform Specific code
21. Flow commands
22. RPC
23. Python RPC Install and Run
24. JavaScript RPC Install and Run
25. Go RPC Install and Run
26. TestNet and MainNet
1. Background
This solution will cover a Reach overview, installation and Command Line Interface (CLI), environment variables, accounts, frontend and backend components. Also this solution covers verification, auditing, mathematical proofs, cryptographic commitment scheme, timeouts and non-participation, logging, platform specific code, flow commands, Remote Procedure Calls (RPC) as well as debugging and how to deploy to TestNet and MainNet.
The following solution is for a Morra game simulation. The object of the game is to throw fingers with an opponent and guess the total number of fingers shown. Throws are repeated until there is a winner. It can also be played with teams.
A Reach dApp is blockchain agnostic. The focus for a developer using Reach is the business logic. In other words, Reach takes care of the internals on contract storage, protocol diagrams, state validation and network details in general. With Reach, the automatic verification of a dApp, alone, is one great reason to use Reach. This protects against blockchain attacks, guarantees contract balance is zero and prevents locked away tokens by the contract that are inaccessible. Reach facilitates blockchain properties, like token linearity.
2. Reach overview
Reach is a Cross-Blockchain deployment and development tool. The Reach frontend and backend provide a separation of concerns as a traditional Client / Server or N-tier architecture solution would do. This provides the ability to interact between the backend calling functions in the frontend. Reach deploys the dApp to the Algorand blockchain and provides verification via mathematical proofs and assertions.
3. Building the dApp, Backend and Frontend
The Backend provides the implementation of the dApp and determines what is published to the blockchain and how. The backend handles solution implementation, Frontend interfaces, Participant definitions, verification and commitment as well as dApp logic. The code for the completed backend for the Morra solution below can be found in index.rsh.
The Frontend provides functions on accounts such as creating accounts, interact method logic, dApp deployment and any blockchain specific code. The frontend can be created in a variety of high level languages using Remote Procedure Calls (RPC) such as JavaScript, Python, Go and C# and deployed to platforms such as iOS, Android, websites and console apps. The code for the completed frontend for the Morra solution below can be found in index.mjs.
The dApp is compiled and deployed to the Algorand Blockchain. Figure 1-1 below shows how all of the parts work together.
Figure 1-1 – Reach with Algorand
4. Reach dApp Life Cycle
The Application Life Cycle for creating a dApp with Reach is done entirely within the product, unlike other blockchain development environments that typically include several different tools.
- Protocol Design – Reach programming language
- Smart Contract– Reach compiler
- Middleware – Reach API
- Frontend – Build with JavaScript or RPC language support for Python, JavaScript, Go and C#
- Testing – Reach Run
- Verification – Every Reach compilation includes a thorough formal verification of the dApp using a SMT-based theorem prover.
- Deployment – Reach compiler configurations to deploy to Algorand Blockchain
5. Install Reach and Command Line Interface
Reach can be installed on Windows or Mac OS. Docker install is a prerequisite.
To install Reach, see the getting started guide.
Reach supports a command line interface for development. Reach Commands are as follows.
Command | Description |
---|---|
compile | Compile an app. |
clean | Delete compiled artifacts. |
init | Set up source files for a simple app. |
run | Run a simple app. |
down | Halt any Dockerized devnets for this app. |
scaffold | Set up Docker scaffolding for a simple app. |
react | Run a simple react app. |
rpc-server | Run an RPC server + frontend with development configuration. |
devnet | Run only the devnet |
upgrade | Upgrade Reach |
update | Update Reach Docker images |
docker-reset | Docker kill and rm all images |
version | Display version. |
hashes | Display git hashes used to build each Docker image. The latest hashes can be confirmed in #releases Discord channel. All the hashes should be the same. |
help | Show this info. |
Note: After installing reach, the images must be downloaded. This can be accomplished by running
./reach update
./reach hashes
provides hashes of the individual components of Reach. The list of hashes should all be the same and the most recent can be verified in the #releases channel on the Reach discord Server.
For more information on version mismatches click here.
6. Initialize dApp
The easiest way to get started is to use the reach init
command to generate ‘Hello World’ backend index.rsh
and frontend index.mjs
files.
$ ./reach init
writing index.rsh
writing index.mjs
The code generated can be found here for the Backend index.rsh and the Frontend index.mjs
Run the Hello World application
$ ./reach run
Output should say something like this:
Hello, Alice and Bob!
Launching...
Starting backends...
Goodbye, Alice and Bob!
7. Environment Variables
Environment variables are used by the Backend. This makes it easier to control the deployment and execution of your dApp. For example, to see debug messages, set the REACH_DEBUG environment variable to any non-empty variable.
Variable | Description |
---|---|
REACH_VERSION | Signifies what version of Reach |
REACH_CONNECTOR_MODE | Specifies which context to run in and determines which blockchain to deploy to. ALGO-live, uses a live Algorand network node, specified by the environment variables documented in the Algorand connector section ALGO-browser, uses the Algorand Wallet Reach Browser Spec ARC-0011. ALGO-devnet, which uses a Dockerized private Algorand network. |
REACH_DEBUG | If set to any non-empty value, enables debug messages from the Reach standard library, which will appear in the console. |
REACH_RPC_KEY | Used to determine the RPC server key. If not defined, it defaults to opensesame , and a warning will appear in the console stating that the development key is being used. |
REACH_RPC_PORT | Used to determine which port to bind to. It defaults to 3000 . |
REACH_RPC_TLS_CRT | Used to determine the path to the TLS crt file, which must be in the ./tls directory. It defaults to reach-server.crt . |
REACH_RPC_TLS_PASSPHRASE | Used to determine the TLS passphrase. It defaults to rpc-demo . |
REACH_RPC_TLS_REJECT_UNVERIFIED | This value determines whether to verify the TLS certificate of the Reach RPC Server instance. If it is not present, the client library must default to the value of the environment variable REACH_RPC_TLS_REJECT_UNVERIFIED. If that is not present, it must default to true. To disable verification, set this value to the string: “0”; any other value will be considered to mean “enable”. |
Backends must respect the following environment variables:
Variable | Description |
---|---|
ALGO_TOKEN | Used as the API token for your algod. |
ALGO_SERVER | Used as the address of your algod. |
ALGO_PORT | Used as the port of your algod. |
ALGO_INDEXER_TOKEN | Used as the API token for your indexer. |
ALGO_INDEXER_SERVER | Used as the address of your indexer. |
ALGO_INDEXER_PORT | Used as the port of your indexer. |
ALGO_FAUCET_PASSPHRASE | Used as the mnemonic for the faucet of your network. This is useful if you are running your own testing network. |
Sometimes it may be convenient to use the reach run command, preceded by setting the REACH_CONNECTOR_MODE, especially when testing multiple blockchain deployments.
$ REACH_CONNECTOR_MODE=ALGO ./reach run
$ REACH_CONNECTOR_MODE=ALGO-devnet ./reach run**
Environment variables can also be set like this:
`export REACH_CONNECTOR_MODE="ALGO-devnet"`
More information here in Reach Docs.
8. Frontend Support in JavaScript
The Reach JavaScript standard library, @reach-sh/stdlib can be included in your application by using the following code:
import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
const stdlib = await loadStdlib(process.env);
9. Accounts
Several functions are available for the JavaScript frontends. The dispenser faucet is built-in when creating a new account with funds for dev/private networks. For example, to create and fund a new account with 10 Algos, use the following command: newTestAccount(10)
.
If deploying to TestNet, accounts can be funded from the TestNet dispenser. Several methods are available to work with accounts as shown below. The newAccountFromMnemonic method can be used for development and testing purposes as well, but in production, developers should use one or more of the wallet technologies available in the Algorand ecosystem.
A few of the more used functions are below. See all Account related functions here.
- getDefaultAccount() The meaning of “default account” varies depending on what context the Reach standard library is executed. When running in the browser, the default account will be connected to a wallet such as AlgoSigner. When running in node.js while connected to one of reach’s standard devnets, the default account will be connected to a faucet on the devnet. The function will fail if no sensible default account can be accessed for the current context.
- newAccountFromSecret(secret: string) Returns an account specified by the given secret.
- newAccountFromMnemonic(phrase: string) Returns an account specified by the given mnemonic phrase.
- newTestAccount(balance) Returns a new account with a given balance of network tokens. This can only be used in private testing scenarios, as it uses a private faucet to issue network tokens.
- createAccount() Creates a new account with a zero balance.
- acc.getAddress() Returns the account specified in the acc object string address.
- acc.setDebugLabel(string) This function allows setting the debug label for the account specified in the acc object. If no label is provided, then the first four digits of the account address will be used.
- balanceOf(acc, token?) => Returns a Promise for the balance of network tokens (or non-network tokens if token is provided) held by the account provided by the acc argument.
- transfer(from:acc, to:acc, amount, token?) => Performs a transfer between from and to accounts. If a token is not provided, then the transfer is in the default network tokens; otherwise, it is of the designated non-network token. The returned Promise will only be resolved after the transfer completes.
10. Contracts
Reach contracts are accounts with three extra capacities:
1) They persistently store values (called the consensus state).
2) They may receive publications. Participants make publications and the contract and all the other participants receive them.
3) When they receive publications, they systematically process them and may modify their consensus state, make publications, and may transfer network tokens and non-network tokens in response to the reception.
The creation of a contract is called deployment. Furthermore, a contract may provide views of its consensus state, which are labeled functions and values which may be hierarchically organized, such as NFT.owner or scoreOfPlayer. These views are visible in sub-trees of the computation graph. Views provide localstate. It could be a variable, or be dynamic, or the view can even be a function to calculate something in the smart contract.
A participant is a logical actor which takes part in a DApp. It is associated with an account on the consensus network.
For functions to create and interact with contracts sethe Reach [documentation] [(https://docs.reach.sh/frontend/#ref-frontends-js-ctc)
See more information on Network Utilities , Provider Info, Utilities and Ask.mjs
11. Sample dApp Solution
The following solution is for a game simulation called Morra. The object of the game is to throw fingers with an opponent and guess the total number of fingers shown.
The backend index.rsh contains the logic for the rules of the game to determine a winner.
'reach 0.1';
const [ isFingers, ZERO, ONE, TWO, THREE, FOUR, FIVE ] = makeEnum(6);
const [ isGuess, ZEROG, ONEG, TWOG, THREEG, FOURG, FIVEG, SIXG, SEVENG, EIGHTG, NINEG, TENG ] = makeEnum(11);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
// game logic
const winner = (fingersA, fingersB, guessA, guessB) => {
if ( guessA == guessB )
{
const myoutcome = DRAW; //tie
return myoutcome;
} else {
if ( ((fingersA + fingersB) == guessA ) ) {
const myoutcome = A_WINS;
return myoutcome;// player A wins
}
else {
if ( ((fingersA + fingersB) == guessB)) {
const myoutcome = B_WINS;
return myoutcome;// player B wins
}
else {
const myoutcome = DRAW; // tie
return myoutcome;
}
}
}
};
12. Verification
Reach is a Domain Specific Language (DSL) which makes it easy to write and verify smart contracts. Reach programs can be formally verified to prove facts that the dApp does not lock away funds or does not spend more than it has. One can even verify richer properties such as, all participants should have the same contribution in a pool. For more details see the Reach documentation.
The importance of verification for smart contract writers and businesses cannot be stressed enough. In the world where tools like Reach are available, having a formally verified contract with strong guarantees should always be considered. This way every participant in the system can trust the code and thus one can safely usher in a new generation of dApps.
Verification protects against some of the attacks in the blockchain world, such as guaranteeing that the balance in the contract at the end of the program is zero. Otherwise, tokens could be locked away by the contract and inaccessible. Verification also facilitates general blockchain properties, like token linearity. In other words, any network tokens transferred into the account must be removed by the DApp’s completion.
How to do verification?
At a High level write down _every single assumption the developer has _into the program. IE, if a value, x is assumed to be smaller than 20, then the programmer should always include:
assert(x < 20)
This gives the verification engine assumptions that it can attempt to falsify.
At a low level, the programmer should see the verification engine as a tool to prevent test regressions by encoding tests directly into the program in the form of assertions.
For example, suppose that during development and testing, a programmer observes an erroneous state where the variable y is assigned to the value 41, then the programmer should insert into the program:
assert(y != 41);
The programmer should insert this check before they fix the problem in the code. This will ensure that all future versions of the program will also be protected from this same problem.
For example, if a programmer expects a unary function over integers, f, to always return a number between 0 and 50, then they should write in their program:
assert(f(forall(UInt)) <= 50);
The following code is used for verification by using assert statements. Morra is a fingers game. So whenver the opponents throw the same number of fingers, it is a draw. If both players guess the same total, it is a draw. The player wins if they guess the winning number total.
// assert
// Alice throws a 0, AND Bob throws a 2,
// and Alice guesses 0 and Bob guesses 2
// then Bob wins as the total thrown is 2
assert(winner(ZERO,TWO,ZEROG,TWOG)== B_WINS);
assert(winner(TWO,ZERO,TWOG,ZEROG)== A_WINS);
assert(winner(ZERO,ONE,ZEROG,TWOG)== DRAW);
assert(winner(ONE,ONE,ONEG,ONEG)== DRAW);
// assets for all combinations
forall(UInt, fingersA =>
forall(UInt, fingersB =>
forall(UInt, guessA =>
forall(UInt, guessB =>
assert(isOutcome(winner(fingersA, fingersB, guessA, guessB)))))));
// asserts for a draw - each guesses the same
forall(UInt, (fingerA) =>
forall(UInt, (fingerB) =>
forall(UInt, (guess) =>
assert(winner(fingerA, fingerB, guess, guess) == DRAW))));
13. Backend
Define the Interfaces for a Player and Bob and Alice. The interface is simply a list of the methods and properties where the logic is provided in the frontend. Also, a deadline is defined for a timeout value.
// added a timeout function
const Player =
{ ...hasRandom,
getFingers: Fun([], UInt),
getGuess: Fun([UInt], UInt),
seeWinning: Fun([UInt], Null),
seeOutcome: Fun([UInt], Null) ,
informTimeout: Fun([], Null)
};
// added a wager function for Alice
const Alice =
{ ...Player,
wager: UInt,
...hasConsoleLogger
};
// added a acceptWager function for Bob
const Bob =
{ ...Player,
acceptWager: Fun([UInt], Null),
...hasConsoleLogger
};
const DEADLINE = 30;
Alice proposes a wager amount, Bob accepts the wager within the timeout period. The interact statements in backend call functions defined in the frontend.
export const main =
Reach.App(
{},
[Participant('Alice', Alice), Participant('Bob', Bob)],
(A, B) => {
const informTimeout = () => {
each([A, B], () => {
interact.informTimeout(); }); };
A.only(() => {
const wager = declassify(interact.wager); });
A.publish(wager)
.pay(wager);
commit();
B.only(() => {
interact.acceptWager(wager); });
B.pay(wager)
.timeout(relativeTime(DEADLINE), () => closeTo(A, informTimeout));
Each round is played until a winner is declared via a while loop. Alice wants to be able to publish her fingers and her guess, but also keep it secret, makeCommitment
is used for this. Bob does not know the values for Alice, but Alice does know the values using unknowable
.
var outcome = DRAW;
invariant(balance() == 2 * wager && isOutcome(outcome) );
// loop until we have a winner
while ( outcome == DRAW ) {
commit();
A.only(() => {
const _fingersA = interact.getFingers();
const _guessA = interact.getGuess(_fingersA);
// interact.log(_fingersA);
// interact.log(_guessA);
// We need Alice to be able to publish her fingers and guess,
// but also keep it secret. makeCommitment does this.
const [_commitA, _saltA] = makeCommitment(interact, _fingersA);
const commitA = declassify(_commitA);
const [_guessCommitA, _guessSaltA] = makeCommitment(interact, _guessA);
const guessCommitA = declassify(_guessCommitA);
});
A.publish(commitA)
.timeout(relativeTime(DEADLINE), () => closeTo(B, informTimeout));
commit();
A.publish(guessCommitA)
.timeout(relativeTime(DEADLINE), () => closeTo(B, informTimeout));
;
commit();
// Bob does not know the values for Alice, but Alice does know the values
unknowable(B, A(_fingersA, _saltA));
unknowable(B, A(_guessA, _guessSaltA));
Bob provides his fingers and his guess. Alice declassifies her secret information. The total on the fingers thrown between both players is calculated by adding the fingers thrown for Alice and Bob. Finally, a winner is determined.
B.only(() => {
const _fingersB = interact.getFingers();
// interact.log(_fingersB);
const _guessB = interact.getGuess(_fingersB);
// interact.log(_guessB);
const fingersB = declassify(_fingersB);
const guessB = declassify(_guessB);
});
B.publish(fingersB)
.timeout(relativeTime(DEADLINE), () => closeTo(A, informTimeout));
commit();
B.publish(guessB)
.timeout(relativeTime(DEADLINE), () => closeTo(A, informTimeout));
;
commit();
// Alice will declassify the secret information
A.only(() => {
const [saltA, fingersA] = declassify([_saltA, _fingersA]);
const [guessSaltA, guessA] = declassify([_guessSaltA, _guessA]);
});
A.publish(saltA, fingersA)
.timeout(relativeTime(DEADLINE), () => closeTo(B, informTimeout));
// check that the published values match the original values.
checkCommitment(commitA, saltA, fingersA);
commit();
A.publish(guessSaltA, guessA)
.timeout(relativeTime(DEADLINE), () => closeTo(B, informTimeout));
checkCommitment(guessCommitA, guessSaltA, guessA);
commit();
A.only(() => {
const WinningNumber = fingersA + fingersB;
interact.seeWinning(WinningNumber);
});
A.publish(WinningNumber)
.timeout(relativeTime(DEADLINE), () => closeTo(A, informTimeout));
outcome = winner(fingersA, fingersB, guessA, guessB);
continue;
}
assert(outcome == A_WINS || outcome == B_WINS);
// send winnings to winner
transfer(2 * wager).to(outcome == A_WINS ? A : B);
commit();
each([A, B], () => {
interact.seeOutcome(outcome); })
exit(); });
14. Frontend
The frontend instantiates an instance of the Reach stdlib and funds Bob and Alice’s accounts. A getBalance call is used to show the balance on each account before they play.
import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
const stdlib = await loadStdlib(process.env);
(async () => {
const startingBalance = stdlib.parseCurrency(10);
const accAlice = await stdlib.newTestAccount(startingBalance);
const accBob = await stdlib.newTestAccount(startingBalance);
const fmt = (x) => stdlib.formatCurrency(x, 4);
const getBalance = async (who) => fmt(await stdlib.balanceOf(who));
const beforeAlice = await getBalance(accAlice);
const beforeBob = await getBalance(accBob);
Alice deploys the contract and Bob attaches to it.
const ctcAlice = accAlice.contract(backend);
const ctcBob = accBob.contract(backend, ctcAlice.getInfo());
The Player has methods to getFingers, get Guess, seeWinning, seeOutcome, informTimout.
const FINGERS = [0, 1, 2, 3, 4, 5];
const GUESS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
const Player = (Who) => ({
...stdlib.hasRandom,
getFingers: async () => {
const fingers = Math.floor(Math.random() * 6);
console.log(`----------------------------`);
console.log(`${Who} shoots ${FINGERS[fingers]} fingers`);
if ( Math.random() <= 0.01 ) {
for ( let i = 0; i < 10; i++ ) {
console.log(` ${Who} takes their sweet time sending it back...`);
await stdlib.wait(1);
}
}
return fingers;
},
getGuess: async (fingers) => {
// guess should be greater than or equal to number of fingers thrown
const guess= Math.floor(Math.random() * 6) + FINGERS[fingers];
// occasional timeout
if ( Math.random() <= 0.01 ) {
for ( let i = 0; i < 10; i++ ) {
console.log(` ${Who} takes their sweet time sending it back...`);
await stdlib.wait(1);
}
}
console.log(`${Who} guessed total of ${guess}`);
return guess;
},
seeWinning: (winningNumber) => {
console.log(`Actual total fingers thrown: ${winningNumber}`);
},
seeOutcome: (outcome) => {
console.log(`${Who} saw outcome ${OUTCOME[outcome]}`);
},
informTimeout: () => {
console.log(`${Who} observed a timeout`);
},
});
Backends are deployed for Alice and Bob with wager for Alice and acceptWager for Bob.
await Promise.all([
backend.Alice(ctcAlice, {
...Player('Alice'),
wager: stdlib.parseCurrency(5),
...stdlib.hasConsoleLogger,
}),
backend.Bob(ctcBob, {
...Player('Bob'),
acceptWager: (amt) => {
console.log(`----------------------------`);
console.log(`Bob accepts the wager of ${fmt(amt)}.`);
},
...stdlib.hasConsoleLogger,
}),
]);
Balances are displayed at the end of the game.
const afterAlice = await getBalance(accAlice);
const afterBob = await getBalance(accBob);
console.log(`Alice went from ${beforeAlice} to ${afterAlice}.`);
console.log(`Bob went from ${beforeBob} to ${afterBob}.`);
})();
Run the application and the program logic will loop until a winner is determined.
$ ./reach run
Output should be similar to this:
----------------------------
Bob accepts the wager of 5.
----------------------------
Alice shoots 2 fingers
Alice guessed total of 6
----------------------------
Bob shoots 3 fingers
Bob guessed total of 7
Actual total fingers thrown: 5
----------------------------
Alice shoots 1 fingers
Alice guessed total of 6
----------------------------
Bob shoots 5 fingers
Bob guessed total of 9
Actual total fingers thrown: 6
Alice saw outcome Alice wins
Bob saw outcome Alice wins
Alice went from 10 to 14.9999.
Bob went from 10 to 4.9999.
15. Auditing
Without Reach, an auditor looks at the blockchain program and proves that the program is correct.
When auditing dApps written in Reach, the compiler verifies the program. An auditor would just need to verify if enough assertions are provided for the Reach compiler. On subsequent updates, the auditor would just need to check and make sure that the right assertions were included for the updates, rather than doing a complete new audit for the entire new version.
16. Mathematical proofs
Reach performs a runtime check as well as compile-time check using mathematical proofs.
Verification shows that no flaw exists, and no attack is possible. Reach provides automatic verifications to ensure and guarantees that your applications are free from entire categories of errors. This ensures the program does not lose funds, lock away funds or overspend funds.
17. Cryptographic Commitment Scheme
Reach provides tools to add custom verifications to a program, like ensuring that information is known only to one party. Or that the implementation of a sensitive algorithm is correct. In our sample app, Alice’s fingers and prediction guess needs to be protected until Bob reveals his hand. Otherwise Bob would have an unfair advantage. We need Alice to be able to publish her hand, but also keep it secret. This is a job for a cryptographic commitment scheme. Reach’s standard library comes with makeCommitment
function to make this easier for you. Using this our implementation is now secure and doesn’t contain any exploits for either Alice or Bob to guarantee a win. When publishing information that needs to be a secret, the makeCommitment and checkCommitment commands facilitate this by applying a salt value when committing.
18. Timeouts and nonparticipation
Non-participation means one party ceases to continue playing their role in an application. In traditional client-server programs, there is typically no need to consider the consequences of non-participation. IE. Let a website sit idle, if logged in, it may log out or info entered is cached and it will be there upon return. If after Alice has paid her wager, Bob never accepts and the application dosen’t continue. In this case, Alice’s network tokens would be locked inside of the contract and lost to her.
Similarly, if after Bob accepted and paid his wager, Alice stopped participating and never submitted her hand, then both their funds would be locked away forever. In each of these cases, both parties would be greatly hurt and their fear of that outcome would introduce an additional cost to transacting, which would lower the value they got from participating in the application.
In Reach, non-participation is handled through a “timeout” mechanism whereby each consensus transfer can be paired with a step that occurs for all participants if the originator of the consensus transfer fails to make the required publication before a particular time. We’ll integrate this mechanism into our version Morra and deliberately insert non-participation into our JavaScript testing program to watch the consequences play out.
The time argument can be expressed in absoluteTime(amt)
, relativeTime(time)
, relativeSecs(amt)
or absoluteSecs(secs)
. Also timeremaining()
can be used in conjunction with the makeDeadline(UInt)
function.
19. Logging on the Backend
The hasConsoleLogger method is used to provide logging. First define logging in the participant interface in index.rsh
:
// added a wager function for Alice
const Alice =
{ ...Player,
wager: UInt,
...hasConsoleLogger
};
// added a acceptWager function for Bob
const Bob =
{ ...Player,
acceptWager: Fun([UInt], Null),
...hasConsoleLogger
};
Then call the interact.log function to display a variable value … in the frontend.
In index.mjs
...stdlib.hasConsoleLogger
as shown below.
await Promise.all([
backend.Alice(ctcAlice, {
...Player('Alice'),
wager: stdlib.parseCurrency(5),
...stdlib.hasConsoleLogger,
}),
backend.Bob(ctcBob, {
...Player('Bob'),
acceptWager: (amt) => {
console.log(`Bob accepts the wager of ${fmt(amt)}.`);
},
...stdlib.hasConsoleLogger,
}),
]);
Then on the backend use the following code to display the value. All backend numbers are BigNumbers, so the front end may need formatting functions.
In index.rsh
interact.log(_variable);
When this is run, it will generate something similar to this:
BigNumber { _hex: '0x03', _isBigNumber: true }
20. Platform Specific Code
Most of the time, platform specific code is not needed, as the abstractions have already been made for common functions across blockchains. However, blockchain-specific code may be needed for functions that are specific to a blockchain, such as the number of rounds to wait for a transaction. Code written in .rsh backend has no notion of which chain it is running on so Blockchain specific code would need to go in the frontend. Also, env variables could be used to control your logic as well, or you can branch on stdlib.connector . The example below shows how to adjust howManyRounds, based on the deployment platform. If the stdlib.connector is ALGO, then the howManyRounds const will be set to 3, otherwise if it is another blockchain, it will be 10.
import * as backend from './build/index.main.mjs';
const stdlib = await loadStdlib(process.env);
(async () => {
const howManyRounds = stdlib.connector === 'ALGO' ? 3 : 10;
21. Flow commands
Common reach app flow commands include:
- Each - Used for loops for each participant.
- Only - Used for just 1 participant.
- Forks - Used for many participants that result in different outcomes.
- Parallel reduce - Used for while loops with a fork.
- Race - Used when First one to end wins.
- Publish or Pay - Publish to blockchain, or Pay from one account to another.
Pay and Publish without a Race are for when one participant wants to do one thing.
Race, and participant classes are for when many participants want to do one thing.
A Fork is for when many participants want to each do a different thing.
parallelReduce is needed if the situation is where a race or fork, and the options are available repeatedly after small diversions. For example, in an auction, bidders repeatedly provide new bids as they compete to be the highest bidder before a time limit is reached.
22. RPC
The Reach RPC Server provides access to compiled JavaScript backends via an HTTPS-accessible JSON-based RPC protocol. The server allows frontends to be written in any programming language. Reach provides client libraries for JavaScript, Python, and Go. It is easy to implement a client library yourself. An example frontend written using the Reach RPC Server is shown in the tutorial section on RPC-based frontends.
When using RPC the stdlib functions listed above would be replaced by corresponding RPC Calls in JavaScript. Go or Python.
For example, for calling stdlib methods:
Using JavaScript (local)
const stdlib = await loadStdlib(process.env);
const startingBalance = stdlib.parseCurrency(10);
Using JavaScript RPC
const { rpc, rpcCallbacks } = await mkRPC();
const startingBalance = await rpc(`/stdlib/parseCurrency`, 10);
Using Python RPC
rpc, rpc_callbacks = mk_rpc()
starting_balance = rpc('/stdlib/parseCurrency', 10)
Using Go RPC
rpc, rpcCallbacks := reachrpc.Mk()
fmtc := func(i jsono) string {
return rpc("/stdlib/formatCurrency", i, 4).(string)
}
RPC Options:
Instead of using environment variables on the client platform, the RPC Client Standard Options can be passed in as a dictionary object when instantiating the RPC object.
JavaScript RPC options
var opts = {
host: <host>,
port: <port>,
key: <API key>,
verify: '0',
timeout: 10
};
const { rpc, rpcCallbacks } = await mkRPC(opts);
Python RPC options
opts = {
"host": <host>,
"port": <port>,
"verify": '0',
"timeout": 10
"key": <API key>',
}
Go RPC options
rpc, rpc_callbacks = mk_rpc(opts)
opts := map[string]string{
"host": <host>,
"port": <port>,
"verify": <verify>,
"timeout": <timeout>,
"key": <API key>,
}
rpc, rpcCallbacks := reachrpc.Mk(opts)
The options are as follows:
- host — This value sets the hostname to contact for the Reach RPC Server instance. If it is not present, the client library must default to the value of the environment variable REACH_RPC_SERVER.
- port — This value sets the TCP port to contact for the Reach RPC Server instance. If it is not present, the client library must default to the value of the environment variable REACH_RPC_PORT.
- verify — This value determines whether to verify the TLS certificate of the Reach RPC Server instance. If it is not present, the client library must default to the value of the environment variable REACH_RPC_TLS_REJECT_UNVERIFIED. If that is not present, it must default to true. To disable verification, set this value to the string: “0”; any other value will be considered to mean “enable”.
- timeout — This value sets the number of seconds to wait for the Reach RPC Server instance to respond to its first request. If it is not present, the client library must default to the value of the environment variable REACH_RPC_TIMEOUT. If that is not present, it must default to 5 seconds.
- key — This value sets the API key for the Reach RPC Server instance. If it is not present, the client library must default to the value of the environment variable REACH_RPC_KEY.
Debugging RPC:
export REACH_DEBUG=0 // RPC debugging
export REACH_DEBUG=1 // RPC and line by line debugging
export REACH_DEBUG= or unset REACH_DEBUG //turns off debugging
Logging through interact.log:
When writing apps sometimes it is useful for developers to put in print statements to help debugging. This section shows how to log.
JavaScript
index.rsh
const Alice =
{ ...Player,
wager: UInt,
...hasConsoleLogger
};
// added a acceptWager function for Bob
const Bob =
{ ...Player,
acceptWager: Fun([UInt], Null),
...hasConsoleLogger
};
…
A.only(() => {
const _fingersA = interact.getFingers();
const _guessA = interact.getGuess(_fingersA);
index.mjs
await Promise.all([
backend.Alice(ctcAlice, {
...Player('Alice'),
wager: stdlib.parseCurrency(5),
...stdlib.hasConsoleLogger,
}),
backend.Bob(ctcBob, {
...Player('Bob'),
acceptWager: (amt) => {
console.log(`Bob accepts the wager of ${fmt(amt)}.`);
},
...stdlib.hasConsoleLogger,
}),
]);
RPC logging
In index.mjs
add log: console.log
JavaScript RPC logging
// log: console.log,
await Promise.all([
rpcCallbacks(`/backend/Alice`, ctcAlice, {
...Player('Alice'),
wager: await rpc(`/stdlib/parseCurrency`, 5),
log: console.log,
}),
rpcCallbacks(`/backend/Bob`, ctcBob, {
...Player('Bob'),
acceptWager: async (amt) => {
console.log(`Bob accepts the wager of ${await fmt(amt)}.`);
},
log: console.log,
}),
]);
and on the Backend in index.rsh
add interact.log(variable);
A.only(() => {
const _fingersA = interact.getFingers();
interact.log(_fingersA);
const _guessA = interact.getGuess(_fingersA);
interact.log(_guessA);
RPC Server
To start an instance of the Reach RPC Server, use this command:
$ ./reach rpc-server
.
The Reach RPC Server supports the following RPC methods. Here are a few useful ones. More can be seen at the Reach Documentation.
/health
returns true to indicate the server is running properly./stdlib/$METHOD
where$METHOD
is a function of the JavaScript standard library. \
All/stdlib
methods are synchronous value RPC methods that accept and produce the same arguments and return values as the corresponding function, encoded as JSON objects, except those that produce or consume account representations. \
Those methods instead accept and produce account RPC handles, which are random strings that represent the corresponding account representations. For example,/stdlib/newTestAccount
does not return an account like newTestAccount, but instead returns an account RPC handle./forget/acc
accepts an account RPC handle and deletes it from the Reach RPC Server’s memory./acc/$METHOD
where$METHOD
is a method of an account representation of the JavaScript standard library. \/forget/ctc
accepts a contract RPC handle and deletes it from the Reach RPC Server’s memory.-
/ctc/$METHOD
where$METHOD
is a method of a contract representation of the JavaScript standard library. -
/stop
quits the server.
More information can be found on the RPC Client and Reach RPC Tutorial
23. Python RPC Install and Run
The backend index.rsh file will require no changes for all of the RPC examples that follow. It just needs to be copied into the same folder as the frontend code.
Next, open a terminal in that directory and install the Reach Python RPC client:
Run and Install the Python RPC Client…
Run this command from the same folder as index.py
and index.rsh
.
$ ([ -d ./venv ] || python3 -m venv ./venv) && source ./venv/bin/activate
A Python venv is a “virtual environment” that sandboxes dependencies to avoid cluttering your system directories.
(venv) $ pip install --upgrade reach-rpc-client
(venv) $ REACH_CONNECTOR_MODE=ALGO ./reach rpc-run python3 -u ./index.py
Note: to exit the venv environment use:
$ deactivate
Sample Python frontend code index.py
import random
from threading import Thread
from reach_rpc import mk_rpc
def main():
# use opts to override defaults
# rpc, rpc_callbacks = mk_rpc(opts)
rpc, rpc_callbacks = mk_rpc()
starting_balance = rpc('/stdlib/parseCurrency', 10)
acc_alice = rpc('/stdlib/newTestAccount', starting_balance)
acc_bob = rpc('/stdlib/newTestAccount', starting_balance)
def fmt(x):
return rpc('/stdlib/formatCurrency', x, 4)
def get_balance(w):
return fmt(rpc('/stdlib/balanceOf', w))
before_alice = get_balance(acc_alice)
before_bob = get_balance(acc_bob)
ctc_alice = rpc('/acc/contract', acc_alice)
# ctc_bob = rpc('/acc/contract', acc_bob, rpc('/ctc/getInfo', ctc_alice))
FINGERS = [0, 1, 2, 3, 4, 5]
GUESS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
OUTCOME = ['Bob wins', 'Draw', 'Alice wins']
def player(who):
def getFingers():
fingers = random.randint(0, 5)
print('%s shoots %s fingers' % (who, FINGERS[fingers]))
return rpc('/stdlib/bigNumberToNumber', fingers)
def getGuess(fingers):
guess = (random.randint(
0, 5)) + FINGERS[rpc('/stdlib/bigNumberToNumber', fingers)]
print('%s guessed total of %s' % (who, GUESS[guess]))
return rpc('/stdlib/bigNumberToNumber', guess)
def seeWinning(winningNumber):
print('Actual total fingers thrown: %s' %
rpc('/stdlib/bigNumberToNumber', winningNumber))
print('----------------------------')
def informTimeout():
print('%s observed a timeout' % who)
def seeOutcome(n):
print('%s saw outcome %s'
% (who, OUTCOME[rpc('/stdlib/bigNumberToNumber', n)]))
return {'stdlib.hasRandom': True,
'getFingers': getFingers,
'getGuess': getGuess,
'seeWinning': seeWinning,
'informTimeout': informTimeout,
'seeOutcome': seeOutcome,
}
def play_alice():
rpc_callbacks(
'/backend/Alice',
ctc_alice,
dict(wager=rpc('/stdlib/parseCurrency', 5), deadline=10, log=print , **player('Alice')))
alice = Thread(target=play_alice)
alice.start()
def play_bob():
def acceptWager(amt):
print('Bob accepts the wager of %s ' %
rpc('/stdlib/bigNumberToNumber', fmt(amt)))
ctc_bob = rpc('/acc/contract', acc_bob, rpc('/ctc/getInfo', ctc_alice))
rpc_callbacks(
'/backend/Bob',
ctc_bob,
dict(acceptWager=acceptWager, **player('Bob')))
bob = Thread(target=play_bob)
bob.start()
alice.join()
bob.join()
after_alice = get_balance(acc_alice)
after_bob = get_balance(acc_bob)
print('Alice went from %s to %s' % (before_alice, after_alice))
print(' Bob went from %s to %s' % (before_bob, after_bob))
rpc('/forget/acc', acc_alice, acc_bob)
rpc('/forget/ctc', ctc_alice)
if __name__ == '__main__':
main()
24. JavaScript RPC Install and Run
Install and Run steps in folder where index.mjs
is:
npm install --save @reach-sh/rpc-client
Once installed, add the following import line to your JavaScript file which will connect to the Reach RPC Server:
import { mkRPC } from '@reach-sh/rpc-client';
You may need to set the environment variable for NODE_TLS_REJECT_UNAUTHORIZED if you get this error on this line:
RPC /stdlib/parseCurrency [10]
(node:50468) UnhandledPromiseRejectionWarning: Error: self signed certificate
$ export NODE_TLS_REJECT_UNAUTHORIZED='0'
Run with
$ ./reach rpc-run node index.mjs
Sample JavaScript frontend code **index.mjs:
import { mkRPC } from '@reach-sh/rpc-client';
const { rpc, rpcCallbacks } = await mkRPC();
const startingBalance = await rpc(`/stdlib/parseCurrency`, 10);
const accAlice = await rpc(`/stdlib/newTestAccount`, startingBalance);
const accBob = await rpc(`/stdlib/newTestAccount`, startingBalance);
const fmt = async x =>
await rpc(`/stdlib/formatCurrency`, x, 4);
const getBalance = async who =>
fmt(await rpc(`/stdlib/balanceOf`, who));
const beforeAlice = await getBalance(accAlice);
const beforeBob = await getBalance(accBob);
const ctcAlice = await rpc(`/acc/contract`, accAlice);
const FINGERS = [0, 1, 2, 3, 4, 5];
const GUESS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
const Player = (Who) => ({
"stdlib.hasRandom": true,
getFingers: async () => {
const fingers = Math.floor(Math.random() * 6);
console.log(`${Who} shoots ${FINGERS[fingers]} fingers`);
return fingers;
},
getGuess: async (fingersBN) => {
//const guess= Math.floor(Math.random() * 6) + FINGERS[fingers];
const fingers = await rpc(`/stdlib/bigNumbertoNumber`, fingersBN);
const guess= Math.floor(Math.random() * 6) + FINGERS[fingers];
console.log(`${Who} guessed total of ${guess}`);
return guess;
},
seeWinning: async (winningNumberBN) => {
const winningNumber = await rpc(`/stdlib/bigNumbertoNumber`, winningNumberBN);
console.log(`Actual total fingers thrown: ${winningNumber}`);
console.log(`----------------------------`);
},
seeOutcome: async (outcomeBN) => {
const outcome = await rpc(`/stdlib/bigNumbertoNumber`, outcomeBN);
console.log(`${Who} saw outcome ${OUTCOME[outcome]}`);
},
informTimeout: () => {
console.log(`${Who} observed a timeout`);
},
});
// log: console.log,
await Promise.all([
rpcCallbacks(`/backend/Alice`, ctcAlice, {
...Player('Alice'),
wager: await rpc(`/stdlib/parseCurrency`, 5),
log: console.log,
deadline: 10,
}),
rpc(`/ctc/getInfo`, ctcAlice).then(async (info) => {
const ctcBob = await rpc(`/acc/contract`, accBob, info);
rpcCallbacks(`/backend/Bob`, ctcBob, {
...Player('Bob'),
acceptWager: async (amt) => {
console.log(`Bob accepts the wager of ${await fmt(amt)}.`);
},
log: console.log,
});
return await rpc(`/forget/ctc`, ctcBob);
}),
]);
const afterAlice = await getBalance(accAlice);
const afterBob = await getBalance(accBob);
console.log(`Alice went from ${beforeAlice} to ${afterAlice}.`);
console.log(`Bob went from ${beforeBob} to ${afterBob}.`);
await Promise.all([
await rpc(`/forget/acc`, accAlice, accBob),
await rpc(`/forget/ctc`, ctcAlice),
await rpc(`/stop`),
]);
25. Go RPC Install and Run
Install and Run from the folder with index.go
:
$ go get github.com/reach-sh/reach-lang/rpc-client/go
$ ./reach rpc-run go run index.go
Sample Go frontend code can be fouund here: index.go.
26. TestNet and MainNet
In order to deploy to TestNet/MainNet, the provider selection needs to be done first. This function allows you to choose which particular consensus network API provider to connect to.
setProviderByName(string) => void
Supported provider names are: ‘MainNet’, ‘TestNet’, and ‘LocalHost’.
On Algorand, ‘MainNet’ will connect to MainNet, and ‘TestNet’ to TestNet. API services can be found here.
More information on using providers with Reach can be found here.
After the provider is selected, one needs to replace creating a new test account w funds, with account functions discussed above.
To deploy to Algorand TestNet see example environment variables below:
REACH_CONNECTOR_MODE=ALGO-live
ALGO_SERVER='https://testnet.algoexplorerapi.io'
ALGO_INDEXER_SERVER='https://testnet.algoexplorerapi.io/idx2'
REACH_RPC_SERVER=127.0.0.1
REACH_RPC_PORT=3000
REACH_RPC_TLS_REJECT_UNVERIFIED=0
REACH_RPC_KEY=opensesame
REACH_RPC_TIMEOUT=20
Complete code for this Morra Game simulation can be found here:
https://github.com/algorand-devrel/Reach-Morra-Game
Morra Book cover courtesy of Fustino Brothers.