mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
Added smart contract upgrade support. (#250)
Supports smart contract self-upgrades by allowing "post_exec.sh" script to be executed after consensus contract execution.
This commit is contained in:
@@ -11,13 +11,18 @@
|
||||
#include <sys/stat.h>
|
||||
#include "json.h"
|
||||
|
||||
// Private constants.
|
||||
#define __HP_MMAP_BLOCK_SIZE 4096
|
||||
#define __HP_MMAP_BLOCK_ALIGN(x) (((x) + ((off_t)(__HP_MMAP_BLOCK_SIZE)-1)) & ~((off_t)(__HP_MMAP_BLOCK_SIZE)-1))
|
||||
#define __HP_STREAM_MSG_HEADER_SIZE 4
|
||||
#define __HP_SEQPKT_MAX_SIZE 131072 // 128KB to support SEQ_PACKET sockets.
|
||||
const char *__HP_PATCH_FILE_PATH = "../patch.cfg";
|
||||
|
||||
// Public constants.
|
||||
#define HP_NPL_MSG_MAX_SIZE __HP_SEQPKT_MAX_SIZE
|
||||
#define HP_KEY_SIZE 66 // Hex pubkey size. (64 char key + 2 chars for key type prfix)
|
||||
#define HP_HASH_SIZE 64
|
||||
const char *HP_POST_EXEC_SCRIPT_NAME = "post_exec.sh";
|
||||
|
||||
#define __HP_ASSIGN_STRING(dest, elem) \
|
||||
{ \
|
||||
@@ -174,8 +179,6 @@ struct __hp_contract
|
||||
size_t user_inmap_size;
|
||||
};
|
||||
|
||||
const char *PATCH_FILE_PATH = "../patch.cfg";
|
||||
|
||||
int hp_init_contract();
|
||||
int hp_deinit_contract();
|
||||
const struct hp_contract_context *hp_get_context();
|
||||
@@ -438,7 +441,7 @@ int hp_read_npl_msg(void *msg_buf, char *pubkey_buf, const int timeout)
|
||||
*/
|
||||
struct hp_config *hp_get_config()
|
||||
{
|
||||
const int fd = open(PATCH_FILE_PATH, O_RDONLY);
|
||||
const int fd = open(__HP_PATCH_FILE_PATH, O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
fprintf(stderr, "Error opening patch.cfg file.\n");
|
||||
@@ -512,7 +515,7 @@ int hp_update_config(const struct hp_config *config)
|
||||
config->round_limits.proc_cpu_seconds < 0 || config->round_limits.proc_mem_bytes < 0 || config->round_limits.proc_ofd_count < 0)
|
||||
__HP_UPDATE_CONFIG_ERROR("Invalid round limits.");
|
||||
|
||||
const int fd = open(PATCH_FILE_PATH, O_RDWR);
|
||||
const int fd = open(__HP_PATCH_FILE_PATH, O_RDWR);
|
||||
if (fd == -1)
|
||||
__HP_UPDATE_CONFIG_ERROR("Error opening patch.cfg file.");
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const clientProtocols = {
|
||||
Object.freeze(clientProtocols);
|
||||
|
||||
const PATCH_CONFIG_PATH = "../patch.cfg";
|
||||
const POST_EXEC_SCRIPT_NAME = "post_exec.sh";
|
||||
|
||||
class HotPocketContract {
|
||||
|
||||
@@ -399,5 +400,6 @@ const errHandler = (err) => console.log(err);
|
||||
|
||||
module.exports = {
|
||||
Contract: HotPocketContract,
|
||||
clientProtocols
|
||||
clientProtocols,
|
||||
POST_EXEC_SCRIPT_NAME,
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace sc
|
||||
constexpr int FILE_PERMS = 0644;
|
||||
constexpr const char *STDOUT_LOG = ".stdout.log";
|
||||
constexpr const char *STDERR_LOG = ".stderr.log";
|
||||
constexpr const char *POST_EXEC_SCRIPT = "post_exec.sh";
|
||||
|
||||
constexpr uint32_t CONTRACT_FS_ID = 0;
|
||||
|
||||
@@ -51,6 +52,7 @@ namespace sc
|
||||
contract_server.deinit();
|
||||
contract_fs.deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the contract process and passes the specified context arguments.
|
||||
* @return 0 on successful process creation. -1 on failure or contract process is already running.
|
||||
@@ -61,6 +63,24 @@ namespace sc
|
||||
if (start_hpfs_session(ctx) == -1)
|
||||
return -1;
|
||||
|
||||
// Set contract working directory.
|
||||
ctx.working_dir = contract_fs.physical_path(ctx.args.hpfs_session_name, STATE_DIR_PATH);
|
||||
|
||||
// Setup contract output log file paths.
|
||||
if (conf::cfg.contract.log_output)
|
||||
{
|
||||
const time_t epoch = util::get_epoch_milliseconds() / 1000;
|
||||
std::stringstream now_ss;
|
||||
now_ss << std::put_time(std::localtime(&epoch), "%Y%m%dT%H%M%S");
|
||||
const std::string now = now_ss.str();
|
||||
|
||||
// For consensus execution, we keep appending logs to the same out/err files.
|
||||
// For read request executions, independent log files are created based on read request session names.
|
||||
const std::string prefix = ctx.args.readonly ? (ctx.args.hpfs_session_name + "_" + now) : ctx.args.hpfs_session_name;
|
||||
ctx.stdout_file = conf::ctx.contract_log_dir + "/" + prefix + STDOUT_LOG;
|
||||
ctx.stderr_file = conf::ctx.contract_log_dir + "/" + prefix + STDERR_LOG;
|
||||
}
|
||||
|
||||
// Create the IO sockets for users, control channel and npl.
|
||||
// (Note: User socket will only be used for contract output only. For feeding user inputs we are using a memfd.)
|
||||
if (create_iosockets_for_fdmap(ctx.user_fds, ctx.args.userbufs) == -1 ||
|
||||
@@ -133,8 +153,7 @@ namespace sc
|
||||
execv_args[j] = conf::cfg.contract.runtime_binexec_args[i].data();
|
||||
execv_args[len - 1] = NULL;
|
||||
|
||||
const std::string current_dir = contract_fs.physical_path(ctx.args.hpfs_session_name, STATE_DIR_PATH);
|
||||
chdir(current_dir.c_str());
|
||||
chdir(ctx.working_dir.c_str());
|
||||
|
||||
if (create_contract_log_files(ctx) == -1)
|
||||
{
|
||||
@@ -154,6 +173,10 @@ namespace sc
|
||||
|
||||
cleanup_fds(ctx);
|
||||
|
||||
// If the consensus contact finished executing successfully, run the post-exec.sh script if it exists.
|
||||
if (ctx.exit_success && !ctx.args.readonly && run_post_exec_script(ctx) == -1)
|
||||
ret = -1;
|
||||
|
||||
if (stop_hpfs_session(ctx) == -1)
|
||||
ret = -1;
|
||||
|
||||
@@ -214,12 +237,13 @@ namespace sc
|
||||
|
||||
if (WIFEXITED(scstatus))
|
||||
{
|
||||
ctx.exit_success = true;
|
||||
LOG_DEBUG << "Contract process" << (ctx.args.readonly ? " (rdonly)" : "") << " ended normally.";
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Contract process" << (ctx.args.readonly ? " (rdonly)" : "") << " ended prematurely with code " << WEXITSTATUS(scstatus);
|
||||
LOG_ERROR << "Contract process" << (ctx.args.readonly ? " (rdonly)" : "") << " ended prematurely. Exit code " << WEXITSTATUS(scstatus);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -438,6 +462,53 @@ namespace sc
|
||||
LOG_DEBUG << "Contract monitor stopped";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the contract post execution script if exists.
|
||||
*/
|
||||
int run_post_exec_script(const execution_context &ctx)
|
||||
{
|
||||
// Check whether the post-exec script exists within contract state dir.
|
||||
const std::string script_path = ctx.working_dir + "/" + POST_EXEC_SCRIPT;
|
||||
if (!util::is_file_exists(script_path.c_str()))
|
||||
return 0;
|
||||
|
||||
LOG_INFO << "Running post-exec script...";
|
||||
|
||||
const std::string log_redirect = conf::cfg.contract.log_output ? (" >>" + ctx.stdout_file + " 2>>" + ctx.stderr_file + " ") : "";
|
||||
|
||||
// We set current working dir and pass lcl as command line arg to the script.
|
||||
const std::string command = "(cd " + ctx.working_dir + " && ./" + POST_EXEC_SCRIPT + " " + ctx.args.lcl + log_redirect + ")";
|
||||
|
||||
const int ret = system(command.c_str());
|
||||
if (ret == -1)
|
||||
{
|
||||
LOG_ERROR << errno << ": Could not run post-exec script " << script_path;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the script returns a code 0 or 3 to 125 we consider it a successful execition.
|
||||
// If the script returns code 0, we consider script lifetime is over and delete the file. Otherwise we retain the file.
|
||||
const int exit_code = WEXITSTATUS(ret);
|
||||
if (WIFEXITED(ret) && (exit_code == 0 || (exit_code > 2 && exit_code < 126)))
|
||||
{
|
||||
LOG_INFO << "Post-exec script executed successfully. Exit code:" << exit_code;
|
||||
// Exit code 0 means post-execution script can be deleted.
|
||||
if (exit_code == 0 && util::remove_file(script_path) == -1)
|
||||
{
|
||||
LOG_ERROR << errno << ": Error deleting post-exec script after execution.";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Post-exec script ended prematurely. Exit code:" << exit_code;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes any hp input messages to the contract.
|
||||
*/
|
||||
@@ -729,29 +800,18 @@ namespace sc
|
||||
if (!conf::cfg.contract.log_output)
|
||||
return 0;
|
||||
|
||||
const time_t epoch = util::get_epoch_milliseconds() / 1000;
|
||||
std::stringstream now_ss;
|
||||
now_ss << std::put_time(std::localtime(&epoch), "%Y%m%dT%H%M%S");
|
||||
const std::string now = now_ss.str();
|
||||
|
||||
// For consensus execution, we keep appending logs to the same out/err files.
|
||||
// For read request executions, independent log files are created based on read request session names.
|
||||
const std::string prefix = ctx.args.readonly ? (ctx.args.hpfs_session_name + "_" + now) : ctx.args.hpfs_session_name;
|
||||
const std::string stdout_file = conf::ctx.contract_log_dir + "/" + prefix + STDOUT_LOG;
|
||||
const std::string stderr_file = conf::ctx.contract_log_dir + "/" + prefix + STDERR_LOG;
|
||||
|
||||
const int outfd = open(stdout_file.data(), O_CREAT | O_WRONLY | O_APPEND, FILE_PERMS);
|
||||
const int outfd = open(ctx.stdout_file.data(), O_CREAT | O_WRONLY | O_APPEND, FILE_PERMS);
|
||||
if (outfd == -1)
|
||||
{
|
||||
std::cerr << errno << ": Error opening " << stdout_file << "\n";
|
||||
std::cerr << errno << ": Error opening " << ctx.stdout_file << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int errfd = open(stderr_file.data(), O_CREAT | O_WRONLY | O_APPEND, FILE_PERMS);
|
||||
const int errfd = open(ctx.stderr_file.data(), O_CREAT | O_WRONLY | O_APPEND, FILE_PERMS);
|
||||
if (errfd == -1)
|
||||
{
|
||||
close(outfd);
|
||||
std::cerr << errno << ": Error opening " << stderr_file << "\n";
|
||||
std::cerr << errno << ": Error opening " << ctx.stderr_file << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -759,7 +819,7 @@ namespace sc
|
||||
// to mark the start of each execution.
|
||||
if (!ctx.args.readonly)
|
||||
{
|
||||
const std::string header = "Execution lcl " + ctx.args.lcl + " on " + now + "\n";
|
||||
const std::string header = "Execution lcl " + ctx.args.lcl + "\n";
|
||||
if (write(outfd, header.data(), header.size()) == -1 ||
|
||||
write(errfd, header.data(), header.size()) == -1)
|
||||
{
|
||||
|
||||
@@ -124,10 +124,20 @@ namespace sc
|
||||
|
||||
size_t total_npl_output_size = 0;
|
||||
|
||||
// The path set as contract working directory.
|
||||
std::string working_dir;
|
||||
|
||||
// Full paths to std out/err log files for the contract execution.
|
||||
std::string stdout_file;
|
||||
std::string stderr_file;
|
||||
|
||||
// Indicates that the contract has sent termination control message.
|
||||
bool termination_signaled = false;
|
||||
|
||||
// Indicates that the deinit procedure has begun.
|
||||
// Indicates whether the contract exited normally without any errors.
|
||||
bool exit_success = false;
|
||||
|
||||
// Indicates that the hpcore deinit procedure has begun.
|
||||
bool is_shutting_down = false;
|
||||
|
||||
execution_context(util::buffer_store &user_input_store) : args(user_input_store)
|
||||
@@ -158,6 +168,8 @@ namespace sc
|
||||
|
||||
void contract_monitor_loop(execution_context &ctx);
|
||||
|
||||
int run_post_exec_script(const execution_context &ctx);
|
||||
|
||||
int write_control_inputs(execution_context &ctx);
|
||||
|
||||
int write_npl_messages(execution_context &ctx);
|
||||
|
||||
BIN
test/bin/hpfs
BIN
test/bin/hpfs
Binary file not shown.
@@ -25,7 +25,7 @@ if [ "$CONTRACT" = "cecho" ]; then # C echo contract
|
||||
gcc echo_contract.c -o echo_contract
|
||||
popd > /dev/null 2>&1
|
||||
copyfiles="$hpcore/examples/c_contract/echo_contract"
|
||||
binary="/contract/bin/echo_contract"
|
||||
binary="echo_contract"
|
||||
|
||||
elif [ "$CONTRACT" = "nodefile" ]; then # nodejs file contract (uses BSON protocol)
|
||||
echo "Using nodejs file contract."
|
||||
@@ -34,13 +34,13 @@ elif [ "$CONTRACT" = "nodefile" ]; then # nodejs file contract (uses BSON protoc
|
||||
popd > /dev/null 2>&1
|
||||
copyfiles="$hpcore/examples/nodejs_contract/{node_modules,package.json,hp-contract-lib.js,file_contract.js}"
|
||||
binary="/usr/local/bin/node"
|
||||
binargs="/contract/bin/file_contract.js"
|
||||
binargs="file_contract.js"
|
||||
|
||||
else # nodejs echo contract (default)
|
||||
echo "Using nodejs echo contract."
|
||||
copyfiles="$hpcore/examples/nodejs_contract/{package.json,hp-contract-lib.js,echo_contract.js}"
|
||||
binary="/usr/local/bin/node"
|
||||
binargs="/contract/bin/echo_contract.js"
|
||||
binargs="echo_contract.js"
|
||||
fi
|
||||
|
||||
if [ "$loglevel" = "" ]; then
|
||||
@@ -123,9 +123,8 @@ do
|
||||
popd > /dev/null 2>&1
|
||||
|
||||
# Copy the contract files and appbill.
|
||||
mkdir ./node$n/bin
|
||||
eval "cp -r $copyfiles ./node$n/bin/"
|
||||
cp ../bin/appbill ./node$n/bin/
|
||||
eval "cp -r $copyfiles ./node$n/contract_fs/seed/state/"
|
||||
cp ../bin/appbill ./node$n/contract_fs/seed/state/
|
||||
done
|
||||
|
||||
# Function to generate JSON array string while skiping a given index.
|
||||
|
||||
Reference in New Issue
Block a user