|
1 | 1 | import path from 'path';
|
2 | 2 | import http from 'http';
|
3 | 3 | import fetch from 'node-fetch';
|
4 |
| -import { spawn } from 'child_process'; |
| 4 | +import { execFileSync } from 'child_process'; |
5 | 5 | import djson from 'deterministic-json';
|
6 | 6 | import { createHash } from 'crypto';
|
7 | 7 | import connect from 'lotion-connect';
|
8 | 8 |
|
9 | 9 | export async function connectToChain(basedir, GCI, rpcAddresses, myAddr, inbound) {
|
10 |
| - //const connection = await connect(GCI, { nodes: [`ws://localhost:${rpcPort}`]}); |
11 |
| - //makeChainFollower(connection, MYNAME, 'chain', inbound); |
12 |
| - |
13 |
| - // We spawn a long-running copy of 'ag-cosmos-helper', the |
14 |
| - // swingset-flavored cosmos-sdk CLI tool. We tell that process to listen on |
15 |
| - // its REST port, and then we make HTTP calls to that port. This is the |
16 |
| - // middle ground between our original hackish approach (shelling out to a |
17 |
| - // brand new copy of ag-cosmos-helper each time we wanted to send a |
18 |
| - // message) and the too-hard-to-do-right-this-instant less-hackish approach |
19 |
| - // (building an FFI binding to the same golang code that ag-cosmos-helper |
20 |
| - // uses, and calling it directly). |
| 10 | + // Each time we read our mailbox from the chain's state, and each time we |
| 11 | + // send an outbound message to the chain, we shell out to a one-shot copy |
| 12 | + // of 'ag-cosmos-helper', the swingset-flavored cosmos-sdk CLI tool. |
| 13 | + |
| 14 | + // We originally thought we could use the tool's "rest-server" mode, leave |
| 15 | + // it running for the duration of our process, but it turns out that the |
| 16 | + // rest-server cannot sign transactions (that ability was removed as a |
| 17 | + // security concern in https://github.com/cosmos/cosmos-sdk/issues/3641) |
| 18 | + |
| 19 | + // A better approach I'm hopeful we can achieve is an FFI binding to the |
| 20 | + // same golang code that powers ag-cosmos-helper, so we can call the |
| 21 | + // query/tx functions directly without the overhead of spawning a |
| 22 | + // subprocess and encoding everything as strings over stdio. |
21 | 23 |
|
22 | 24 | // the 'ag-cosmos-helper' tool in our repo is built by 'make install' and
|
23 | 25 | // put into the user's $GOPATH/bin . That's a bit intrusive, ideally it
|
24 |
| - // would live in the build tree along with bin/css-solo . But for now we |
| 26 | + // would live in the build tree along with bin/ag-solo . But for now we |
25 | 27 | // assume that 'ag-cosmos-helper' is on $PATH somewhere.
|
26 | 28 |
|
27 |
| - if (rpcAddresses.length > 1) { |
28 |
| - // We want the rest server to rotate/failover between multiple supplied |
29 |
| - // ports, so we can give client nodes a list of validators to follow, and |
30 |
| - // any given validator can restart without clients failing, so 1: we need |
31 |
| - // to find out if the rest-server automatically reconnects, and 2: we |
32 |
| - // need to spin up multiple rest-servers (one per rpcAddress) if they |
33 |
| - // can't do the multiple-server thing themselves. |
34 |
| - console.log('NOTE: multiple rpc ports provided, but I can only use one right now'); |
35 |
| - } |
36 |
| - |
37 |
| - // the rest-server defaults to '--laddr tcp://localhost:1317' , but it |
38 |
| - // might behoove us to allocate a new port so we don't collide with |
39 |
| - // anything else on the box (we generally avoid relying on default ports) |
40 |
| - |
41 | 29 | // TODO: --chain-id matches something in the genesis block, should we
|
42 | 30 | // include it in the arguments to `css-solo set-gci-ingress`? It's included
|
43 | 31 | // in the GCI, but if we also need it to establish an RPC connection, then
|
44 | 32 | // it needs to be learned somehow, rather than being hardcoded.
|
45 | 33 |
|
46 |
| - const serverDir = path.join(basedir, 'ag-cosmos-helper-statedir'); |
47 |
| - |
48 |
| - const args = [ 'rest-server', |
49 |
| - '--node', rpcAddresses[0], |
50 |
| - '--chain-id', 'agchain', |
51 |
| - '--output', 'json', |
52 |
| - '--trust-node', 'false', |
53 |
| - '--home', serverDir, |
54 |
| - ]; |
55 |
| - |
56 |
| - const p = spawn('ag-cosmos-helper', args); |
57 |
| - p.stdout.on('data', _data => 'ignored'); |
58 |
| - //p.stdout.on('data', data => console.log(`ag-cosmos-helper rest-server stdout: ${data}`)); |
59 |
| - p.stderr.on('data', data => console.log(`ag-cosmos-helper rest-server stderr: ${data}`)); |
60 |
| - p.on('close', code => console.log(`ag-cosmos-helper rest-server exited with ${code}`)); |
61 |
| - console.log(`started ag-cosmos-helper rest-server`); |
62 |
| - |
63 |
| - const restURL = `http://localhost:1317`; |
64 |
| - const mailboxURL = `${restURL}/swingset/mailbox/${myAddr}`; |
| 34 | + const helperDir = path.join(basedir, 'ag-cosmos-helper-statedir'); |
65 | 35 |
|
66 | 36 | function getMailbox() {
|
67 |
| - return fetch(mailboxURL) |
68 |
| - .then(res => res.json()) // .json() returns a Promise |
69 |
| - .then(r => { |
70 |
| - if (!r.value) { |
71 |
| - console.log(`no value in getMailbox query, got`, r); |
72 |
| - // error is probably r.codespace/r.code, with human-readable |
73 |
| - // r.message |
74 |
| - } |
75 |
| - return JSON.parse(r.value); |
76 |
| - }); |
| 37 | + const args = ['query', 'swingset', 'mailbox', myAddr, |
| 38 | + '--chain-id', 'agchain', '--output', 'json', |
| 39 | + '--home', helperDir, |
| 40 | + ]; |
| 41 | + const stdout = execFileSync('ag-cosmos-helper', args); |
| 42 | + console.log(` helper said: ${stdout}`); |
| 43 | + const mailbox = JSON.parse(JSON.parse(stdout).value); |
| 44 | + // mailbox is [[[num,msg], ..], ack] |
| 45 | + return mailbox; |
77 | 46 | }
|
78 | 47 |
|
79 |
| - getMailbox().then(m => console.log(`mailbox is`, m)); |
80 |
| - |
81 | 48 | const rpcURL = `http://${rpcAddresses[0]}`;
|
82 | 49 | const rpcWSURL = `ws://${rpcAddresses[0]}`;
|
83 | 50 | function getGenesis() {
|
@@ -119,68 +86,41 @@ export async function connectToChain(basedir, GCI, rpcAddresses, myAddr, inbound
|
119 | 86 |
|
120 | 87 | c.lightClient.on('update', _a => {
|
121 | 88 | console.log(`new block on ${GCI}, fetching mailbox`);
|
122 |
| - getMailbox().then(m => { |
123 |
| - const [outbox, ack] = m; |
124 |
| - inbound(GCI, outbox, ack); |
125 |
| - }); |
| 89 | + const [outbox, ack] = getMailbox(); |
| 90 | + inbound(GCI, outbox, ack); |
126 | 91 | });
|
127 | 92 |
|
128 | 93 | async function deliver(newMessages, acknum) {
|
129 |
| - // each message must include { account_number, sequence } for |
130 |
| - // ordering/replay protection. These must be learned from the chain (from |
131 |
| - // the 'auth' module, which manages accounts, which are indexed by |
132 |
| - // address even though the messages contain an account_number). The |
133 |
| - // 'account_number' is (I'd hope) static, so we could fetch it once at |
134 |
| - // startup. The 'sequence' number increase with each txn, and we're the |
135 |
| - // only party with this private key, so we could also fetch it once at |
136 |
| - // startup (or remember it in our local state) and just increment it with |
137 |
| - // each message. For now we stay lazy and fetch it every time. |
138 | 94 | console.log(`delivering to chain`, GCI, newMessages, acknum);
|
139 | 95 |
|
140 |
| - const accountURL = `${restURL}/auth/accounts/${myAddr}`; |
141 |
| - console.log(`accountURL is ${accountURL}`); |
142 |
| - const ans = await fetch(accountURL) |
143 |
| - .then(r => { |
144 |
| - console.log(`r is`, r); |
145 |
| - if (r.status === 204) { |
146 |
| - throw new Error(`account query returned empty body: the chain doesn't know us`); |
147 |
| - } |
148 |
| - return r.json(); |
149 |
| - }) |
150 |
| - .then(a => { |
151 |
| - return { account_number: a.value.account_number, |
152 |
| - sequence: a.value.sequence, |
153 |
| - }; |
154 |
| - }, |
155 |
| - err => { |
156 |
| - console.log(`error in .json`, err); |
157 |
| - throw err; |
158 |
| - }); |
159 |
| - console.log(`account_ans:`, ans); |
160 |
| - |
161 |
| - const url = `${restURL}/swingset/mailbox`; |
162 |
| - const body = { |
163 |
| - base_req: { |
164 |
| - chain_id: 'agchain', |
165 |
| - from: myAddr, |
166 |
| - sequence: ans.sequence, |
167 |
| - account_number: ans.account_number, |
168 |
| - password: 'mmmmmmmm', |
169 |
| - }, |
170 |
| - peer: myAddr, // TODO: combine peer and submitter in the message format? |
171 |
| - submitter: myAddr, |
172 |
| - // TODO: remove this JSON.stringify change 'deliverMailboxReq' to have |
173 |
| - // more structure than a single string |
174 |
| - deliver: JSON.stringify([newMessages, acknum]), |
175 |
| - }; |
176 |
| - return fetch(url, { method: 'POST', |
177 |
| - body: JSON.stringify(body), |
178 |
| - //headers: { 'Content-Type': 'application/json' } // do we need it? |
179 |
| - }) |
180 |
| - .then(res => res.json()) |
181 |
| - .then(json => { |
182 |
| - console.log(`POST done`, JSON.stringify(json)); |
183 |
| - }); |
| 96 | + // TODO: combine peer and submitter in the message format (i.e. remove |
| 97 | + // the extra 'myAddr' after 'tx swingset deliver'). All messages from |
| 98 | + // solo vats are "from" the signer, and messages relayed from another |
| 99 | + // chain will have other data to demonstrate which chain it comes from |
| 100 | + |
| 101 | + // TODO: remove this JSON.stringify([newMessages, acknum]): change |
| 102 | + // 'deliverMailboxReq' to have more structure than a single string, and |
| 103 | + // have the CLI handle variable args better |
| 104 | + |
| 105 | + const args = ['tx', 'swingset', 'deliver', myAddr, |
| 106 | + JSON.stringify([newMessages, acknum]), |
| 107 | + '--from', 'ag-solo', '--yes', |
| 108 | + '--chain-id', 'agchain', |
| 109 | + '--home', helperDir, |
| 110 | + ]; |
| 111 | + const password = 'mmmmmmmm\n'; |
| 112 | + try { |
| 113 | + console.log(`running helper`, args); |
| 114 | + const stdout = execFileSync('ag-cosmos-helper', args, |
| 115 | + { input: Buffer.from(`${password}`), |
| 116 | + }); |
| 117 | + console.log(` helper said: ${stdout}`); |
| 118 | + } catch (e) { |
| 119 | + console.log(`helper failed`); |
| 120 | + console.log(`rc: ${e.status}`); |
| 121 | + console.log(`stdout:`, e.stdout.toString()); |
| 122 | + console.log(`stderr:`, e.stderr.toString()); |
| 123 | + } |
184 | 124 |
|
185 | 125 | }
|
186 | 126 |
|
|
0 commit comments