-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cosmic-swingset): use a fake chain for scenario3 (#322)
* feat(cosmic-swingset): use a fake chain for scenario3 This introduces block latency so that the scenario3 chain behaves much more like a real blockchain, but within the same process (for debuggability) and without the actual Cosmos SDK usage. * fix(Makefile): refine rules Make a `deprecated-scenario3-setup` and `deprecated-scenario3-run-client` to illustrate the old scenario3. Create `scenario3-run` to make tab-completion better. * doc(README): remove Golang prerequisite: no longer needed
- Loading branch information
1 parent
8dc31a0
commit f833610
Showing
7 changed files
with
213 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,19 @@ repository: instead you should [follow our instructions for getting started](htt | |
|
||
But if you are improving the platform itself, this is the repository to use. | ||
|
||
## Pre-requisites | ||
## Prerequisites | ||
|
||
* Git | ||
* Node.js (version 11 or higher) | ||
* Golang (1.13 or higher) (TODO: only require this for cosmic-swingset) | ||
* Yarn (`npm install -g yarn`) | ||
|
||
You don't need Golang if you just want to test contracts and run the | ||
"scenario3" simulator. Golang (1.13 or higher) is needed only if you | ||
want to build/debug Cosmos SDK support. (The `1.12` release will work, but | ||
it will modify `packages/cosmic-swingset/go.mod` upon each build (by adding | ||
a dependency upon `appengine`). The `1.13` release will leave the `go.mod` | ||
file correctly unmodified. | ||
|
||
## Build | ||
|
||
From a new checkout of this repository, run: | ||
|
@@ -36,8 +42,7 @@ section tells us when symlinks could not be used (generally because e.g. | |
`ERTP` wants `[email protected]`, but `packages/marshal/package.json` says it's | ||
actually `0.2.0`). We want to get rid of all mismatched dependencies. | ||
|
||
The `yarn build` step generates kernel bundles, and compiles the Go code in | ||
cosmic-swingset. | ||
The `yarn build` step generates kernel bundles. | ||
|
||
## Test | ||
|
||
|
@@ -110,18 +115,3 @@ To create a new (empty) package (e.g. spinning Zoe out from ERTP): | |
* commit everything to a new branch, push, check the GitHub `Actions` tab to | ||
make sure CI tested everything properly | ||
* merge with a PR | ||
|
||
## Running without Go | ||
|
||
A golang installation is necessary for building `cosmic-swingset`. At | ||
present, this build happens during `yarn install`, which is also necessary to | ||
set up the monorepo's cross-package symlinks. | ||
|
||
Until we change this, to build everything else without a Go install, just | ||
edit the top-level `package.json` and remove `packages/cosmic-swingset` from | ||
the `workspaces` clause. | ||
|
||
We recommend Go `1.13`. The `1.12` release will work, but it will modify | ||
`packages/cosmic-swingset/go.mod` upon each build (by adding a dependency | ||
upon `appengine`). The `1.13` release will leave the `go.mod` file correctly | ||
unmodified. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Introduce "fake chain" to scenario3 configuration. | ||
|
||
Notably, have a simulated 5-second block time. To | ||
reset this to the old behaviour, use: | ||
|
||
make scenario3-setup FAKE_CHAIN_DELAY=0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* eslint-disable no-await-in-loop */ | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import stringify from '@agoric/swingset-vat/src/kernel/json-stable-stringify'; | ||
import { launch } from '../launch-chain'; | ||
|
||
const PRETEND_BLOCK_DELAY = 5; | ||
|
||
async function readMap(file) { | ||
let content; | ||
const map = new Map(); | ||
try { | ||
content = await fs.promises.readFile(file); | ||
} catch (e) { | ||
return map; | ||
} | ||
const obj = JSON.parse(content); | ||
Object.entries(obj).forEach(([k, v]) => map.set(k, v)); | ||
return map; | ||
} | ||
|
||
async function writeMap(file, map) { | ||
const obj = {}; | ||
[...map.entries()].forEach(([k, v]) => (obj[k] = v)); | ||
const json = stringify(obj); | ||
await fs.promises.writeFile(file, json); | ||
} | ||
|
||
export async function connectToFakeChain(basedir, GCI, role, delay, inbound) { | ||
const stateFile = path.join(basedir, `fake-chain-${GCI}-state.json`); | ||
const mailboxFile = path.join(basedir, `fake-chain-${GCI}-mailbox.json`); | ||
const bootAddress = `${GCI}-client`; | ||
|
||
const mailboxStorage = await readMap(mailboxFile); | ||
|
||
const vatsdir = path.join(basedir, 'vats'); | ||
const argv = [`--role=${role}`, bootAddress]; | ||
const s = await launch(mailboxStorage, stateFile, vatsdir, argv); | ||
const { deliverInbound, deliverStartBlock } = s; | ||
|
||
let pretendLast = Date.now(); | ||
let blockHeight = 0; | ||
let intoChain = []; | ||
let thisBlock = []; | ||
async function simulateBlock() { | ||
const actualStart = Date.now(); | ||
// Gather up the new messages into the latest block. | ||
thisBlock.push(...intoChain); | ||
intoChain = []; | ||
|
||
try { | ||
const commitStamp = pretendLast + PRETEND_BLOCK_DELAY * 1000; | ||
const blockTime = Math.floor(commitStamp / 1000); | ||
await deliverStartBlock(blockHeight, blockTime); | ||
for (let i = 0; i < thisBlock.length; i += 1) { | ||
const [newMessages, acknum] = thisBlock[i]; | ||
await deliverInbound( | ||
bootAddress, | ||
newMessages, | ||
acknum, | ||
blockHeight, | ||
blockTime, | ||
); | ||
} | ||
|
||
// Done processing, "commit the block". | ||
await writeMap(mailboxFile, mailboxStorage); | ||
thisBlock = []; | ||
pretendLast = commitStamp + Date.now() - actualStart; | ||
blockHeight += 1; | ||
} catch (e) { | ||
console.log(`error fake processing`, e); | ||
} | ||
|
||
if (delay) { | ||
setTimeout(simulateBlock, delay * 1000); | ||
} | ||
|
||
// TODO: maybe add latency to the inbound messages. | ||
const mailbox = JSON.parse(mailboxStorage.get(`mailbox.${bootAddress}`)); | ||
const { outbox, ack } = mailbox || { | ||
outbox: [], | ||
ack: 0, | ||
}; | ||
inbound(GCI, outbox, ack); | ||
} | ||
|
||
async function deliver(newMessages, acknum) { | ||
intoChain.push([newMessages, acknum]); | ||
if (!delay) { | ||
await simulateBlock(); | ||
} | ||
} | ||
if (delay) { | ||
setTimeout(simulateBlock, delay * 1000); | ||
} | ||
return deliver; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
|
||
export default function setFakeChain(basedir, GCI, role, fakeDelay) { | ||
const fn = path.join(basedir, 'connections.json'); | ||
const connsByType = {}; | ||
const add = c => { | ||
const { type } = c; | ||
const conns = connsByType[type]; | ||
if (!conns) { | ||
connsByType[type] = [c]; | ||
return; | ||
} | ||
|
||
switch (type) { | ||
case 'fake-chain': { | ||
// Replace duplicate GCIs. | ||
const { GCI: newGCI } = c; | ||
const index = conns.findIndex(({ GCI: oldGCI }) => oldGCI === newGCI); | ||
if (index < 0) { | ||
conns.push(c); | ||
} else { | ||
conns[index] = c; | ||
} | ||
break; | ||
} | ||
default: | ||
conns.push(c); | ||
} | ||
}; | ||
|
||
JSON.parse(fs.readFileSync(fn)).forEach(add); | ||
const newconn = { | ||
type: 'fake-chain', | ||
GCI, | ||
fakeDelay, | ||
role, | ||
}; | ||
add(newconn); | ||
const connections = []; | ||
Object.entries(connsByType).forEach(([_type, conns]) => | ||
connections.push(...conns), | ||
); | ||
fs.writeFileSync(fn, `${JSON.stringify(connections, undefined, 2)}\n`); | ||
|
||
const gciFileContents = `\ | ||
export const GCI = ${JSON.stringify(GCI)}; | ||
`; | ||
const bfn = path.join(basedir, 'vats', 'gci.js'); | ||
fs.writeFileSync(bfn, gciFileContents); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters