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:
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);
|
||||
Reference in New Issue
Block a user