mirror of
https://github.com/EvernodeXRPL/hp-devkit.git
synced 2026-04-29 15:37:58 +00:00
Added new contract templates (#31)
This commit is contained in:
@@ -24,6 +24,7 @@ FROM ubuntu:focal as runner
|
||||
COPY --from=builder /build/docker-extracted/usr/bin/docker /usr/bin/
|
||||
COPY --from=builder /build/scripts/cluster.sh /usr/bin/cluster
|
||||
COPY --from=builder /build/scripts/codegen.sh /usr/bin/codegen
|
||||
COPY --from=builder /build/scripts/templates.sh /usr/bin/templates
|
||||
COPY --from=builder /build/jq /usr/bin/jq
|
||||
|
||||
COPY code-templates /code-templates
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"hotpocket-js-client": "0.5.4"
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx ncc build src/_projname_.js -o dist",
|
||||
"build": "npx ncc build src/contract.js -o dist",
|
||||
"build:prod": "npx ncc build src/contract.js --minify -o dist",
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "0.5.10",
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ async function clientApp() {
|
||||
console.log('My public key is: ' + pkhex);
|
||||
|
||||
const ip = process.argv[2] || 'localhost';
|
||||
const port = process.argv[3] || '8080';
|
||||
const port = process.argv[3] || '8081';
|
||||
const client = await HotPocket.createClient(
|
||||
['wss://' + ip + ':' + port],
|
||||
userKeyPair,
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"bson": "6.4.0",
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
163
docker/code-templates/nodejs/file-client/_projname_.js
Normal file
163
docker/code-templates/nodejs/file-client/_projname_.js
Normal file
@@ -0,0 +1,163 @@
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
const bson = require('bson');
|
||||
const path = require("path");
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
async function clientApp() {
|
||||
|
||||
const keyFile = 'user.key';
|
||||
|
||||
// Re-generate a user key pair for the client.
|
||||
if (process.argv[2] == 'generatekeys' || !fs.existsSync(keyFile)) {
|
||||
const newKeyPair = await HotPocket.generateKeys();
|
||||
const saveData = Buffer.from(newKeyPair.privateKey).toString('hex');
|
||||
fs.writeFileSync(keyFile, saveData);
|
||||
console.log('New key pair generated.');
|
||||
|
||||
if (process.argv[2] == 'generatekeys') {
|
||||
const pkhex = Buffer.from(newKeyPair.publicKey).toString('hex');
|
||||
console.log('My public key is: ' + pkhex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the key pair using saved private key data.
|
||||
const savedPrivateKeyHex = fs.readFileSync(keyFile).toString();
|
||||
const userKeyPair = await HotPocket.generateKeys(savedPrivateKeyHex);
|
||||
|
||||
const pkhex = Buffer.from(userKeyPair.publicKey).toString('hex');
|
||||
console.log('My public key is: ' + pkhex);
|
||||
|
||||
const ip = process.argv[2] || 'localhost';
|
||||
const port = process.argv[3] || '8081';
|
||||
const client = await HotPocket.createClient(
|
||||
['wss://' + ip + ':' + port],
|
||||
userKeyPair,
|
||||
{ protocol: HotPocket.protocols.bson }
|
||||
);
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await client.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
rl.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
client.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Disconnected');
|
||||
rl.close();
|
||||
});
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
client.on(HotPocket.events.contractOutput, (r) => {
|
||||
|
||||
r.outputs.forEach(output => {
|
||||
handleOutput(output, r.ledgerSeqNo);
|
||||
});
|
||||
});
|
||||
|
||||
const handleOutput = (output, ledgerSeqNo) => {
|
||||
// If bson.deserialize error occurred it'll be caught by this try catch.
|
||||
try {
|
||||
const result = bson.deserialize(output);
|
||||
if (result.type == "uploadResult") {
|
||||
if (result.status == "ok")
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${result.status}`);
|
||||
else
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Upload failed. reason: ${result.status}`);
|
||||
}
|
||||
else if (result.type == "deleteResult") {
|
||||
if (result.status == "ok")
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${result.status}`);
|
||||
else
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Delete failed. reason: ${result.status}`);
|
||||
}
|
||||
else if (result.type == "downloadResult") {
|
||||
if (result.status == "ok")
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${result.content}`);
|
||||
else
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Download failed. reason: ${result.status}`);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const input_pump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
let input;
|
||||
if (inp.startsWith("download ")) {
|
||||
const fileName = inp.substr(9);
|
||||
|
||||
// Read only inputs can be sent as read requests, So it'll be quick.
|
||||
const output = await client.submitContractReadRequest(bson.serialize({
|
||||
type: "download",
|
||||
fileName: fileName,
|
||||
}));
|
||||
|
||||
handleOutput(output, (await client.getStatus()).ledgerSeqNo);
|
||||
}
|
||||
else if (inp.startsWith("upload ")) {
|
||||
|
||||
const filePath = inp.substr(7);
|
||||
const fileName = path.basename(filePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
const sizeKB = Math.round(fileContent.length / 1024);
|
||||
console.log("Uploading file " + fileName + " (" + sizeKB + " KB)");
|
||||
|
||||
input = await client.submitContractInput(bson.serialize({
|
||||
type: "upload",
|
||||
fileName: fileName,
|
||||
content: fileContent
|
||||
}));
|
||||
}
|
||||
else
|
||||
console.log("File not found");
|
||||
}
|
||||
else if (inp.startsWith("delete ")) {
|
||||
const fileName = inp.substr(7);
|
||||
|
||||
input = await client.submitContractInput(bson.serialize({
|
||||
type: "delete",
|
||||
fileName: fileName,
|
||||
}));
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [upload <local path>], [delete <file name>] or [download <file name>] expected.")
|
||||
}
|
||||
|
||||
if (input) {
|
||||
const submission = await input.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
console.log("Submission failed. reason: " + submission.reason);
|
||||
}
|
||||
|
||||
input_pump();
|
||||
})
|
||||
}
|
||||
input_pump();
|
||||
}
|
||||
|
||||
clientApp();
|
||||
7
docker/code-templates/nodejs/file-client/package.json
Normal file
7
docker/code-templates/nodejs/file-client/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"bson": "6.4.0",
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
6
docker/code-templates/nodejs/file-contract/dist/hp.cfg.override
vendored
Normal file
6
docker/code-templates/nodejs/file-contract/dist/hp.cfg.override
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"contract": {
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "index.js"
|
||||
}
|
||||
}
|
||||
14
docker/code-templates/nodejs/file-contract/package.json
Normal file
14
docker/code-templates/nodejs/file-contract/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx ncc build src/contract.js -o dist",
|
||||
"build:prod": "npx ncc build src/contract.js --minify -o dist",
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson": "6.4.0",
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
98
docker/code-templates/nodejs/file-contract/src/_projname_.js
Normal file
98
docker/code-templates/nodejs/file-contract/src/_projname_.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const fs = require('fs');
|
||||
const bson = require('bson');
|
||||
|
||||
export class _projname_ {
|
||||
sendOutput; // This function must be wired up by the caller.
|
||||
|
||||
async handleRequest(user, msg, isReadOnly) {
|
||||
|
||||
// This sample application defines simple file operations.
|
||||
// It's up to the application to decide the structure and contents of messages.
|
||||
|
||||
if (msg.type == "upload") {
|
||||
// Check already exist.
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "already_exists",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
// Error is too large.
|
||||
else if (msg.content.length > 10 * 1024 * 1024) { // 10MB
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "too_large",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
// Do not write in read only mode.
|
||||
if (!isReadOnly) {
|
||||
// Save the file.
|
||||
fs.writeFileSync(msg.fileName, msg.content.buffer);
|
||||
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
type: "uploadResult",
|
||||
status: "error",
|
||||
error: 'Write is not supported in readonly mode'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg.type == "delete") {
|
||||
// Delete if exist.
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
// Do not delete in read only mode.
|
||||
if (!isReadOnly) {
|
||||
fs.unlinkSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
type: "deleteResult",
|
||||
status: "error",
|
||||
error: 'Delete is not supported in readonly mode'
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Send file if exist.
|
||||
else if (msg.type == "download") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
const fileContent = fs.readFileSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName,
|
||||
content: fileContent
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
docker/code-templates/nodejs/file-contract/src/contract.js
Normal file
48
docker/code-templates/nodejs/file-contract/src/contract.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const HotPocket = require('hotpocket-nodejs-contract');
|
||||
const bson = require('bson');
|
||||
const { _projname_ } = require('./_projname_');
|
||||
|
||||
// HotPocket smart contract is defined as a function which takes the HotPocket contract context as an argument.
|
||||
// This function gets invoked every consensus round and whenever a user sends a out-of-concensus read-request.
|
||||
async function contract(ctx) {
|
||||
|
||||
// Create our application logic component.
|
||||
// This pattern allows us to test the application logic independently of HotPocket.
|
||||
const app = new _projname_();
|
||||
|
||||
// Wire-up output emissions from the application before we pass user inputs to it.
|
||||
app.sendOutput = async (user, output) => {
|
||||
await user.send(output)
|
||||
}
|
||||
|
||||
// In 'readonly' mode, nothing our contract does will get persisted on the ledger. The benefit is
|
||||
// readonly messages gets processed much faster due to not being subjected to consensus.
|
||||
// We should only use readonly mode for returning/replying data for the requesting user.
|
||||
//
|
||||
// In consensus mode (NOT read-only), we can do anything like persisting to data storage and/or
|
||||
// sending data to any connected user at the time. Everything will get subjected to consensus so
|
||||
// there is a time-penalty.
|
||||
const isReadOnly = ctx.readonly;
|
||||
|
||||
// Process user inputs.
|
||||
// Loop through list of users who have sent us inputs.
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
// Loop through inputs sent by each user.
|
||||
for (const input of user.inputs) {
|
||||
|
||||
// Read the data buffer sent by user (this can be any kind of data like string, json or binary data).
|
||||
const buf = await ctx.users.read(input);
|
||||
|
||||
// Let's assume all data buffers for this contract are binary.
|
||||
// In real-world apps, we need to gracefully filter out invalid data formats for our contract.
|
||||
const msg = bson.deserialize(buf);
|
||||
|
||||
// Pass the JSON message to our application logic component.
|
||||
await app.handleRequest(user, msg, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hpc = new HotPocket.Contract();
|
||||
hpc.init(contract);
|
||||
89
docker/code-templates/nodejs/multisig-client/_projname_.js
Normal file
89
docker/code-templates/nodejs/multisig-client/_projname_.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const readline = require('readline');
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
async function clientApp() {
|
||||
|
||||
const userKeyPair = await HotPocket.generateKeys();
|
||||
const client = await HotPocket.createClient(['wss://localhost:8081'], userKeyPair);
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await client.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
rl.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
client.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Disconnected');
|
||||
rl.close();
|
||||
});
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
client.on(HotPocket.events.contractOutput, (r) => {
|
||||
r.outputs.forEach(output => {
|
||||
handleOutput(output, r.ledgerSeqNo);
|
||||
});
|
||||
});
|
||||
|
||||
const handleOutput = (output, ledgerSeqNo) => {
|
||||
if (output.type == "makePaymentResult") {
|
||||
if (output.status == "ok")
|
||||
console.log(`(ledger:${ledgerSeqNo})>> `, output.data);
|
||||
else
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Payment failed. reason: `, output.error ?? output.status);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.", output);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const input_pump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
let input;
|
||||
if (inp.startsWith("makePayment ")) {
|
||||
const params = inp.split(' ');
|
||||
if (params.length == 3) {
|
||||
input = await client.submitContractInput(JSON.stringify({
|
||||
type: "makePayment",
|
||||
sender: params[1],
|
||||
receiver: params[2]
|
||||
}));
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [makePayment <sender-address> <receiver-address>]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [makePayment <sender-address> <receiver-address>]");
|
||||
}
|
||||
|
||||
if (input) {
|
||||
const submission = await input.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
console.log("Submission failed. reason: " + submission.reason);
|
||||
}
|
||||
|
||||
input_pump();
|
||||
})
|
||||
}
|
||||
input_pump();
|
||||
}
|
||||
|
||||
clientApp();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"hotpocket-js-client": "0.5.4"
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
6
docker/code-templates/nodejs/multisig-contract/dist/hp.cfg.override
vendored
Normal file
6
docker/code-templates/nodejs/multisig-contract/dist/hp.cfg.override
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"contract": {
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "index.js"
|
||||
}
|
||||
}
|
||||
14
docker/code-templates/nodejs/multisig-contract/package.json
Normal file
14
docker/code-templates/nodejs/multisig-contract/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx ncc build src/contract.js -o dist",
|
||||
"build:prod": "npx ncc build src/contract.js --minify -o dist",
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"everpocket-nodejs-contract": "^0.1.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
const evp = require('everpocket-nodejs-contract');
|
||||
|
||||
export class _projname_ {
|
||||
sendOutput; // This function must be wired up by the caller.
|
||||
voteContext;
|
||||
hpContext;
|
||||
|
||||
// This function will be called in each contract execution.
|
||||
async handleContractExecution(ctx) {
|
||||
this.voteContext = new evp.VoteContext(ctx);
|
||||
this.hpContext = new evp.HotPocketContext(ctx, { voteContext: this.voteContext });
|
||||
|
||||
if (!ctx.readonly) {
|
||||
// Listen to incoming unl messages and feed them to elector.
|
||||
ctx.unl.onMessage((node, msg) => {
|
||||
this.voteContext.feedUnlMessage(node, msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequest(user, message, isReadOnly) {
|
||||
// This sample application defines two simple messages. 'get' and 'set'.
|
||||
// It's up to the application to decide the structure and contents of messages.
|
||||
|
||||
if (message.type == 'makePayment') {
|
||||
if (isReadOnly) {
|
||||
await this.sendOutput(user, {
|
||||
type: 'makePaymentResult',
|
||||
status: 'error',
|
||||
error: 'Submit multisig not supported in readonly mode'
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const sender = message.sender;
|
||||
const receiver = message.receiver;
|
||||
|
||||
try {
|
||||
const xrplContext = new evp.XrplContext(this.hpContext, sender);
|
||||
await xrplContext.init();
|
||||
const tx = await xrplContext.xrplAcc.prepareMakePayment(receiver, "1", "XRP")
|
||||
|
||||
console.log("----------- Multi-Signing Transaction");
|
||||
const res = await xrplContext.multiSignAndSubmitTransaction(tx);
|
||||
console.log("Transaction submitted");
|
||||
|
||||
await this.sendOutput(user, {
|
||||
type: 'makePaymentResult',
|
||||
status: 'ok',
|
||||
data: res
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
|
||||
await this.sendOutput(user, {
|
||||
type: 'makePaymentResult',
|
||||
status: 'error',
|
||||
error: e
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
status: 'error',
|
||||
error: 'Unknown message type'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
const HotPocket = require('hotpocket-nodejs-contract');
|
||||
const { _projname_ } = require('./_projname_');
|
||||
|
||||
// HotPocket smart contract is defined as a function which takes the HotPocket contract context as an argument.
|
||||
// This function gets invoked every consensus round and whenever a user sends a out-of-concensus read-request.
|
||||
async function contract(ctx) {
|
||||
|
||||
// Create our application logic component.
|
||||
// This pattern allows us to test the application logic independently of HotPocket.
|
||||
const app = new _projname_();
|
||||
|
||||
// Wire-up output emissions from the application before we pass user inputs to it.
|
||||
app.sendOutput = async (user, output) => {
|
||||
await user.send(output)
|
||||
}
|
||||
|
||||
// In 'readonly' mode, nothing our contract does will get persisted on the ledger. The benefit is
|
||||
// readonly messages gets processed much faster due to not being subjected to consensus.
|
||||
// We should only use readonly mode for returning/replying data for the requesting user.
|
||||
//
|
||||
// In consensus mode (NOT read-only), we can do anything like persisting to data storage and/or
|
||||
// sending data to any connected user at the time. Everything will get subjected to consensus so
|
||||
// there is a time-penalty.
|
||||
const isReadOnly = ctx.readonly;
|
||||
|
||||
// This function is executed per each contract round.
|
||||
await app.handleContractExecution(ctx);
|
||||
|
||||
// Process user inputs.
|
||||
// Loop through list of users who have sent us inputs.
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
// Loop through inputs sent by each user.
|
||||
for (const input of user.inputs) {
|
||||
|
||||
// Read the data buffer sent by user (this can be any kind of data like string, json or binary data).
|
||||
const buf = await ctx.users.read(input);
|
||||
|
||||
// Let's assume all data buffers for this contract are JSON.
|
||||
// In real-world apps, we need to gracefully filter out invalid data formats for our contract.
|
||||
const message = JSON.parse(buf);
|
||||
|
||||
// Pass the JSON message to our application logic component.
|
||||
await app.handleRequest(user, message, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hpc = new HotPocket.Contract();
|
||||
hpc.init(contract);
|
||||
78
docker/code-templates/nodejs/npl-client/_projname_.js
Normal file
78
docker/code-templates/nodejs/npl-client/_projname_.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const readline = require('readline');
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
async function clientApp() {
|
||||
|
||||
const userKeyPair = await HotPocket.generateKeys();
|
||||
const client = await HotPocket.createClient(['wss://localhost:8081'], userKeyPair);
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await client.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
rl.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
client.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Disconnected');
|
||||
rl.close();
|
||||
});
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
client.on(HotPocket.events.contractOutput, (r) => {
|
||||
r.outputs.forEach(output => {
|
||||
if (output.type == "rndResult") {
|
||||
if (output.status == "ok")
|
||||
console.log(`(ledger:${r.ledgerSeqNo})>> ${JSON.stringify(output)}`);
|
||||
else
|
||||
console.log(`(ledger:${r.ledgerSeqNo})>> Rnd failed. reason: ${output.error}`);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const input_pump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
let input;
|
||||
if (inp.startsWith("rnd")) {
|
||||
|
||||
input = await client.submitContractInput(JSON.stringify({
|
||||
type: "rnd"
|
||||
}));
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [rnd] expected.")
|
||||
}
|
||||
|
||||
if (input) {
|
||||
const submission = await input.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
console.log("Submission failed. reason: " + submission.reason);
|
||||
}
|
||||
|
||||
input_pump();
|
||||
})
|
||||
}
|
||||
input_pump();
|
||||
}
|
||||
|
||||
clientApp();
|
||||
6
docker/code-templates/nodejs/npl-client/package.json
Normal file
6
docker/code-templates/nodejs/npl-client/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
6
docker/code-templates/nodejs/npl-contract/dist/hp.cfg.override
vendored
Normal file
6
docker/code-templates/nodejs/npl-contract/dist/hp.cfg.override
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"contract": {
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "index.js"
|
||||
}
|
||||
}
|
||||
13
docker/code-templates/nodejs/npl-contract/package.json
Normal file
13
docker/code-templates/nodejs/npl-contract/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx ncc build src/contract.js -o dist",
|
||||
"build:prod": "npx ncc build src/contract.js --minify -o dist",
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
65
docker/code-templates/nodejs/npl-contract/src/_projname_.js
Normal file
65
docker/code-templates/nodejs/npl-contract/src/_projname_.js
Normal file
@@ -0,0 +1,65 @@
|
||||
export class _projname_ {
|
||||
sendOutput; // This function must be wired up by the caller.
|
||||
|
||||
async handleRequest(user, message, unl, timeout, isReadOnly) {
|
||||
|
||||
// This sample application defines a simple messages. 'rnd' and 'set'.
|
||||
// It's up to the application to decide the structure and contents of messages.
|
||||
|
||||
if (message.type == 'rnd') {
|
||||
|
||||
// NPL messages are not supported in readonly mode.
|
||||
if (!isReadOnly) {
|
||||
// Start listening to incoming NPL messages before we send ours.
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
let t = setTimeout(() => {
|
||||
reject(`NPL messages aren't received withing ${timeout} ms.`);
|
||||
}, timeout);
|
||||
|
||||
let nplRes = [];
|
||||
unl.onMessage((node, msg) => {
|
||||
nplRes.push({ pubkey: node.publicKey, number: Number(msg.toString()) });
|
||||
// Resolve once all are received
|
||||
if (nplRes.length === unl.count()) {
|
||||
clearTimeout(t);
|
||||
resolve(nplRes.sort((a, b) => a.number - b.number));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await unl.send(Math.random().toString());
|
||||
|
||||
try {
|
||||
const receipt = await promise;
|
||||
|
||||
await this.sendOutput(user, {
|
||||
type: 'rndResult',
|
||||
status: "ok",
|
||||
nplMessages: receipt,
|
||||
rnd: receipt[0].number
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
await this.sendOutput(user, {
|
||||
type: "rndResult",
|
||||
status: "error",
|
||||
error: e.toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
type: "rndResult",
|
||||
status: "error",
|
||||
error: 'NPL messages are not supported in readonly mode'
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
type: 'error',
|
||||
error: 'Unknown message type'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
53
docker/code-templates/nodejs/npl-contract/src/contract.js
Normal file
53
docker/code-templates/nodejs/npl-contract/src/contract.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const HotPocket = require('hotpocket-nodejs-contract');
|
||||
const { _projname_ } = require('./_projname_');
|
||||
|
||||
// HotPocket smart contract is defined as a function which takes the HotPocket contract context as an argument.
|
||||
// This function gets invoked every consensus round and whenever a user sends a out-of-concensus read-request.
|
||||
async function contract(ctx) {
|
||||
|
||||
// Create our application logic component.
|
||||
// This pattern allows us to test the application logic independently of HotPocket.
|
||||
const app = new _projname_();
|
||||
|
||||
// Wire-up output emissions from the application before we pass user inputs to it.
|
||||
app.sendOutput = async (user, output) => {
|
||||
await user.send(output)
|
||||
}
|
||||
|
||||
// In 'readonly' mode, nothing our contract does will get persisted on the ledger. The benefit is
|
||||
// readonly messages gets processed much faster due to not being subjected to consensus.
|
||||
// We should only use readonly mode for returning/replying data for the requesting user.
|
||||
//
|
||||
// In consensus mode (NOT read-only), we can do anything like persisting to data storage and/or
|
||||
// sending data to any connected user at the time. Everything will get subjected to consensus so
|
||||
// there is a time-penalty.
|
||||
const isReadOnly = ctx.readonly;
|
||||
|
||||
// Access the HotPocket config.
|
||||
const hpconfig = await ctx.getConfig();
|
||||
// Wait only for half of roundtime for random number generation.
|
||||
const timeout = Math.ceil(hpconfig.consensus.roundtime);
|
||||
|
||||
|
||||
// Process user inputs.
|
||||
// Loop through list of users who have sent us inputs.
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
// Loop through inputs sent by each user.
|
||||
for (const input of user.inputs) {
|
||||
|
||||
// Read the data buffer sent by user (this can be any kind of data like string, json or binary data).
|
||||
const buf = await ctx.users.read(input);
|
||||
|
||||
// Let's assume all data buffers for this contract are JSON.
|
||||
// In real-world apps, we need to gracefully filter out invalid data formats for our contract.
|
||||
const message = JSON.parse(buf);
|
||||
|
||||
// Pass the JSON message to our application logic component.
|
||||
await app.handleRequest(user, message, ctx.unl, timeout, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hpc = new HotPocket.Contract();
|
||||
hpc.init(contract);
|
||||
115
docker/code-templates/nodejs/starter-client/_projname_.js
Normal file
115
docker/code-templates/nodejs/starter-client/_projname_.js
Normal file
@@ -0,0 +1,115 @@
|
||||
const readline = require('readline');
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
async function clientApp() {
|
||||
|
||||
const userKeyPair = await HotPocket.generateKeys();
|
||||
const client = await HotPocket.createClient(['wss://localhost:8081'], userKeyPair);
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await client.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
rl.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
client.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Disconnected');
|
||||
rl.close();
|
||||
});
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
client.on(HotPocket.events.contractOutput, (r) => {
|
||||
r.outputs.forEach(output => {
|
||||
handleOutput(output, r.ledgerSeqNo);
|
||||
});
|
||||
});
|
||||
|
||||
const handleOutput = (output, ledgerSeqNo) => {
|
||||
if (output.type == "statResult") {
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${output.data}`);
|
||||
}
|
||||
else if (output.type == "dataResult") {
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${output.data}`);
|
||||
}
|
||||
else if (output.type == "error") {
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Error: ${output.error}`);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const input_pump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
let input;
|
||||
if (inp.startsWith("stat")) {
|
||||
// Get contract status by read request and contract input.
|
||||
// This output means
|
||||
// Read request - contract is deployed and excitable.
|
||||
// Contract input - node are in sync.
|
||||
if (inp.startsWith("stat contract")) {
|
||||
input = await client.submitContractInput(JSON.stringify({
|
||||
type: "stat"
|
||||
}));
|
||||
}
|
||||
else if (inp.startsWith("stat read")) {
|
||||
const output = await client.submitContractReadRequest(JSON.stringify({
|
||||
type: "stat"
|
||||
}));
|
||||
handleOutput(output, (await client.getStatus()).ledgerSeqNo);
|
||||
}
|
||||
else {
|
||||
const res = await client.getStatus();
|
||||
console.log(res);
|
||||
}
|
||||
}
|
||||
else if (inp.startsWith("set ")) {
|
||||
input = await client.submitContractInput(JSON.stringify({
|
||||
type: "set",
|
||||
data: inp.substr(4)
|
||||
}));
|
||||
}
|
||||
else if (inp.startsWith("get")) {
|
||||
|
||||
// Read only inputs can be sent as read requests, So it'll be quick.
|
||||
const output = await client.submitContractReadRequest(JSON.stringify({
|
||||
type: "get"
|
||||
}));
|
||||
|
||||
handleOutput(output, (await client.getStatus()).ledgerSeqNo);
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [set <data>] or [get] expected.")
|
||||
}
|
||||
|
||||
if (input) {
|
||||
const submission = await input.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
console.log("Submission failed. reason: " + submission.reason);
|
||||
}
|
||||
|
||||
input_pump();
|
||||
})
|
||||
}
|
||||
input_pump();
|
||||
}
|
||||
|
||||
clientApp();
|
||||
6
docker/code-templates/nodejs/starter-client/package.json
Normal file
6
docker/code-templates/nodejs/starter-client/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
index.js
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@vercel/ncc": "0.34.0",
|
||||
"hotpocket-nodejs-contract": "0.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
|
||||
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/hotpocket-nodejs-contract": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.6.tgz",
|
||||
"integrity": "sha512-Q52cE5lYGoVGvdUl3cDYixAh27QxzFF6JFaOm1+HoXU36dsKxZfDeJLGCfnDf4XcgEytM2mPE9STWUV+mdHqEg=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/ncc": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
|
||||
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A=="
|
||||
},
|
||||
"hotpocket-nodejs-contract": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.6.tgz",
|
||||
"integrity": "sha512-Q52cE5lYGoVGvdUl3cDYixAh27QxzFF6JFaOm1+HoXU36dsKxZfDeJLGCfnDf4XcgEytM2mPE9STWUV+mdHqEg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "0.5.10",
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,23 @@ export class _projname_ {
|
||||
sendOutput; // This function must be wired up by the caller.
|
||||
|
||||
async handleRequest(user, message, isReadOnly) {
|
||||
|
||||
// This sample application defines two simple messages. 'get' and 'set'.
|
||||
// It's up to the application to decide the structure and contents of messages.
|
||||
|
||||
if (message.type == 'get') {
|
||||
if (message.type == 'stat') {
|
||||
|
||||
// Send response as the status.
|
||||
await this.sendOutput(user, {
|
||||
type: 'statResult',
|
||||
data: 'Contract is online'
|
||||
})
|
||||
}
|
||||
else if (message.type == 'get') {
|
||||
|
||||
// Retrieved previously saved data and return to the user.
|
||||
const data = await this.getData();
|
||||
await this.sendOutput(user, {
|
||||
type: 'data_result',
|
||||
type: 'dataResult',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
@@ -26,6 +33,11 @@ export class _projname_ {
|
||||
if (!isReadOnly) {
|
||||
// Save the provided data into storage.
|
||||
await this.setData(message.data);
|
||||
|
||||
await this.sendOutput(user, {
|
||||
type: 'dataResult',
|
||||
data: 'success'
|
||||
})
|
||||
}
|
||||
else {
|
||||
await this.sendOutput(user, {
|
||||
|
||||
@@ -34,7 +34,7 @@ async function contract(ctx) {
|
||||
const buf = await ctx.users.read(input);
|
||||
|
||||
// Let's assume all data buffers for this contract are JSON.
|
||||
// In real-world apps, we need to gracefully fitler out invalid data formats for our contract.
|
||||
// In real-world apps, we need to gracefully filter out invalid data formats for our contract.
|
||||
const message = JSON.parse(buf);
|
||||
|
||||
// Pass the JSON message to our application logic component.
|
||||
|
||||
131
docker/code-templates/nodejs/upgrader-client/_projname_.js
Normal file
131
docker/code-templates/nodejs/upgrader-client/_projname_.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
const bson = require('bson');
|
||||
const path = require("path");
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
async function clientApp() {
|
||||
|
||||
const keyFile = 'user.key';
|
||||
|
||||
// Re-generate a user key pair for the client.
|
||||
if (process.argv[2] == 'generatekeys' || !fs.existsSync(keyFile)) {
|
||||
const newKeyPair = await HotPocket.generateKeys();
|
||||
const saveData = Buffer.from(newKeyPair.privateKey).toString('hex');
|
||||
fs.writeFileSync(keyFile, saveData);
|
||||
console.log('New key pair generated.');
|
||||
|
||||
if (process.argv[2] == 'generatekeys') {
|
||||
const pkhex = Buffer.from(newKeyPair.publicKey).toString('hex');
|
||||
console.log('My public key is: ' + pkhex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the key pair using saved private key data.
|
||||
const savedPrivateKeyHex = fs.readFileSync(keyFile).toString();
|
||||
const userKeyPair = await HotPocket.generateKeys(savedPrivateKeyHex);
|
||||
|
||||
const pkhex = Buffer.from(userKeyPair.publicKey).toString('hex');
|
||||
console.log('My public key is: ' + pkhex);
|
||||
|
||||
const ip = process.argv[2] || 'localhost';
|
||||
const port = process.argv[3] || '8081';
|
||||
const client = await HotPocket.createClient(
|
||||
['wss://' + ip + ':' + port],
|
||||
userKeyPair,
|
||||
{ protocol: HotPocket.protocols.bson }
|
||||
);
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await client.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
rl.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
client.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Disconnected');
|
||||
rl.close();
|
||||
});
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
client.on(HotPocket.events.contractOutput, (r) => {
|
||||
|
||||
r.outputs.forEach(output => {
|
||||
handleOutput(output, r.ledgerSeqNo);
|
||||
});
|
||||
});
|
||||
|
||||
const handleOutput = (output, ledgerSeqNo) => {
|
||||
// If bson.deserialize error occurred it'll be caught by this try catch.
|
||||
try {
|
||||
const result = bson.deserialize(output);
|
||||
if (result.type == "upgradeResult") {
|
||||
if (result.status == "ok")
|
||||
console.log(`(ledger:${ledgerSeqNo})>> ${result.status}`);
|
||||
else
|
||||
console.log(`(ledger:${ledgerSeqNo})>> Upgrade failed. reason: `, result.error ?? result.status);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const input_pump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
let input;
|
||||
if (inp.startsWith("upgrade ")) {
|
||||
|
||||
const filePath = inp.substr(8);
|
||||
const fileName = path.basename(filePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
const sizeKB = Math.round(fileContent.length / 1024);
|
||||
console.log("Uploading file " + fileName + " (" + sizeKB + " KB)");
|
||||
|
||||
input = await client.submitContractInput(bson.serialize({
|
||||
type: "upgrade",
|
||||
content: fileContent
|
||||
}));
|
||||
}
|
||||
else
|
||||
console.log("File not found");
|
||||
}
|
||||
else {
|
||||
console.log("Invalid command. [upgrade <local path>] expected.")
|
||||
}
|
||||
|
||||
if (input) {
|
||||
const submission = await input.submissionStatus;
|
||||
if (submission.status != "accepted")
|
||||
console.log("Submission failed. reason: " + submission.reason);
|
||||
}
|
||||
|
||||
input_pump();
|
||||
})
|
||||
}
|
||||
input_pump();
|
||||
}
|
||||
|
||||
clientApp();
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"dependencies": {
|
||||
"bson": "6.4.0",
|
||||
"hotpocket-js-client": "^0.5.6"
|
||||
}
|
||||
}
|
||||
6
docker/code-templates/nodejs/upgrader-contract/dist/hp.cfg.override
vendored
Normal file
6
docker/code-templates/nodejs/upgrader-contract/dist/hp.cfg.override
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"contract": {
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "index.js"
|
||||
}
|
||||
}
|
||||
15
docker/code-templates/nodejs/upgrader-contract/dist/post_exec.sh
vendored
Executable file
15
docker/code-templates/nodejs/upgrader-contract/dist/post_exec.sh
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install unzip, jq if not installed, because it's required for the upgrader.
|
||||
|
||||
if ! command -v unzip &>/dev/null; then
|
||||
echo "Installing unzip"
|
||||
apt-get update
|
||||
apt-get install -y unzip
|
||||
fi
|
||||
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "Installing unzip"
|
||||
apt-get update
|
||||
apt-get install -y jq
|
||||
fi
|
||||
14
docker/code-templates/nodejs/upgrader-contract/package.json
Normal file
14
docker/code-templates/nodejs/upgrader-contract/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "_projname_",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx ncc build src/contract.js -o dist",
|
||||
"build:prod": "npx ncc build src/contract.js --minify -o dist",
|
||||
"start": "npm run build && hpdevkit deploy dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson": "6.4.0",
|
||||
"hotpocket-nodejs-contract": "^0.7.3",
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
}
|
||||
193
docker/code-templates/nodejs/upgrader-contract/src/_projname_.js
Normal file
193
docker/code-templates/nodejs/upgrader-contract/src/_projname_.js
Normal file
@@ -0,0 +1,193 @@
|
||||
const fs = require('fs');
|
||||
const bson = require('bson');
|
||||
const child_process = require('child_process');
|
||||
|
||||
const BUNDLE = "bundle.zip";
|
||||
const HP_CFG_OVERRIDE = "hp.cfg.override";
|
||||
const CONTRACT_CFG = "contract.config";
|
||||
const INSTALL_SCRIPT = "install.sh"
|
||||
const PATH_CFG = "../patch.cfg"
|
||||
const BACKUP_PATH_CFG = "../patch.cfg.bk"
|
||||
const HP_POST_EXEC_SCRIPT = "post_exec.sh";
|
||||
const BACKUP = "backup";
|
||||
const POST_EXEC_ERR_FILE = "post_exec.err"
|
||||
|
||||
export class _projname_ {
|
||||
sendOutput; // This function must be wired up by the caller.
|
||||
postExecErrors = {};
|
||||
|
||||
// This function will be called in each contract execution.
|
||||
async handleContractExecution() {
|
||||
// Read and clear the error file.
|
||||
if (fs.existsSync(POST_EXEC_ERR_FILE)) {
|
||||
this.postExecErrors = fs.readFileSync(POST_EXEC_ERR_FILE);
|
||||
|
||||
// Clear the file after reading.
|
||||
fs.rmSync(POST_EXEC_ERR_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
// This function will be called per each user.
|
||||
async handleUserExecution(user) {
|
||||
// Handle if there are errors for the user.
|
||||
if (this.postExecErrors[user.publicKey]) {
|
||||
if (this.postExecErrors[user.publicKey] !== "success") {
|
||||
console.error(`Found post execution errors!`);
|
||||
|
||||
const error = this.postExecErrors[user.publicKey];
|
||||
delete this.postExecErrors[user.publicKey];
|
||||
|
||||
await this.sendOutput(user, {
|
||||
type: "upgradeResult",
|
||||
status: "error",
|
||||
error: error
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #upgradeContract(bundleContent, user, ctx) {
|
||||
// Backup all the files first.
|
||||
const backup = `${BACKUP}-${ctx.timestamp}`
|
||||
child_process.execSync(`mkdir -p ../${backup} && cp -r ./* ../${backup}/ && mv ../${backup} ./${backup}`);
|
||||
|
||||
console.log('Contract binaries backed up!');
|
||||
|
||||
try {
|
||||
fs.writeFileSync(BUNDLE, bundleContent, { mode: 0o644 });
|
||||
|
||||
// Install unzip if not exist.
|
||||
child_process.execSync(`/usr/bin/unzip -o ${BUNDLE} && rm -f ${BUNDLE}`);
|
||||
|
||||
console.log('New contract binaries extracted!');
|
||||
|
||||
let hpCfg = {};
|
||||
if (fs.existsSync(HP_CFG_OVERRIDE)) {
|
||||
hpCfg = JSON.parse(fs.readFileSync(HP_CFG_OVERRIDE).toString());
|
||||
child_process.execSync(`rm ${HP_CFG_OVERRIDE}`);
|
||||
}
|
||||
|
||||
if (hpCfg.contract) {
|
||||
let contractCfg = {};
|
||||
if (fs.existsSync(CONTRACT_CFG)) {
|
||||
contractCfg = JSON.parse(fs.readFileSync(CONTRACT_CFG).toString());
|
||||
}
|
||||
contractCfg = { ...contractCfg, ...hpCfg.contract };
|
||||
|
||||
fs.writeFileSync(CONTRACT_CFG, JSON.stringify(contractCfg, null, 2), { mode: 0o644 });
|
||||
|
||||
console.log('New contract configurations persisted!');
|
||||
}
|
||||
|
||||
// mesh section. (only known_peers section handled currently)
|
||||
if (hpCfg.mesh?.known_peers) {
|
||||
if (hpCfg.mesh.known_peers.length > 0) {
|
||||
ctx.updatePeers(hpCfg.mesh.known_peers);
|
||||
console.log('Peer list updated!');
|
||||
}
|
||||
}
|
||||
|
||||
const command = `#!/bin/bash
|
||||
|
||||
# Backup patch config.
|
||||
cp ${PATH_CFG} ${BACKUP_PATH_CFG}
|
||||
|
||||
function print_err() {
|
||||
local error=$1
|
||||
log=$(jq . ${POST_EXEC_ERR_FILE})
|
||||
for key in $(jq -c 'keys[]' <<<$log); do
|
||||
log=$(jq ".$key = \"$error\"" <<<$log)
|
||||
done
|
||||
echo $log >${POST_EXEC_ERR_FILE}
|
||||
}
|
||||
|
||||
function rollback() {
|
||||
# Restore patch.cfg if backup exists
|
||||
[ -f ${BACKUP_PATH_CFG} ] && mv ${BACKUP_PATH_CFG} ${PATH_CFG}
|
||||
return 0
|
||||
}
|
||||
|
||||
function upgrade() {
|
||||
[ -f "${CONTRACT_CFG}" ] && jq -s '.[0] * .[1]' ${PATH_CFG} ${CONTRACT_CFG} > ../tmp.cfg && mv ../tmp.cfg ${PATH_CFG}
|
||||
|
||||
if [ -f "${INSTALL_SCRIPT}" ]; then
|
||||
echo "${INSTALL_SCRIPT} found. Executing..."
|
||||
|
||||
chmod +x ${INSTALL_SCRIPT}
|
||||
./${INSTALL_SCRIPT}
|
||||
installcode=$?
|
||||
|
||||
rm ${INSTALL_SCRIPT}
|
||||
|
||||
if [ "$installcode" -eq "0" ]; then
|
||||
echo "${INSTALL_SCRIPT} executed successfully."
|
||||
return 0
|
||||
else
|
||||
echo "${INSTALL_SCRIPT} ended with exit code:$installcode"
|
||||
print_err "InstallScriptFailed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
upgrade
|
||||
upgradecode=$?
|
||||
|
||||
if [ "$upgradecode" -eq "0" ]; then
|
||||
# We have upgraded the contract successfully.
|
||||
echo "Upgrade successful."
|
||||
else
|
||||
echo "Upgrade failed. Rolling back."
|
||||
rollback
|
||||
fi
|
||||
|
||||
finalcode=$?
|
||||
exit $finalcode`;
|
||||
|
||||
// Create file to write post execution errors.
|
||||
this.postExecErrors[user.publicKey] = 'success';
|
||||
fs.writeFileSync(POST_EXEC_ERR_FILE, JSON.stringify(this.postExecErrors, null, 2), { mode: 0o644 });
|
||||
console.log('Generated error log file!');
|
||||
|
||||
fs.writeFileSync(HP_POST_EXEC_SCRIPT, command, { mode: 0o777 });
|
||||
console.log('Generated post execution script!');
|
||||
|
||||
await user.send(bson.serialize({
|
||||
type: "upgradeResult",
|
||||
status: "ok"
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
|
||||
child_process.execSync(`cp -r ${backup}/* ./ && rm -r ${backup}`);
|
||||
|
||||
await user.send(bson.serialize({
|
||||
type: "upgradeResult",
|
||||
status: "error",
|
||||
error: e
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// This function will be called per each user input.
|
||||
async handleRequest(user, msg, isReadOnly, ctx) {
|
||||
// This sample application defines simple file operations.
|
||||
// It's up to the application to decide the structure and contents of messages.
|
||||
|
||||
if (msg.type == "upgrade") {
|
||||
|
||||
if (isReadOnly) {
|
||||
await this.sendOutput(user, {
|
||||
type: "upgradeResult",
|
||||
status: "error",
|
||||
error: 'Contract upgrade is not supported in readonly mode'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.#upgradeContract(msg.content.buffer, user, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
const HotPocket = require('hotpocket-nodejs-contract');
|
||||
const bson = require('bson');
|
||||
const { _projname_ } = require('./_projname_');
|
||||
|
||||
// HotPocket smart contract is defined as a function which takes the HotPocket contract context as an argument.
|
||||
// This function gets invoked every consensus round and whenever a user sends a out-of-concensus read-request.
|
||||
async function contract(ctx) {
|
||||
|
||||
// Create our application logic component.
|
||||
// This pattern allows us to test the application logic independently of HotPocket.
|
||||
const app = new _projname_();
|
||||
|
||||
// Wire-up output emissions from the application before we pass user inputs to it.
|
||||
app.sendOutput = async (user, output) => {
|
||||
await user.send(output)
|
||||
}
|
||||
|
||||
// In 'readonly' mode, nothing our contract does will get persisted on the ledger. The benefit is
|
||||
// readonly messages gets processed much faster due to not being subjected to consensus.
|
||||
// We should only use readonly mode for returning/replying data for the requesting user.
|
||||
//
|
||||
// In consensus mode (NOT read-only), we can do anything like persisting to data storage and/or
|
||||
// sending data to any connected user at the time. Everything will get subjected to consensus so
|
||||
// there is a time-penalty.
|
||||
const isReadOnly = ctx.readonly;
|
||||
|
||||
// This function is executed per each contract round.
|
||||
await app.handleContractExecution();
|
||||
|
||||
// Process user inputs.
|
||||
// Loop through list of users who have sent us inputs.
|
||||
for (const user of ctx.users.list()) {
|
||||
// This function is executed per each user.
|
||||
await app.handleUserExecution(user);
|
||||
|
||||
// Loop through inputs sent by each user.
|
||||
for (const input of user.inputs) {
|
||||
|
||||
// Read the data buffer sent by user (this can be any kind of data like string, json or binary data).
|
||||
const buf = await ctx.users.read(input);
|
||||
|
||||
// Let's assume all data buffers for this contract are binary.
|
||||
// In real-world apps, we need to gracefully filter out invalid data formats for our contract.
|
||||
const msg = bson.deserialize(buf);
|
||||
|
||||
// Pass the JSON message to our application logic component.
|
||||
await app.handleRequest(user, msg, isReadOnly, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hpc = new HotPocket.Contract();
|
||||
hpc.init(contract);
|
||||
@@ -8,6 +8,7 @@ network=$NETWORK
|
||||
container_prefix=$CONTAINER_PREFIX
|
||||
volume_mount=$VOLUME_MOUNT
|
||||
bundle_mount=$BUNDLE_MOUNT
|
||||
disparate_dir=$DISPARATE_DIR
|
||||
hotpocket_image=$HOTPOCKET_IMAGE
|
||||
config_overrides_file=$CONFIG_OVERRIDES_FILE
|
||||
user_port_begin=$HP_USER_PORT_BEGIN
|
||||
@@ -274,9 +275,13 @@ function attach_logs {
|
||||
function sync_instance {
|
||||
local node=$1
|
||||
local contract_dir=$(contract_dir_mount_path $node)
|
||||
rm -rf $contract_dir/ledger_fs/* $contract_dir/contract_fs/*
|
||||
rm -rf $contract_dir/ledger_fs/* $contract_dir/contract_fs/*
|
||||
mkdir -p $contract_dir/contract_fs/seed
|
||||
cp -r $bundle_mount $contract_dir/contract_fs/seed/state
|
||||
[ -d $contract_dir/contract_fs/seed/state/$disparate_dir ] && rm -r $contract_dir/contract_fs/seed/state/$disparate_dir
|
||||
|
||||
# Copy non-syncable files if exist.
|
||||
[ -d "$bundle_mount/$disparate_dir/$i" ] && cp -r "$bundle_mount/$disparate_dir/$i/." "$contract_dir/contract_fs/seed/"
|
||||
|
||||
# Merge contract config overrides.
|
||||
local cfg_file=$contract_dir/cfg/hp.cfg
|
||||
|
||||
@@ -18,8 +18,8 @@ fi
|
||||
|
||||
[ ! -d $templates_dir/$platform ] && echo "Invalid platform '$platform' specified." && exit 1
|
||||
[ ! -d $templates_dir/$platform/$apptype ] && echo "Invalid application type '$apptype' specified." && exit 1
|
||||
if ! [[ "$projname" =~ ^[a-z][a-z0-9_-]*$ ]]; then
|
||||
echo "Invalid project name. Must be lowercase. Must start with a letter. Can only contain letters, numbers, dash and underscore."
|
||||
if ! [[ "$projname" =~ ^[a-z][a-z0-9_]*$ ]]; then
|
||||
echo "Invalid project name. Must be lowercase. Must start with a letter. Can only contain letters, numbers, and underscore."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
26
docker/scripts/templates.sh
Normal file
26
docker/scripts/templates.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
cmd=$1
|
||||
|
||||
templates_dir="/code-templates"
|
||||
usage="Usage:
|
||||
\nlist <platform>"
|
||||
|
||||
if [ "$cmd" = "list" ]; then
|
||||
platform=$2
|
||||
|
||||
if [ ! -z $platform ]; then
|
||||
platforms=("$templates_dir/$platform")
|
||||
else
|
||||
platforms="$templates_dir/*"
|
||||
fi
|
||||
|
||||
for p in $platforms; do
|
||||
[ ! -d "$p" ] && echo "There are no templates for platform: ${p##*/}." && exit 0
|
||||
echo "$(tput bold)PLATFORM: ${p##*/}$(tput sgr0)"
|
||||
for t in $(ls -1 "$p"); do
|
||||
echo " $t"
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -8,6 +8,9 @@ const appenv = {
|
||||
instanceImage: process.env.HP_INSTANCE_IMAGE || 'evernode/hotpocket:0.6.4-ubt.20.04-njs.20',
|
||||
hpUserPortBegin: process.env.HP_USER_PORT_BEGIN || 8081,
|
||||
hpPeerPortBegin: process.env.HP_PEER_PORT_BEGIN || 22861,
|
||||
network: process.env.HP_EV_NETWORK || 'mainnet',
|
||||
signerWeight: process.env.HP_MULTI_SIGNER_WEIGHT || 1,
|
||||
signerQuorum: process.env.HP_MULTI_SIGNER_QUORUM || 0.8
|
||||
}
|
||||
|
||||
Object.freeze(appenv);
|
||||
|
||||
@@ -8,6 +8,11 @@ program
|
||||
.description('Display the hpdevkit version.')
|
||||
.action(commands.version);
|
||||
|
||||
program
|
||||
.command('list [platform]')
|
||||
.description('Lists existing templates in the specified platform. Lists all templates in the all platforms if unspecified.')
|
||||
.action(commands.list);
|
||||
|
||||
program
|
||||
.command('gen <platform> <app-type> <project-name>')
|
||||
.description('Generate HotPocket application development projects.')
|
||||
@@ -15,6 +20,9 @@ program
|
||||
|
||||
program
|
||||
.command('deploy <contract-path>')
|
||||
.option('-m, --multi-sig [multi-sig]', 'Multi signing enabled.')
|
||||
.option('-a, --master-addr [master-addr]', 'Master address for multi signing.')
|
||||
.option('-s, --master-sec [master-sec]', 'Master secret for multi signing.')
|
||||
.description('Deploy the specified directory to a HotPocket cluster.')
|
||||
.action(commands.deploy);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const appenv = require('../appenv');
|
||||
const kp = require('ripple-keypairs');
|
||||
const evernode = require("evernode-js-client")
|
||||
const { exec } = require('./child-proc');
|
||||
const {
|
||||
CONSTANTS,
|
||||
@@ -28,6 +30,21 @@ function version() {
|
||||
error(`\n${CONSTANTS.npmPackageName} is not installed.`);
|
||||
}
|
||||
|
||||
function list(platform) {
|
||||
info("List templates\n");
|
||||
|
||||
try {
|
||||
runOnNewContainer(CONSTANTS.codegenContainerName, null, null, null, null, platform ? `list ${platform}` : 'list', 'templates');
|
||||
}
|
||||
catch (e) {
|
||||
error(`Listing templates failed.`);
|
||||
}
|
||||
finally {
|
||||
if (isExists(CONSTANTS.codegenContainerName))
|
||||
exec(`docker rm ${CONSTANTS.codegenContainerName}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function codeGen(platform, apptype, projName) {
|
||||
info("Code generator\n");
|
||||
|
||||
@@ -50,9 +67,14 @@ function codeGen(platform, apptype, projName) {
|
||||
}
|
||||
}
|
||||
|
||||
function deploy(contractPath) {
|
||||
async function deploy(contractPath, options) {
|
||||
info(`command: deploy (cluster: ${appenv.cluster})`);
|
||||
|
||||
if (options.multiSig && !options.masterSec) {
|
||||
error('Master secret is required to setup multi signing!');
|
||||
return;
|
||||
}
|
||||
|
||||
initializeDeploymentCluster();
|
||||
|
||||
// If copying a directory, delete target bundle directory. If not create empty target bundle directory to copy a file.
|
||||
@@ -63,6 +85,56 @@ function deploy(contractPath) {
|
||||
executeOnManagementContainer(prepareBundleDir);
|
||||
exec(`docker cp ${contractPath} "${CONSTANTS.managementContainerName}:${CONSTANTS.bundleMount}"`);
|
||||
|
||||
// Prepare signers if multisig specified
|
||||
if (options.multiSig) {
|
||||
if (!options.masterAddr) {
|
||||
const keypair = kp.deriveKeypair(options.masterSec);
|
||||
options.masterAddr = kp.deriveAddress(keypair.publicKey);
|
||||
}
|
||||
|
||||
let signers = [];
|
||||
for (let i = 0; i < appenv.clusterSize; i++) {
|
||||
const nodeSecret = kp.generateSeed({ algorithm: "ecdsa-secp256k1" });
|
||||
const keypair = kp.deriveKeypair(nodeSecret);
|
||||
const signerInfo = {
|
||||
account: kp.deriveAddress(keypair.publicKey),
|
||||
secret: nodeSecret,
|
||||
weight: appenv.signerWeight
|
||||
};
|
||||
|
||||
signers.push(signerInfo);
|
||||
|
||||
const disparatePath = `${CONSTANTS.bundleMount}/${CONSTANTS.disparateDir}/${i + 1}`;
|
||||
executeOnManagementContainer(`mkdir -p ${disparatePath} && echo '${JSON.stringify(signerInfo, null, 2).replace(/"/g, '\\"')}' > ${disparatePath}/${options.masterAddr}.key`);
|
||||
}
|
||||
|
||||
await evernode.Defaults.useNetwork(appenv.network);
|
||||
const xrplApi = new evernode.XrplApi(null);
|
||||
evernode.Defaults.set({
|
||||
xrplApi: xrplApi
|
||||
});
|
||||
await xrplApi.connect();
|
||||
const xrplAcc = new evernode.XrplAccount(options.masterAddr, options.masterSec);
|
||||
|
||||
try {
|
||||
const totalWeights = signers.reduce((sum, x) => sum + x.weight, 0);
|
||||
await xrplAcc.setSignerList(signers.map(s => {
|
||||
return {
|
||||
account: s.account,
|
||||
weight: s.weight
|
||||
};
|
||||
}), { signerQuorum: Math.floor(totalWeights * appenv.signerQuorum) });
|
||||
await xrplApi.disconnect();
|
||||
}
|
||||
catch (e) {
|
||||
error('Error occurred while preparing the signer list', e);
|
||||
await xrplApi.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Multi signer setup for ${options.masterAddr} completed!`);
|
||||
}
|
||||
|
||||
// Sync contract bundle to all instance directories in the cluster.
|
||||
executeOnManagementContainer('cluster stop ; cluster sync ; cluster start');
|
||||
|
||||
@@ -157,6 +229,7 @@ function uninstall() {
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
list,
|
||||
codeGen,
|
||||
deploy,
|
||||
clean,
|
||||
|
||||
@@ -11,6 +11,7 @@ const CONSTANTS = {
|
||||
network: `${GLOBAL_PREFIX}_${appenv.cluster}_net`,
|
||||
containerPrefix: `${GLOBAL_PREFIX}_${appenv.cluster}_node`,
|
||||
bundleMount: `${GLOBAL_PREFIX}_vol/contract_bundle`,
|
||||
disparateDir: `disparate`,
|
||||
managementContainerName: `${GLOBAL_PREFIX}_${appenv.cluster}_deploymgr`,
|
||||
confOverrideFile: "hp.cfg.override",
|
||||
codegenOutputDir: "/codegen-output",
|
||||
@@ -49,7 +50,7 @@ function runOnNewContainer(name, detached, autoRemove, mountSock, mountVolume, e
|
||||
command += ` --restart ${restart}`;
|
||||
|
||||
command += ` -e CLUSTER=${appenv.cluster} -e CLUSTER_SIZE=${appenv.clusterSize} -e DEFAULT_NODE=${appenv.defaultNode} -e VOLUME=${CONSTANTS.volume} -e NETWORK=${CONSTANTS.network}`;
|
||||
command += ` -e CONTAINER_PREFIX=${CONSTANTS.containerPrefix} -e VOLUME_MOUNT=${CONSTANTS.volumeMount} -e BUNDLE_MOUNT=${CONSTANTS.bundleMount} -e HOTPOCKET_IMAGE=${appenv.instanceImage}`;
|
||||
command += ` -e CONTAINER_PREFIX=${CONSTANTS.containerPrefix} -e VOLUME_MOUNT=${CONSTANTS.volumeMount} -e BUNDLE_MOUNT=${CONSTANTS.bundleMount} -e DISPARATE_DIR=${CONSTANTS.disparateDir} -e HOTPOCKET_IMAGE=${appenv.instanceImage}`;
|
||||
command += ` -e CONFIG_OVERRIDES_FILE=${CONSTANTS.confOverrideFile} -e CODEGEN_OUTPUT=${CONSTANTS.codegenOutputDir}`;
|
||||
command += ` -e HP_USER_PORT_BEGIN=${appenv.hpUserPortBegin} -e HP_PEER_PORT_BEGIN=${appenv.hpPeerPortBegin}`;
|
||||
|
||||
|
||||
2390
npm/package-lock.json
generated
2390
npm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hpdevkit",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.6",
|
||||
"license": "SEE LICENSE IN https://raw.githubusercontent.com/EvernodeXRPL/evernode-resources/main/license/evernode-license.pdf",
|
||||
"description": "Developer toolkit for HotPocket smart contract development",
|
||||
"scripts": {
|
||||
@@ -18,7 +18,8 @@
|
||||
],
|
||||
"homepage": "https://github.com/HotPocketDev/evernode-sdk",
|
||||
"dependencies": {
|
||||
"commander": "9.4.0"
|
||||
"commander": "9.4.0",
|
||||
"evernode-js-client": "0.6.43"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.3.0"
|
||||
|
||||
Reference in New Issue
Block a user