Skip to content

Commit 46fb5e8

Browse files
committed
ag-solo: use helper CLI instead of rest-server, it cannot sign txns
closes Agoric#3
1 parent e2f9468 commit 46fb5e8

File tree

1 file changed

+55
-115
lines changed

1 file changed

+55
-115
lines changed

lib/ag-solo/chain-cosmos-sdk.js

+55-115
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,50 @@
11
import path from 'path';
22
import http from 'http';
33
import fetch from 'node-fetch';
4-
import { spawn } from 'child_process';
4+
import { execFileSync } from 'child_process';
55
import djson from 'deterministic-json';
66
import { createHash } from 'crypto';
77
import connect from 'lotion-connect';
88

99
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.
2123

2224
// the 'ag-cosmos-helper' tool in our repo is built by 'make install' and
2325
// 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
2527
// assume that 'ag-cosmos-helper' is on $PATH somewhere.
2628

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-
4129
// TODO: --chain-id matches something in the genesis block, should we
4230
// include it in the arguments to `css-solo set-gci-ingress`? It's included
4331
// in the GCI, but if we also need it to establish an RPC connection, then
4432
// it needs to be learned somehow, rather than being hardcoded.
4533

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');
6535

6636
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;
7746
}
7847

79-
getMailbox().then(m => console.log(`mailbox is`, m));
80-
8148
const rpcURL = `http://${rpcAddresses[0]}`;
8249
const rpcWSURL = `ws://${rpcAddresses[0]}`;
8350
function getGenesis() {
@@ -119,68 +86,41 @@ export async function connectToChain(basedir, GCI, rpcAddresses, myAddr, inbound
11986

12087
c.lightClient.on('update', _a => {
12188
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);
12691
});
12792

12893
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.
13894
console.log(`delivering to chain`, GCI, newMessages, acknum);
13995

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+
}
184124

185125
}
186126

0 commit comments

Comments
 (0)