@taqueria/plugin-tezbox 0.61.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +15 -0
- package/docker.ts +8 -0
- package/index.d.mts +2 -0
- package/index.d.ts +2 -0
- package/index.js +720 -0
- package/index.js.map +1 -0
- package/index.mjs +703 -0
- package/index.mjs.map +1 -0
- package/index.ts +71 -0
- package/package.json +74 -0
- package/proxy.ts +1129 -0
- package/tsconfig.json +108 -0
- package/types.ts +34 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { Option, Plugin, Task } from "@taqueria/node-sdk";
|
|
3
|
+
|
|
4
|
+
// proxy.ts
|
|
5
|
+
import {
|
|
6
|
+
execCmd,
|
|
7
|
+
getArch,
|
|
8
|
+
getDockerImage as getDockerImage2,
|
|
9
|
+
sendAsyncErr,
|
|
10
|
+
sendAsyncJsonRes,
|
|
11
|
+
sendAsyncRes
|
|
12
|
+
} from "@taqueria/node-sdk";
|
|
13
|
+
import { generateSecretKey, InMemorySigner } from "@taquito/signer";
|
|
14
|
+
import BigNumber from "bignumber.js";
|
|
15
|
+
import * as bip39 from "bip39";
|
|
16
|
+
import { createHash } from "crypto";
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as hjson from "hjson";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
|
|
21
|
+
// docker.ts
|
|
22
|
+
import { getDockerImage } from "@taqueria/node-sdk";
|
|
23
|
+
var getDefaultDockerImage = (opts) => `ghcr.io/tez-capital/tezbox:tezos-v20.3.20241003`;
|
|
24
|
+
|
|
25
|
+
// proxy.ts
|
|
26
|
+
var logger = {
|
|
27
|
+
info: (message) => console.log(message),
|
|
28
|
+
warn: (message) => console.warn(message),
|
|
29
|
+
error: (message) => console.error(message)
|
|
30
|
+
};
|
|
31
|
+
function getErrorMessage(prefix, error) {
|
|
32
|
+
if (prefix === "") {
|
|
33
|
+
return error instanceof Error ? error.message : String(error);
|
|
34
|
+
}
|
|
35
|
+
if (typeof error === "boolean") {
|
|
36
|
+
return `${prefix}:`;
|
|
37
|
+
}
|
|
38
|
+
const errorString = error instanceof Error ? error.message : String(error);
|
|
39
|
+
return `${prefix}: ${errorString}`;
|
|
40
|
+
}
|
|
41
|
+
async function runCommand(cmd, stderrHandler) {
|
|
42
|
+
try {
|
|
43
|
+
const { stdout, stderr } = await execCmd(cmd);
|
|
44
|
+
if (stderr.trim()) {
|
|
45
|
+
if (stderrHandler) {
|
|
46
|
+
await stderrHandler(stderr.trim());
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error(stderr.trim());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { stdout };
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(getErrorMessage(`Command failed`, error));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isTezBoxEnvironment(taskArgs) {
|
|
57
|
+
const environment = taskArgs.config.environment[taskArgs.env];
|
|
58
|
+
if (!environment || typeof environment !== "object") return false;
|
|
59
|
+
const sandboxes = environment.sandboxes;
|
|
60
|
+
if (!Array.isArray(sandboxes) || sandboxes.length === 0) return false;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
async function instantiateDeclaredAccount(declaredAccountName, balanceInMutez) {
|
|
64
|
+
logger.warn(`instantiateDeclaredAccount is not implemented. Returning dummy data for ${declaredAccountName}.`);
|
|
65
|
+
return {
|
|
66
|
+
encryptedKey: "unencrypted:edpktdummykey123",
|
|
67
|
+
publicKeyHash: `tz1_dummy_public_key_hash_${declaredAccountName}`,
|
|
68
|
+
secretKey: "unencrypted:edskdummysecretkey123"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function getInstantiatedAccounts(taskArgs) {
|
|
72
|
+
const sandboxConfig = getSandboxConfig(taskArgs);
|
|
73
|
+
if (!sandboxConfig.accounts) {
|
|
74
|
+
throw new Error("No instantiated accounts found in sandbox config.");
|
|
75
|
+
}
|
|
76
|
+
const accounts = sandboxConfig.accounts;
|
|
77
|
+
return Object.entries(accounts).filter(([key]) => key !== "default").reduce((acc, [key, value]) => {
|
|
78
|
+
acc[key] = value;
|
|
79
|
+
return acc;
|
|
80
|
+
}, {});
|
|
81
|
+
}
|
|
82
|
+
function getSandboxConfig(taskArgs) {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
if (!isTezBoxEnvironment(taskArgs)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`This configuration doesn't appear to be configured to use TezBox environments. Check the ${taskArgs.env} environment in your .taq/config.json.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const environment = taskArgs.config.environment[taskArgs.env];
|
|
90
|
+
const sandboxName = (_a = environment.sandboxes) == null ? void 0 : _a[0];
|
|
91
|
+
if (sandboxName) {
|
|
92
|
+
const sandboxConfig = (_b = taskArgs.config.sandbox) == null ? void 0 : _b[sandboxName];
|
|
93
|
+
if (sandboxConfig) {
|
|
94
|
+
const retval = {
|
|
95
|
+
blockTime: 1,
|
|
96
|
+
baking: "enabled" /* ENABLED */,
|
|
97
|
+
...sandboxConfig,
|
|
98
|
+
...sandboxConfig.annotations
|
|
99
|
+
};
|
|
100
|
+
return retval;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`No sandbox configuration found for ${taskArgs.env} environment.`);
|
|
104
|
+
}
|
|
105
|
+
async function getOrCreateInstantiatedAccounts(taskArgs) {
|
|
106
|
+
let instantiatedAccounts;
|
|
107
|
+
try {
|
|
108
|
+
instantiatedAccounts = await getInstantiatedAccounts(taskArgs);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
instantiatedAccounts = {};
|
|
111
|
+
const declaredAccounts = taskArgs.config.accounts;
|
|
112
|
+
for (const [accountName, balanceInMutez] of Object.entries(declaredAccounts)) {
|
|
113
|
+
const balanceInMutezBN = new BigNumber(balanceInMutez.toString().replace(/_/g, ""));
|
|
114
|
+
const instantiatedAccount = await instantiateDeclaredAccount(accountName, balanceInMutezBN);
|
|
115
|
+
instantiatedAccounts[accountName] = instantiatedAccount;
|
|
116
|
+
}
|
|
117
|
+
await saveInstantiatedAccounts(instantiatedAccounts, taskArgs.projectDir, taskArgs.env);
|
|
118
|
+
}
|
|
119
|
+
return instantiatedAccounts;
|
|
120
|
+
}
|
|
121
|
+
async function saveInstantiatedAccounts(accounts, projectDir, env) {
|
|
122
|
+
const configPath = path.join(projectDir, `.taq/config.local.${env}.json`);
|
|
123
|
+
try {
|
|
124
|
+
let config = {};
|
|
125
|
+
try {
|
|
126
|
+
const configContent = await fs.promises.readFile(configPath, "utf8");
|
|
127
|
+
config = JSON.parse(configContent);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
}
|
|
130
|
+
const sandboxAccounts = {};
|
|
131
|
+
for (const [name, account] of Object.entries(accounts)) {
|
|
132
|
+
sandboxAccounts[name] = {
|
|
133
|
+
publicKeyHash: account.publicKeyHash,
|
|
134
|
+
secretKey: account.secretKey
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
config.accounts = sandboxAccounts;
|
|
138
|
+
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
139
|
+
await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new Error(getErrorMessage("Failed to save instantiated accounts", error));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function getPublicKeyFromSecretKey(secretKey) {
|
|
145
|
+
const signer = await InMemorySigner.fromSecretKey(secretKey.replace("unencrypted:", ""));
|
|
146
|
+
const publicKey = await signer.publicKey();
|
|
147
|
+
return publicKey;
|
|
148
|
+
}
|
|
149
|
+
async function prepareTezBoxAccounts(instantiatedAccounts, declaredAccounts) {
|
|
150
|
+
const tezboxAccounts = {};
|
|
151
|
+
for (const [accountName, accountData] of Object.entries(instantiatedAccounts)) {
|
|
152
|
+
if (accountName === "default") continue;
|
|
153
|
+
const secretKey = accountData.secretKey;
|
|
154
|
+
tezboxAccounts[accountName] = {
|
|
155
|
+
pkh: accountData.publicKeyHash,
|
|
156
|
+
pk: await getPublicKeyFromSecretKey(secretKey),
|
|
157
|
+
sk: secretKey,
|
|
158
|
+
balance: accountName === "funder" ? new BigNumber(1e14).toString() : new BigNumber(declaredAccounts[accountName].toString()).toString()
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return tezboxAccounts;
|
|
162
|
+
}
|
|
163
|
+
async function writeAccountsHjson(tezboxAccounts, tezBoxConfigDir) {
|
|
164
|
+
await Promise.resolve();
|
|
165
|
+
const hjsonContent = hjson.stringify(tezboxAccounts, {
|
|
166
|
+
quotes: "min",
|
|
167
|
+
bracesSameLine: true,
|
|
168
|
+
separator: false
|
|
169
|
+
});
|
|
170
|
+
const fixedHjsonContent = hjsonContent.replaceAll('"', "");
|
|
171
|
+
const accountsHjsonPath = path.join(tezBoxConfigDir, "accounts.hjson");
|
|
172
|
+
await fs.promises.writeFile(accountsHjsonPath, fixedHjsonContent, "utf8");
|
|
173
|
+
}
|
|
174
|
+
function getDeclaredAccounts(taskArgs) {
|
|
175
|
+
const declaredAccounts = taskArgs.config.accounts;
|
|
176
|
+
return Object.entries(declaredAccounts).reduce((acc, [key, value]) => {
|
|
177
|
+
acc[key] = value.toString().replace(/_/g, "");
|
|
178
|
+
return acc;
|
|
179
|
+
}, {});
|
|
180
|
+
}
|
|
181
|
+
async function prepareAccountsHjson(taskArgs, tezBoxConfigDir) {
|
|
182
|
+
try {
|
|
183
|
+
const instantiatedAccounts = await getOrCreateInstantiatedAccounts(taskArgs);
|
|
184
|
+
const declaredAccounts = getDeclaredAccounts(taskArgs);
|
|
185
|
+
const tezboxAccounts = await prepareTezBoxAccounts(instantiatedAccounts, declaredAccounts);
|
|
186
|
+
await writeAccountsHjson(tezboxAccounts, tezBoxConfigDir);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error(getErrorMessage(`Failed to prepare accounts`, error));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function prepareBakersHjson(taskArgs, tezBoxConfigDir) {
|
|
192
|
+
try {
|
|
193
|
+
const declaredAccounts = getDeclaredAccounts(taskArgs);
|
|
194
|
+
const totalBalance = Object.values(declaredAccounts).reduce(
|
|
195
|
+
(sum, balance) => BigNumber.sum(sum, balance),
|
|
196
|
+
new BigNumber(0)
|
|
197
|
+
).toString();
|
|
198
|
+
const bakers = {
|
|
199
|
+
baker1: {
|
|
200
|
+
pkh: "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU",
|
|
201
|
+
pk: "edpkuTXkJDGcFd5nh6VvMz8phXxU3Bi7h6hqgywNFi1vZTfQNnS1RV",
|
|
202
|
+
sk: "unencrypted:edsk4ArLQgBTLWG5FJmnGnT689VKoqhXwmDPBuGx3z4cvwU9MmrPZZ",
|
|
203
|
+
balance: totalBalance
|
|
204
|
+
},
|
|
205
|
+
baker2: {
|
|
206
|
+
pkh: "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN",
|
|
207
|
+
pk: "edpktzNbDAUjUk697W7gYg2CRuBQjyPxbEg8dLccYYwKSKvkPvjtV9",
|
|
208
|
+
sk: "unencrypted:edsk39qAm1fiMjgmPkw1EgQYkMzkJezLNewd7PLNHTkr6w9XA2zdfo",
|
|
209
|
+
balance: totalBalance
|
|
210
|
+
},
|
|
211
|
+
baker3: {
|
|
212
|
+
pkh: "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv",
|
|
213
|
+
pk: "edpkuFrRoDSEbJYgxRtLx2ps82UdaYc1WwfS9sE11yhauZt5DgCHbU",
|
|
214
|
+
sk: "unencrypted:edsk2uqQB9AY4FvioK2YMdfmyMrer5R8mGFyuaLLFfSRo8EoyNdht3",
|
|
215
|
+
balance: totalBalance
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const hjsonContent = hjson.stringify(bakers, {
|
|
219
|
+
quotes: "min",
|
|
220
|
+
bracesSameLine: true,
|
|
221
|
+
separator: false
|
|
222
|
+
});
|
|
223
|
+
const fixedHjsonContent = hjsonContent.replaceAll('"', "");
|
|
224
|
+
const bakersHjsonPath = path.join(tezBoxConfigDir, "bakers.hjson");
|
|
225
|
+
await fs.promises.writeFile(bakersHjsonPath, fixedHjsonContent, "utf8");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw new Error(getErrorMessage(`Failed to prepare bakers`, error));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function getProjectId(taskArgs) {
|
|
231
|
+
return createHash("sha256").update(taskArgs.projectDir).digest("hex");
|
|
232
|
+
}
|
|
233
|
+
function getDockerContainerName(taskArgs) {
|
|
234
|
+
const projectId = getProjectId(taskArgs);
|
|
235
|
+
return `taq-${taskArgs.env}-${projectId}`;
|
|
236
|
+
}
|
|
237
|
+
function getTezBoxConfigDir(taskArgs) {
|
|
238
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
239
|
+
return path.join(taskArgs.projectDir, `.taq/.${containerName}/config`);
|
|
240
|
+
}
|
|
241
|
+
function getTezBoxDataDir(taskArgs) {
|
|
242
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
243
|
+
return path.join(taskArgs.projectDir, `.taq/.${containerName}/data`);
|
|
244
|
+
}
|
|
245
|
+
function getImage(taskArgs) {
|
|
246
|
+
return getDockerImage2(getDefaultDockerImage(taskArgs), "TAQ_TEZBOX_IMAGE");
|
|
247
|
+
}
|
|
248
|
+
async function isSandboxRunning(taskArgs) {
|
|
249
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
250
|
+
const cmd = `docker ps --filter "name=${containerName}" --format "{{.ID}}"`;
|
|
251
|
+
const { stdout } = await runCommand(cmd);
|
|
252
|
+
return stdout.trim() !== "";
|
|
253
|
+
}
|
|
254
|
+
async function checkSandboxRunning(taskArgs) {
|
|
255
|
+
const running = await isSandboxRunning(taskArgs);
|
|
256
|
+
if (running) {
|
|
257
|
+
await sendAsyncRes("Sandbox is already running.");
|
|
258
|
+
}
|
|
259
|
+
return running;
|
|
260
|
+
}
|
|
261
|
+
function getPortNumber(taskArgs) {
|
|
262
|
+
const rpcUrl = getSandboxConfig(taskArgs).rpcUrl;
|
|
263
|
+
const match = rpcUrl.match(/:(\d+)/);
|
|
264
|
+
return match ? parseInt(match[1], 10) : 80;
|
|
265
|
+
}
|
|
266
|
+
function getContainerPort(taskArgs) {
|
|
267
|
+
return getPortNumber(taskArgs) + 1;
|
|
268
|
+
}
|
|
269
|
+
async function getDockerRunParams(taskArgs) {
|
|
270
|
+
const image = getImage(taskArgs);
|
|
271
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
272
|
+
const platform = await getArch();
|
|
273
|
+
const configDir = getTezBoxConfigDir(taskArgs);
|
|
274
|
+
const dataDir = getTezBoxDataDir(taskArgs);
|
|
275
|
+
const port = getContainerPort(taskArgs);
|
|
276
|
+
return { platform, image, containerName, configDir, dataDir, port };
|
|
277
|
+
}
|
|
278
|
+
async function ensureDirectoriesExist(directories) {
|
|
279
|
+
await Promise.all(
|
|
280
|
+
directories.map((dir) => fs.promises.mkdir(dir, { recursive: true }))
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
function constructDockerRunCommand(params) {
|
|
284
|
+
const { platform, image, containerName, configDir, port } = params;
|
|
285
|
+
const dockerOptions = [
|
|
286
|
+
"docker run",
|
|
287
|
+
"-d",
|
|
288
|
+
`--platform ${platform}`,
|
|
289
|
+
"-p 8732:8732",
|
|
290
|
+
`-p ${port}:20000`,
|
|
291
|
+
`--name ${containerName}`,
|
|
292
|
+
`-v "${configDir}:/tezbox/overrides"`,
|
|
293
|
+
image
|
|
294
|
+
// 'qenabox', // TODO: restore once working upstream
|
|
295
|
+
];
|
|
296
|
+
return dockerOptions.join(" ");
|
|
297
|
+
}
|
|
298
|
+
function validateBlockTime(taskArgs) {
|
|
299
|
+
const sandboxConfig = getSandboxConfig(taskArgs);
|
|
300
|
+
const blockTime = sandboxConfig.blockTime;
|
|
301
|
+
if (blockTime === void 0 || blockTime === null) {
|
|
302
|
+
logger.warn("Block time is not specified; skipping block_time override.");
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return blockTime;
|
|
306
|
+
}
|
|
307
|
+
async function writeSandboxParameters(protocolId, parameters, tezBoxConfigDir) {
|
|
308
|
+
const protocolsDir = path.join(tezBoxConfigDir, "protocols", protocolId);
|
|
309
|
+
await fs.promises.mkdir(protocolsDir, { recursive: true });
|
|
310
|
+
const hjsonContent = hjson.stringify(parameters, {
|
|
311
|
+
quotes: "min",
|
|
312
|
+
bracesSameLine: true,
|
|
313
|
+
separator: false
|
|
314
|
+
});
|
|
315
|
+
const sandboxParamsPath = path.join(protocolsDir, "sandbox-parameters.hjson");
|
|
316
|
+
await fs.promises.writeFile(sandboxParamsPath, hjsonContent, "utf8");
|
|
317
|
+
try {
|
|
318
|
+
await fs.promises.chmod(sandboxParamsPath, 420);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.warn(getErrorMessage(`Failed to set file permissions for ${sandboxParamsPath}`, error));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function applyBlockTimeOverrideToProtocol(protocolId, blockTime, tezBoxConfigDir) {
|
|
324
|
+
const nonce_revelation_threshold = 16;
|
|
325
|
+
const minimal_block_delay = blockTime;
|
|
326
|
+
const parameters = {
|
|
327
|
+
minimal_block_delay: minimal_block_delay.toString()
|
|
328
|
+
};
|
|
329
|
+
await writeSandboxParameters(protocolId, parameters, tezBoxConfigDir);
|
|
330
|
+
}
|
|
331
|
+
async function applyBlockTimeOverrideToProtocols(protocolIds, blockTime, tezBoxConfigDir) {
|
|
332
|
+
await Promise.all(
|
|
333
|
+
protocolIds.map(async (protocolId) => {
|
|
334
|
+
if (/^alpha$/i.test(protocolId)) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
await applyBlockTimeOverrideToProtocol(protocolId, blockTime, tezBoxConfigDir);
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
async function getProtocolIds(taskArgs) {
|
|
342
|
+
const image = getImage(taskArgs);
|
|
343
|
+
const cmd = `docker run --rm --entrypoint ls ${image} /tezbox/configuration/protocols`;
|
|
344
|
+
const { stdout } = await runCommand(cmd);
|
|
345
|
+
const protocolIds = stdout.trim().split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
346
|
+
return protocolIds;
|
|
347
|
+
}
|
|
348
|
+
async function readProtocolJson(image, protocolId) {
|
|
349
|
+
const cmd = `docker run --rm --entrypoint cat ${image} /tezbox/configuration/protocols/${protocolId}/protocol.hjson`;
|
|
350
|
+
try {
|
|
351
|
+
const { stdout } = await runCommand(cmd);
|
|
352
|
+
if (!stdout.trim()) {
|
|
353
|
+
logger.warn(`protocol.hjson not found or empty for protocolId ${protocolId}; skipping this protocol.`);
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
const protocolData = hjson.parse(stdout);
|
|
357
|
+
const protocolHash = protocolData.hash;
|
|
358
|
+
if (protocolHash) {
|
|
359
|
+
return { id: protocolId, hash: protocolHash };
|
|
360
|
+
} else {
|
|
361
|
+
logger.warn(`Protocol hash not found in protocol.hjson for protocolId ${protocolId}; skipping.`);
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
logger.warn(getErrorMessage(`Failed to read protocol.hjson for protocolId ${protocolId}`, error));
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function getProtocolMappings(taskArgs) {
|
|
370
|
+
const image = getImage(taskArgs);
|
|
371
|
+
const protocolIds = await getProtocolIds(taskArgs);
|
|
372
|
+
const protocolMappingsPromises = protocolIds.map(async (protocolId) => {
|
|
373
|
+
const mapping = await readProtocolJson(image, protocolId);
|
|
374
|
+
return mapping;
|
|
375
|
+
});
|
|
376
|
+
const protocolMappings = await Promise.all(protocolMappingsPromises);
|
|
377
|
+
return protocolMappings.filter((mapping) => mapping !== null);
|
|
378
|
+
}
|
|
379
|
+
async function getOctezClientProtocols(taskArgs) {
|
|
380
|
+
const image = getImage(taskArgs);
|
|
381
|
+
const cmd = `docker run --rm --entrypoint octez-client ${image} -M mockup list mockup protocols`;
|
|
382
|
+
const { stdout } = await runCommand(cmd, (stderr) => {
|
|
383
|
+
const ignorableError = "Base directory /tezbox/data/.tezos-client does not exist.";
|
|
384
|
+
if (stderr.trim() !== "" && !stderr.includes(ignorableError)) {
|
|
385
|
+
throw new Error(`Failed to list protocols: ${stderr.trim()}`);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
const protocols = stdout.trim().split("\n").filter((line) => line.trim() !== "");
|
|
389
|
+
return protocols;
|
|
390
|
+
}
|
|
391
|
+
async function prepareSandboxParametersHjson(taskArgs, tezBoxConfigDir) {
|
|
392
|
+
try {
|
|
393
|
+
const blockTime = validateBlockTime(taskArgs);
|
|
394
|
+
if (blockTime === null) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const protocolMappings = await getProtocolMappings(taskArgs);
|
|
398
|
+
const hashToIdMap = {};
|
|
399
|
+
for (const mapping of protocolMappings) {
|
|
400
|
+
hashToIdMap[mapping.hash] = mapping.id;
|
|
401
|
+
}
|
|
402
|
+
const protocolHashes = await getOctezClientProtocols(taskArgs);
|
|
403
|
+
const protocolIdsToOverride = protocolHashes.map((hash) => hashToIdMap[hash]).filter((id) => id !== void 0);
|
|
404
|
+
if (protocolIdsToOverride.length === 0) {
|
|
405
|
+
logger.warn("No matching protocol IDs found; cannot set block_time override.");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
await applyBlockTimeOverrideToProtocols(protocolIdsToOverride, blockTime, tezBoxConfigDir);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const errorMessage = getErrorMessage(`Failed to prepare sandbox parameters:`, error);
|
|
411
|
+
throw new Error(errorMessage);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function prepareBakerHjson(tezBoxConfigDir) {
|
|
415
|
+
const servicesDir = path.join(tezBoxConfigDir, "services");
|
|
416
|
+
try {
|
|
417
|
+
await fs.promises.mkdir(servicesDir, { recursive: true });
|
|
418
|
+
const bakerConfig = {
|
|
419
|
+
autostart: false
|
|
420
|
+
};
|
|
421
|
+
const hjsonContent = hjson.stringify(bakerConfig, {
|
|
422
|
+
quotes: "all",
|
|
423
|
+
bracesSameLine: true,
|
|
424
|
+
separator: true
|
|
425
|
+
});
|
|
426
|
+
const bakerConfigPath = path.join(servicesDir, "baker.hjson");
|
|
427
|
+
await fs.promises.writeFile(bakerConfigPath, hjsonContent, "utf8");
|
|
428
|
+
} catch (error) {
|
|
429
|
+
throw new Error(getErrorMessage(`Failed to prepare baker.hjson`, error));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function prepareTezBoxOverrides(taskArgs) {
|
|
433
|
+
const tezBoxConfigDir = getTezBoxConfigDir(taskArgs);
|
|
434
|
+
try {
|
|
435
|
+
const sandboxConfig = getSandboxConfig(taskArgs);
|
|
436
|
+
await fs.promises.mkdir(tezBoxConfigDir, { recursive: true });
|
|
437
|
+
const tasks = [];
|
|
438
|
+
tasks.push(prepareBakersHjson(taskArgs, tezBoxConfigDir));
|
|
439
|
+
if (taskArgs.config.accounts) {
|
|
440
|
+
tasks.push(prepareAccountsHjson(taskArgs, tezBoxConfigDir));
|
|
441
|
+
}
|
|
442
|
+
if (sandboxConfig.blockTime) {
|
|
443
|
+
tasks.push(prepareSandboxParametersHjson(taskArgs, tezBoxConfigDir));
|
|
444
|
+
}
|
|
445
|
+
if (sandboxConfig.baking === "disabled" /* DISABLED */) {
|
|
446
|
+
tasks.push(prepareBakerHjson(tezBoxConfigDir));
|
|
447
|
+
}
|
|
448
|
+
await Promise.all(tasks);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
throw new Error(getErrorMessage(`Failed to prepare TezBox overrides`, error));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function getProxyContainerName(taskArgs) {
|
|
454
|
+
return `${getDockerContainerName(taskArgs)}-proxy`;
|
|
455
|
+
}
|
|
456
|
+
async function startProxyServer(taskArgs) {
|
|
457
|
+
const containerPort = getContainerPort(taskArgs);
|
|
458
|
+
const proxyPort = getPortNumber(taskArgs);
|
|
459
|
+
const proxyContainerName = getProxyContainerName(taskArgs);
|
|
460
|
+
const proxyCmd = `docker run -d --name ${proxyContainerName} --network host caddy:2-alpine caddy reverse-proxy --from http://:${proxyPort} --to http://127.0.0.1:${containerPort} --access-log`;
|
|
461
|
+
try {
|
|
462
|
+
await runCommand(proxyCmd);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
throw new Error(getErrorMessage(`Failed to start Caddy reverse proxy`, error));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function stopProxyServer(taskArgs) {
|
|
468
|
+
const proxyContainerName = getProxyContainerName(taskArgs);
|
|
469
|
+
const cmd = `docker rm -f ${proxyContainerName}`;
|
|
470
|
+
await runCommand(cmd);
|
|
471
|
+
}
|
|
472
|
+
async function startSandbox(taskArgs) {
|
|
473
|
+
try {
|
|
474
|
+
await checkDockerAvailability();
|
|
475
|
+
if (await checkSandboxRunning(taskArgs)) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const params = await getDockerRunParams(taskArgs);
|
|
479
|
+
await ensureDirectoriesExist([params.dataDir, params.configDir]);
|
|
480
|
+
await prepareTezBoxOverrides(taskArgs);
|
|
481
|
+
const cmd = constructDockerRunCommand(params);
|
|
482
|
+
await runCommand(cmd);
|
|
483
|
+
await startProxyServer(taskArgs);
|
|
484
|
+
await sendAsyncRes("Sandbox started successfully.");
|
|
485
|
+
} catch (error) {
|
|
486
|
+
await sendAsyncErr(getErrorMessage(`Failed to start sandbox`, error));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function checkDockerAvailability() {
|
|
490
|
+
try {
|
|
491
|
+
await runCommand("docker --version");
|
|
492
|
+
} catch (error) {
|
|
493
|
+
throw new Error("Docker is not installed or not running. Please install and start Docker.");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async function removeSandboxContainer(taskArgs) {
|
|
497
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
498
|
+
const cmd = `docker rm -f ${containerName}`;
|
|
499
|
+
try {
|
|
500
|
+
await runCommand(cmd);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
const errorMessage = getErrorMessage("", error);
|
|
503
|
+
if (errorMessage.includes("No such container")) {
|
|
504
|
+
await sendAsyncRes("Sandbox is not running or already stopped.");
|
|
505
|
+
} else {
|
|
506
|
+
throw new Error(errorMessage);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
await stopProxyServer(taskArgs);
|
|
510
|
+
}
|
|
511
|
+
async function stopSandbox(taskArgs) {
|
|
512
|
+
try {
|
|
513
|
+
await removeSandboxContainer(taskArgs);
|
|
514
|
+
const configDir = getTezBoxConfigDir(taskArgs);
|
|
515
|
+
await fs.promises.rm(configDir, { recursive: true, force: true });
|
|
516
|
+
await sendAsyncRes("Sandbox stopped and cleaned up.");
|
|
517
|
+
} catch (error) {
|
|
518
|
+
await sendAsyncErr(getErrorMessage(`Failed to stop sandbox`, error));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async function restartSandbox(taskArgs) {
|
|
522
|
+
try {
|
|
523
|
+
await removeSandboxContainer(taskArgs);
|
|
524
|
+
await startSandbox(taskArgs);
|
|
525
|
+
await sendAsyncRes("Sandbox restarted successfully.");
|
|
526
|
+
} catch (error) {
|
|
527
|
+
await sendAsyncErr(getErrorMessage(`Failed to restart sandbox`, error));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function listProtocols(taskArgs) {
|
|
531
|
+
try {
|
|
532
|
+
const protocolHashes = await getOctezClientProtocols(taskArgs);
|
|
533
|
+
const protocolObjects = protocolHashes.map((protocol) => ({ protocol }));
|
|
534
|
+
await sendAsyncJsonRes(protocolObjects);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
await sendAsyncErr(getErrorMessage(`Failed to list protocols`, error));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function listAccounts(taskArgs) {
|
|
540
|
+
try {
|
|
541
|
+
if (await isSandboxRunning(taskArgs)) {
|
|
542
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
543
|
+
const cmd = `docker exec ${containerName} octez-client list known addresses`;
|
|
544
|
+
const { stdout } = await runCommand(cmd);
|
|
545
|
+
if (!stdout.trim()) {
|
|
546
|
+
await sendAsyncRes("No accounts found.");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const accounts = stdout.trim().split("\n").filter((line) => line.trim() !== "").map((line) => {
|
|
550
|
+
const [name, rest] = line.split(":");
|
|
551
|
+
const address = rest ? rest.trim().split(" ")[0] : "";
|
|
552
|
+
return { name: name.trim(), address };
|
|
553
|
+
});
|
|
554
|
+
await sendAsyncJsonRes(accounts);
|
|
555
|
+
} else {
|
|
556
|
+
await sendAsyncErr(`Sandbox is not running. Please start the sandbox before attempting to list accounts.`);
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
await sendAsyncErr(getErrorMessage(`Failed to list accounts`, error));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function bakeBlock(taskArgs) {
|
|
563
|
+
try {
|
|
564
|
+
if (await isSandboxRunning(taskArgs)) {
|
|
565
|
+
const containerName = getDockerContainerName(taskArgs);
|
|
566
|
+
const cmd = `docker exec ${containerName} octez-client bake for baker1`;
|
|
567
|
+
if (taskArgs.watch) {
|
|
568
|
+
console.log("Baking on demand as operations are injected.");
|
|
569
|
+
console.log("Press CTRL-C to stop and exit.");
|
|
570
|
+
console.log();
|
|
571
|
+
while (true) {
|
|
572
|
+
console.log("Waiting for operations to be injected...");
|
|
573
|
+
while (true) {
|
|
574
|
+
const { stdout } = await runCommand(
|
|
575
|
+
`docker exec ${containerName} octez-client rpc get /chains/main/mempool/pending_operations`
|
|
576
|
+
);
|
|
577
|
+
const ops = JSON.parse(stdout);
|
|
578
|
+
if (Array.isArray(ops.applied) && ops.applied.length > 0) break;
|
|
579
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
580
|
+
}
|
|
581
|
+
await runCommand(cmd);
|
|
582
|
+
console.log("Block baked.");
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
await runCommand(cmd);
|
|
586
|
+
await sendAsyncRes("Block baked successfully.");
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
try {
|
|
590
|
+
await sendAsyncErr("Sandbox is not running. Please start the sandbox before attempting to bake a block.");
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
} catch (error) {
|
|
595
|
+
await sendAsyncErr(getErrorMessage(`Failed to bake block`, error));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
var proxy = async (taskArgs) => {
|
|
599
|
+
var _a;
|
|
600
|
+
if (!isTezBoxEnvironment(taskArgs)) {
|
|
601
|
+
await sendAsyncErr(
|
|
602
|
+
`This configuration doesn't appear to be configured to use TezBox environments. Check the ${taskArgs.env} environment in your .taq/config.json.`
|
|
603
|
+
);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const taskName = (_a = taskArgs.task) == null ? void 0 : _a.toLowerCase().trim();
|
|
607
|
+
const taskHandlers = {
|
|
608
|
+
"start sandbox": startSandbox,
|
|
609
|
+
"stop sandbox": stopSandbox,
|
|
610
|
+
"restart sandbox": restartSandbox,
|
|
611
|
+
"list protocols": listProtocols,
|
|
612
|
+
"list-protocols": listProtocols,
|
|
613
|
+
"show protocols": listProtocols,
|
|
614
|
+
"show-protocols": listProtocols,
|
|
615
|
+
"list accounts": listAccounts,
|
|
616
|
+
"list-accounts": listAccounts,
|
|
617
|
+
"show accounts": listAccounts,
|
|
618
|
+
"show-accounts": listAccounts,
|
|
619
|
+
"bake": bakeBlock,
|
|
620
|
+
"bake block": bakeBlock
|
|
621
|
+
};
|
|
622
|
+
const handler = taskName ? taskHandlers[taskName] : void 0;
|
|
623
|
+
if (handler) {
|
|
624
|
+
try {
|
|
625
|
+
await handler(taskArgs);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
await sendAsyncErr(getErrorMessage(`Error executing task '${taskArgs.task}'`, error));
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
await sendAsyncErr(taskArgs.task ? `Unknown task: ${taskArgs.task}` : "No task provided");
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
var proxy_default = proxy;
|
|
634
|
+
|
|
635
|
+
// index.ts
|
|
636
|
+
Plugin.create((_i18n) => ({
|
|
637
|
+
alias: "tezbox",
|
|
638
|
+
schema: "1.0",
|
|
639
|
+
version: "0.1",
|
|
640
|
+
tasks: [
|
|
641
|
+
Task.create({
|
|
642
|
+
task: "start sandbox",
|
|
643
|
+
command: "start sandbox",
|
|
644
|
+
aliases: ["start tezbox"],
|
|
645
|
+
description: "Starts a TezBox sandbox",
|
|
646
|
+
options: [],
|
|
647
|
+
handler: "proxy",
|
|
648
|
+
encoding: "none"
|
|
649
|
+
}),
|
|
650
|
+
Task.create({
|
|
651
|
+
task: "stop sandbox",
|
|
652
|
+
command: "stop sandbox",
|
|
653
|
+
aliases: ["stop tezbox"],
|
|
654
|
+
description: "Stops a TezBox sandbox",
|
|
655
|
+
options: [],
|
|
656
|
+
handler: "proxy"
|
|
657
|
+
}),
|
|
658
|
+
Task.create({
|
|
659
|
+
task: "restart sandbox",
|
|
660
|
+
command: "restart sandbox",
|
|
661
|
+
aliases: ["restart tezbox"],
|
|
662
|
+
description: "Restarts a TezBox sandbox",
|
|
663
|
+
options: [],
|
|
664
|
+
handler: "proxy"
|
|
665
|
+
}),
|
|
666
|
+
Task.create({
|
|
667
|
+
task: "list accounts",
|
|
668
|
+
command: "list accounts",
|
|
669
|
+
aliases: [],
|
|
670
|
+
description: "List the balances of all sandbox accounts",
|
|
671
|
+
options: [],
|
|
672
|
+
handler: "proxy",
|
|
673
|
+
encoding: "json"
|
|
674
|
+
}),
|
|
675
|
+
Task.create({
|
|
676
|
+
task: "bake",
|
|
677
|
+
command: "bake",
|
|
678
|
+
aliases: ["b"],
|
|
679
|
+
description: 'Manually bake a block. Use when the "baking" setting of a TezBox sandbox is set to "disabled".',
|
|
680
|
+
options: [
|
|
681
|
+
Option.create({
|
|
682
|
+
flag: "watch",
|
|
683
|
+
shortFlag: "w",
|
|
684
|
+
description: "Watch for operations as they are injected into the mempool and bake them as immediate as possible.",
|
|
685
|
+
boolean: true
|
|
686
|
+
})
|
|
687
|
+
],
|
|
688
|
+
handler: "proxy",
|
|
689
|
+
encoding: "none"
|
|
690
|
+
}),
|
|
691
|
+
Task.create({
|
|
692
|
+
task: "show protocols",
|
|
693
|
+
command: "show protocols",
|
|
694
|
+
aliases: ["list protocols"],
|
|
695
|
+
description: "List protocols understood by this version of TezBox",
|
|
696
|
+
options: [],
|
|
697
|
+
handler: "proxy",
|
|
698
|
+
encoding: "json"
|
|
699
|
+
})
|
|
700
|
+
],
|
|
701
|
+
proxy: proxy_default
|
|
702
|
+
}), process.argv);
|
|
703
|
+
//# sourceMappingURL=index.mjs.map
|