diff --git a/main/.vuepress/config.js b/main/.vuepress/config.js index 96aa4a3f5..d2a7e7fc3 100644 --- a/main/.vuepress/config.js +++ b/main/.vuepress/config.js @@ -21,6 +21,34 @@ module.exports = { // Links must be absolute with trailing slash '/guide/' // Trailing slash implies it is looking for a .md file sidebar: { + '/getting-started/': [ + { + title: 'Getting Started', + path: '/getting-started/', + collapsable: false, + sideBarDepth: 3, + children: [ + { + title: 'Agoric\'s Cosmic SwingSet', + path: '/getting-started/', + collapsable: false, + sideBarDepth: 3 + }, + { + title: 'Pixel Demo', + path: '/getting-started/pixel-demo', + collapsable: false, + sideBarDepth: 3 + }, + { + title: 'Timer Service', + path: '/getting-started/timer-service', + collapsable: false, + sideBarDepth: 3 + } + ] + }, + ], '/ertp/': [ { title: 'ERTP Guide', diff --git a/main/.vuepress/themeConfig/nav.js b/main/.vuepress/themeConfig/nav.js index 865546268..b81ede6a6 100644 --- a/main/.vuepress/themeConfig/nav.js +++ b/main/.vuepress/themeConfig/nav.js @@ -6,7 +6,46 @@ module.exports = [ { - text: 'ERTP ', // spaces to add some distance to next link + text: 'Getting Started', + ariaLabel: 'Getting Started Menu', + link: '/getting-started/', + items: [ + { + text: 'Agoric\'s Cosmic SwingSet', + ariaLabel: 'Agoric\'s Cosmic SwingSet Menu', + link: '/getting-started/', + }, + { + text: 'Timer Service', + ariaLabel: 'Timer Service Link', + link: '/getting-started/timer-service' + }, + { + text: 'Pixel Demo', + ariaLabel: 'Pixel Demo Link', + link: '/getting-started/pixel-demo' + }, + { + text: 'Tutorials', + ariaLabel: 'Tutorials Menu', + link: '/smart-contracts-tutorials/guess37-one', + items: [ + { + text: 'Guess 37 - One Participant', + ariaLabel: 'Guess 37 - One Participant', + link: '/smart-contracts-tutorials/guess37-one' + }, + { + text: 'Guess37 - Multiple Participants', + ariaLabel: 'Guess37 Multiple Participants', + link: '/smart-contracts-tutorials/guess37-multiple' + } + ] + } + ] + }, + { + text: 'ERTP', // spaces to add some distance to next link ariaLabel: 'ERTP Menu', link: '/ertp/guide/', items: [ @@ -54,23 +93,6 @@ module.exports = [ } ], }, - { - text: 'Tutorials', - ariaLabel: 'Tutorials Menu', - link: '/smart-contracts-tutorials/guess37-one', - items: [ - { - text: 'Guess 37 - One Participant', - ariaLabel: 'Guess 37 - One Participant', - link: '/smart-contracts-tutorials/guess37-one' - }, - { - text: 'Guess37 - Multiple Participants', - ariaLabel: 'Guess37 Multiple Participants', - link: '/smart-contracts-tutorials/guess37-multiple' - } - ] - }, { text: 'Learn More', ariaLabel: 'Learn More Menu', diff --git a/main/getting-started/README.md b/main/getting-started/README.md new file mode 100644 index 000000000..afae9c6cb --- /dev/null +++ b/main/getting-started/README.md @@ -0,0 +1,161 @@ +# Agoric's Cosmic SwingSet + +Agoric's Cosmic SwingSet enables developers to test smart contracts build with [ERTP](https://github.com/Agoric/ERTP) in various blockchain setup environments + +## Overview +This document gives an overview of the process of setting up + +1. a local environment that will allow you to build and debug +2. an environment that emulates a remote setup for testing +3. deploying to the TestNet +4. (not available yet) deploying to MainNet + +In order to build a DeFi app in the SwingSet environment, your team will have to write code for three things: + +* The UI that displays in interface and talks to the handler via WebSockets +* The Handler that receives commands from the user via WebSockets and sends + transactions to the local solo SwingSet for relay to the Chain +* The Dapp code that runs in the Chain SwingSet and has access to objects on + other chains + + +To develop and deploy new code, you'll have to clone our [Cosmic SwingSet](https://github.com/Agoric/cosmic-swingset) repo from GitHib + +```sh +$ git clone https://github.com/Agoric/cosmic-swingset +``` + +There is more thorough documentation there. This is an overview. + + +## Different ways to run the Pixel Demo + +Running the demo requires a local solo node to serve as your access point. +Whichever environment you want to develop in, you'll start by building a solo +node from the source code. + +**Choose a scenario:** + +## Scenario 3: no testnet +### Develop off-chain demo locally + +In this scenario, you run: +- a **solo node** with the server-side Pixel Demo running and exposing an HTTP server in localhost +- a **web browser** connecting to the solo node and enabling user interaction with the Pixel Demo + +No blockchain is involved. + +| Local Solo | +|:--:| +| *A Local Solo SwingSet. Notice that there's no chain.* | + +Run: +```sh +make scenario3-setup +make scenario3-run-client +``` + +[`lib/ag-solo/vats/vat-demo.js`](https://github.com/Agoric/cosmic-swingset/tree/master/lib/ag-solo/vats/vat-demo.js) contains the code running a vat with +the Pixel Gallery Demo. + +Also, as part of `make scenario3-setup`, `bin/ag-solo init ` gets called and all the +content of the [`vats`](https://github.com/Agoric/cosmic-swingset/tree/master/lib/ag-solo/vats) directory gets copied to the `` + +The objects added to `home` are created in +[`lib/ag-solo/vats/vat-demo.js`](https://github.com/Agoric/cosmic-swingset/tree/master/lib/ag-solo/vats/vat-demo.js). + +The [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop] handler is in +[`lib/ag-solo/vats/vat-http.js`](https://github.com/Agoric/cosmic-swingset/tree/master/lib/ag-solo/vats/vat-http.js). + +The HTML frontend code is pure JS/DOM (no additional libraries yet), in +`lib/ag-solo/html/index.html` and `lib/ag-solo/html/main.js`. + + +## Scenario 2: a single local testnet node +### Develop on-chain demo + +In this scenario, you run: +- one or several **solo node(s)** each exposing an HTTP server in localhost (each to a different port) +- a **single local blockchain testnet node** with the server-side Pixel Demo running +- a **web browser** connecting to each solo node via a different port and enabling user interaction with the Pixel Demo + + +| Local Chain | +|:--:| +| *A Local Chain SwingSet. Notice that the chain is private.* | + +The solo nodes communicate with the testnet node + +Before using this scenario, it is recommanded that you test your code with Scenario 3. + +Prepare the chain and solo nodes: +```sh +make scenario2-setup BASE_PORT=8000 NUM_SOLOS=3 +``` + +fThis prepares for creating 3 solo nodes. Each node exposes a web server to a different port. The +ports start at `8000` (`BASE_PORT`). So the solo node ports here will be `8000`, `8001` and `8002` + +Start the chain: +```sh +make scenario2-run-chain +``` + +Wait about 5 seconds for the chain to produce its first block, then switch to another terminal: +```sh +make scenario2-run-client BASE_PORT=8000 +``` + +You can communicate with the node by opening http://localhost:8000/ + +You can start other solo nodes with `make scenario2-run-client BASE_PORT=8001` and `make +scenario2-run-client BASE_PORT=8002` and communicate with them respectively with on +http://localhost:8001/ and http://localhost:8002/ + + +## Scenario 1: your own local testnet +### Develop testnet provisioner + +In this scenario, you run: +- a **solo node** exposing an HTTP server in localhost +- a **several local blockchain testnet nodes** with the server-side Pixel Demo running on top. +- a **web browser** connecting to the solo node and enabling user interaction with the Pixel Demo + +This scenario is only useful for moving toward deploying the local source code as a new +testnet. Before using this scenario, you should test your on-chain code under Scenario 2. + +| Shared Chain | +|:--:| +| *A Shared Chain setup. You can connect to the chain with multiple solo SwingSets.* | + +```sh +make scenario1-setup +make scenario1-run-chain +``` + +Wait until the bootstrap produces a provisioning server URL and visit it. Then run in another terminal: + +```sh +make scenario1-run-client +``` + +See [Testnet Tutorial](https://github.com/Agoric/cosmic-swingset#testnet-tutorial) for more guidance. + +#### Scenario 0: a public testnet (kick the tires) + +In this scenario, you run: +- a **solo node** exposing an HTTP server in localhost +- a **web browser** connecting to the solo node and enabling user interaction with the Pixel Demo + +This scenario assumes your solo node can access a **blockchain running on the Internet** + +To run the solo node using the current directory's source code against a public testnet, use: +``` +$ make scenario0-setup +$ make scenario0-run-client +``` + +Alternatively, running the solo node from a Docker image and no local source code is described in the [top section](#overview). + +Now go to http://localhost:8000/ to interact with your new solo node. + diff --git a/main/getting-started/assets/LocalChain.pdf b/main/getting-started/assets/LocalChain.pdf new file mode 100644 index 000000000..72bbc1283 Binary files /dev/null and b/main/getting-started/assets/LocalChain.pdf differ diff --git a/main/getting-started/assets/LocalChain.png b/main/getting-started/assets/LocalChain.png new file mode 100644 index 000000000..84a2dcf7e Binary files /dev/null and b/main/getting-started/assets/LocalChain.png differ diff --git a/main/getting-started/assets/LocalSolo.pdf b/main/getting-started/assets/LocalSolo.pdf new file mode 100644 index 000000000..7ca2c5d79 Binary files /dev/null and b/main/getting-started/assets/LocalSolo.pdf differ diff --git a/main/getting-started/assets/LocalSolo.png b/main/getting-started/assets/LocalSolo.png new file mode 100644 index 000000000..0e4ca8717 Binary files /dev/null and b/main/getting-started/assets/LocalSolo.png differ diff --git a/main/getting-started/assets/SharedChain.pdf b/main/getting-started/assets/SharedChain.pdf new file mode 100644 index 000000000..944064824 Binary files /dev/null and b/main/getting-started/assets/SharedChain.pdf differ diff --git a/main/getting-started/assets/SharedChain.png b/main/getting-started/assets/SharedChain.png new file mode 100644 index 000000000..5b9ed899e Binary files /dev/null and b/main/getting-started/assets/SharedChain.png differ diff --git a/main/getting-started/assets/pixel-demo.png b/main/getting-started/assets/pixel-demo.png new file mode 100644 index 000000000..8732eba49 Binary files /dev/null and b/main/getting-started/assets/pixel-demo.png differ diff --git a/main/getting-started/assets/rplace.png b/main/getting-started/assets/rplace.png new file mode 100644 index 000000000..1a15a5e71 Binary files /dev/null and b/main/getting-started/assets/rplace.png differ diff --git a/main/getting-started/pixel-demo.md b/main/getting-started/pixel-demo.md new file mode 100644 index 000000000..19fe303eb --- /dev/null +++ b/main/getting-started/pixel-demo.md @@ -0,0 +1,162 @@ +# Pixel Demo + +This demo is roughly based on [Reddit's +r/Place](https://en.wikipedia.org/wiki/Place_(Reddit)), but has a +number of additional features that showcase the unique affordances of +the Agoric platform, including: higher-order contracts, easy creation +of new assets, and safe code reusability. + +| ![Reddit's r/place](./assets/rplace.png) | +|:--:| +| *Reddit's r/place as a social experiment in cooperation* | + + +## Installation + +| ![Pixel Gallery](./assets/pixel-demo.png) | +|:--:| +| *The testnet pixel demo. Slightly fewer pixels.* | + + +The pixel demo runs on [our private testnet](https://github.com/Agoric/cosmic-swingset#agorics-cosmic-swingset). For instructions on how to +run a local, off-chain version for yourself, please see [Scenario 3 +here](https://github.com/Agoric/cosmic-swingset#different-scenarios). + +## Getting Started + +In the pixel demo, the goal is to be able to amass enough pixels to +draw a design on a pixel canvas. You start out empty-handed, with no +money and no pixels to your name. However, you do have access to the *gallery*, the +administrator of the canvas. The gallery has a handful of +methods that allow you to obtain a few pixels for free, color them, +sell them, and buy more. + +To access the gallery, type `home.gallery` in the REPL. `home.gallery` +is a remote object (what we call a *presence*). It actually lives in +another environment (what we call a *vat*). Instead of `obj.foo()`, we +can write `E(obj).foo()` or the syntactic sugar, `obj~.foo()` and get a +promise for the result. We call this syntactic sugar ['wavy dot'](https://github.com/Agoric/proposal-wavy-dot). The syntax +means "deliver the message foo() to the actual object asynchronously, +in its own turn, wherever and whenever it is, even if it is local." +Using `E` or `~.`, you can talk asynchronously to local and remote objects +in exactly the same way. For example, the first thing you might want +to do is tap the gallery faucet to get a pixel for free: + +```js +px = home.gallery~.tapFaucet() +``` + +`tapFaucet` returns a pixel and saves it under `px`. The pixel that you receive is +actually in the form of an ERTP payment. [ERTP](/ertp/guide/) (Electronic Rights Transfer Protocol) +is our smart contract framework for handling transferable objects. +Payments have a few functions. Let's call `getBalance()` on our payment +to see which pixel we received. + +```js +px~.getBalance() +``` + +You might see something like: + +```js +{ + "label": { + "assay": [Presence 15], + "allegedName": "pixels" + }, + "units" : [{ "x":1, "y":4 }] +} +``` + +The `units` tells us which pixels we've received. `{ x:1, y:4 }` +means that we got a pixel that is in the fifth row (`y:4`) and 2 pixels +from the left (`x:1`). To color the pixel, we need to get the use +object from the payment. You can think of the use object as a regular +JavaScript object that just happens to be associated with an ERTP +payment. + +```js +use = px~.getUse() +``` + +Your use object will be stored under `use`. Now we +can use it to color. + +```js +use~.changeColorAll('#FF69B4') +``` + +The following commands show a pixel being obtained from the faucet, +getting the 'use' object, coloring the pixel, and selling a pixel to the gallery through an +escrow smart contract. + +``` +px = home.gallery~.tapFaucet(); +px~.getBalance(); +use = px~.getUse(); +use~.changeColorAll('yellow'); +px2 = home.gallery~.tapFaucet(); +asset2 = px2~.getBalance(); +asset2.then(a => home.gallery~.pricePixelUnitOps(a)); +hostInvite = home.gallery~.sellToGallery(asset2); +seat = hostInvite~.host~.redeem(hostInvite~.inviteP); +offered = seat~.offer(px2); +assays = home.gallery~.getAssays(); +pxPurse = assays~.pixelAssay~.makeEmptyPurse(); +dustPurse = assays~.dustAssay~.makeEmptyPurse(); +collected = offered.then(_ => home.gallery~.collectFromGallery(seat, dustPurse, pxPurse, 'my escrow')); +collected.then(_ => dustPurse~.getBalance()); +``` + +Woohoo! We're now a few dust richer than when we started. + +Learn more about ERTP and our pixel demo [here](https://github.com/Agoric/ERTP). + +To see the contracts you've uploaded [as per the README](https://github.com/Agoric/cosmic-swingset/blob/master/lib/ag-solo/contracts/README-contract.md), try: + +```js +home.uploads~.list() +home.uploads~.get('encouragementBot')~.spawn()~.encourageMe('Person') +``` + +## Initial Endowments + +When a client is started up, it has a few items in a record named home. + +* gallery: the Pixel Gallery, described above +* purse: a purse that can hold pixel Dust +* moolah: a purse that starts out with 1000 `moolah` +* sharingService: a service that makes it possible to pass capabilities between vats +* canvasStatePublisher: a service with the message subscribe(callback) +* [uploads](https://github.com/Agoric/cosmic-swingset/blob/master/lib/ag-solo/contracts/README-contract.md): a private directory + of contracts you've uploaded +* registrar: a public directory for published objects +* localTimerService and chainTimerService: tools for scheduling +* [zoe](/zoe/guide/): support for contracts with Offer-Safety Enforcement +* [contractHost](/ertp/guide/contract-hosts): secure smart contracts + +### sharingService + +`home.sharingService` is a service that lets you connect to +other vats that are connected to the same remote chain vat. sharingService +has three methods: `createSharedMap(name)`, `grabSharedMap(name)`, and +`validate(sharedMap)`. These allow you to create a SharedMap which you can +use to pass items to and from another vat. The sharingService's +methods are designed to allow you to share a newly created sharedMap +with one other vat, after which the name can't be reused. + +The way to use it is to call `createSharedMap() `with a name that you share +with someone else. They then call `grabSharedMap`() and pass the name you +gave. If they get a valid SharedMap, then you have a private +channel. If they don't get it, then someone else must have tried to +grab the name first, and you can discard that one and try again. + +Once you each have an end, either of you can call `addEntry(key, value)` +to store an object, which the other party can retrieve with +`lookup(key)`. + +### canvasStatePublisher + +`home.canvasStatePublisher` has a `subscribe()` method, which takes a callback +function. When the state of the pixel gallery changes, the callback's +`notify()` method is called with the new state. diff --git a/main/getting-started/timer-service.md b/main/getting-started/timer-service.md new file mode 100644 index 000000000..f6d44e174 --- /dev/null +++ b/main/getting-started/timer-service.md @@ -0,0 +1,104 @@ +# Timer Service + +## Description + +There will be one or two `timerServices` in home. One is from the chain (if +present), the other from the local vat. It would probably be sensible to use a +chain-based timer for contracts, but more efficient to use the local timer +for operations that don't need consensus or consistency. Each `timerService` +gives the ability to get the current time, schedule a single `wake()` call, +create a repeater that will allow scheduling of events at regular intervals, +or remove scheduled calls. + +## API +The timerService's API is: + +```js +interface TimerService { + // Retrieve the time of the start of the current block. + getCurrentTimestamp() -> (integer); + + // Return value is the time at which the call is scheduled to take place. + setWakeup(baseTime :integer, handler :Handler) -> (integer); + + // Remove the handler from all its scheduled wakeups, whether + // produced by timer.setWakeup(h) or repeater.schedule(h). + removeWakeup(handler :Handler) -> (List(integer)); + + // Create and return a repeater that will schedule wake() calls repeatedly at + // times that are a multiple of interval following baseTime. Interval is the + // delay between successive times at which wake will be called. When + // schedule(h) is called, h.wake() will be scheduled to be called after the + // next multiple of interval from the base. Since block times are coarse- + // grained, the actual call may occur later, but this won't change when the + // next event will be called. + createRepeater(delaySecs :integer, interval :integer) -> (Repeater); +} + +interface Repeater { + // Return value is the time scheduled for the first call on handler. + // The handler will continue to be scheduled for a wake() call every + // interval until the repeater is disabled. + schedule(handler :Handler) -> (integer); + + // Disable this repeater, so schedule() can't be called, and handlers + // already scheduled with this repeater won't be rescheduled again after + // wake() is next called on them. + disable(); +} + +interface Handler { + // The time passed to wake() is the time that the call was scheduled to + // occur. + wake(time); +} +``` + +## Transcript +Here's a transcript of a session showing the use of the repeater. + +``` +command[0] home +history[0] {"LOADING":[Promise],"gallery":[Presence o-50],"sharingService":[Presence o-51], +"purse":[Presence o-52],"canvasStatePublisher":[Presence o-53],"contractHost":[Presence o-54], +"chainTimerService":[Presence o-55],"sharing":[Presence o-56],"registry":[Presence o-57],"zoe": +[Presence o-58],"localTimerService":[Presence o-59],"uploads":[Presence o-60]} +command[1] home.localTimerService~.getCurrentTimestamp() +history[1] 1571782780000 +command[2] home.chainTimerService~.getCurrentTimestamp() +history[2] 1571782793 +command[3] makeHandler = () => { let calls = 0; const args = []; return { getCalls() { +return calls; }, getArgs() { return args; }, wake(arg) { args.push(arg); calls += 1; }, }; } +history[3] [Function makeHandler] +command[4] h1 = makeHandler() +history[4] {"getCalls":[Function getCalls],"getArgs":[Function getArgs],"wake":[Function wake]} +command[5] h2 = makeHandler() +history[5] {"getCalls":[Function getCalls],"getArgs":[Function getArgs],"wake":[Function wake]} +command[6] tl = home.localTimerService +history[6] [Presence o-59] +command[7] tc = home.chainTimerService +history[7] [Presence o-55] +command[8] rl = tl~.createRepeater(7, 1500) +history[8] [Presence o-64] +command[9] rc = tc~.createRepeater(7, 1) +history[9] [Presence o-65] +command[10] rl~.schedule(h1) +history[10] 1571783040007 +command[11] rc~.schedule(h2) +history[11] 1571783051 +command[12] h1.getCalls() +history[12] 3 +command[13] h2.getCalls() +history[13] 1 +... +command[22] h1.getCalls() +history[22] 50 +command[23] h1.getCalls() +history[23] 53 +command[24] h1.getCalls() +history[24] 54 +command[25] tl~.getCurrentTimestamp() +history[25] 1571783375000 +command[26] tc~.getCurrentTimestamp() +history[26] 1571783384 +```