From 5d509df5880b6480463677ba91e41e6acd0f2503 Mon Sep 17 00:00:00 2001 From: cryi Date: Tue, 9 Apr 2024 22:04:58 +0200 Subject: [PATCH] initial --- .gitattributes | 1 + .github/workflows/release.yml | 80 +++ .gitignore | 5 + bin/.gitkeep | 0 build/amalg.lua | 749 +++++++++++++++++++++++++ build/build.lua | 53 ++ configuration/accounts.hjson | 20 + configuration/genesis.hjson | 14 + configuration/init-config-args.hjson | 8 + configuration/protocols/Proxford.hjson | 13 + configuration/sandbox-parameters.hjson | 110 ++++ configuration/services/baker.hjson | 17 + configuration/services/node.hjson | 20 + containers/minimal/Containerfile | 18 + containers/tezos/Containerfile | 33 ++ src/box/constants.lua | 6 + src/box/context.lua | 147 +++++ src/box/core.lua | 168 ++++++ src/box/env.lua | 56 ++ src/box/octez.lua | 144 +++++ src/tezbox.lua | 36 ++ src/util/args.lua | 31 + src/util/log.lua | 40 ++ src/version-info.lua | 5 + tests/all.lua | 0 25 files changed, 1774 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 bin/.gitkeep create mode 100644 build/amalg.lua create mode 100644 build/build.lua create mode 100644 configuration/accounts.hjson create mode 100644 configuration/genesis.hjson create mode 100644 configuration/init-config-args.hjson create mode 100644 configuration/protocols/Proxford.hjson create mode 100644 configuration/sandbox-parameters.hjson create mode 100644 configuration/services/baker.hjson create mode 100644 configuration/services/node.hjson create mode 100644 containers/minimal/Containerfile create mode 100644 containers/tezos/Containerfile create mode 100644 src/box/constants.lua create mode 100644 src/box/context.lua create mode 100644 src/box/core.lua create mode 100644 src/box/env.lua create mode 100644 src/box/octez.lua create mode 100644 src/tezbox.lua create mode 100644 src/util/args.lua create mode 100644 src/util/log.lua create mode 100644 src/version-info.lua create mode 100644 tests/all.lua diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..881dea7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: release + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + outputs: + NEEDS_RELEASE: ${{ steps.prep.outputs.NEEDS_RELEASE }} + VERSION: ${{ steps.prep.outputs.VERSION }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: setup eli + uses: alis-is/setup-eli@v1 + + - name: test tezbox + run: | + eli ./tests/all.lua + + - name: prep + id: prep + run: | + VERSION=$(eli -e "info = require'src.version-info'; io.write(info.VERSION)") + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + if git tag -l "$VERSION" | grep "$VERSION"; then + echo "Version $VERSION already exists"; + else + echo "Found new version - $VERSION" + echo "NEEDS_RELEASE=true" >> $GITHUB_OUTPUT + fi + echo "CURRENT_DATE=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT + + - name: build tezbox + if: ${{ steps.prep.outputs.NEEDS_RELEASE == 'true'}} + run: | + export ELI_PATH=$PWD/eli + eli ./build/build.lua + + - name: publish + uses: ncipollo/release-action@v1 + if: ${{ steps.prep.outputs.NEEDS_RELEASE == 'true'}} + with: + artifacts: "bin/tezbox" + tag: ${{ steps.prep.outputs.VERSION }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ghcr.io + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: build tezos v19.1 + uses: docker/build-push-action@v5 + if: ${{ steps.prep.outputs.NEEDS_RELEASE == 'true'}} + with: + file: containers/tezos/Containerfile + context: . + platforms: linux/amd64 + build-args: | + IMAGE_TAG=v19.1 + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + tags: ghcr.io/tez-capital/tezbox:tezos-v19.1.${{steps.prep.outputs.CURRENT_DATE}},ghcr.io/tez-capital/tezbox:tezos-v19.1,ghcr.io/tez-capital/tezbox:latest + push: true + provenance: false + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d82417 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +archive +.vscode + +bin/* +!bin/.gitkeep \ No newline at end of file diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/amalg.lua b/build/amalg.lua new file mode 100644 index 0000000..0b3d971 --- /dev/null +++ b/build/amalg.lua @@ -0,0 +1,749 @@ +#!/usr/bin/env lua + +-- **Amalg** is a Lua tool for bundling a Lua script and dependent +-- Lua modules in a single `.lua` file for easier distribution. +-- +-- Features: +-- * Pure Lua (compatible with Lua 5.1 and up), no external +-- dependencies. (Even works for modules using the deprecated +-- `module` function.) +-- * You don't have to take care of the order in which the modules +-- are `require`d. +-- * Can embed compiled C modules. +-- * Can collect `require`d Lua (and C) modules automatically. +-- +-- What it doesn't do: +-- +-- * It does not compile to bytecode. Use `luac` for that yourself, +-- or take a look at [squish][1], or [luac.lua][4]. +-- * It doesn't do static analysis of Lua code to collect `require`d +-- modules. That won't work reliably anyway in a dynamic language! +-- You can write your own program for that (e.g. using the output +-- of `luac -p -l`), or use [squish][1], or [soar][3] instead. +-- * It will not compress, minify, obfuscate your Lua source code, +-- or any of the other things [squish][1] can do. +-- * It doesn't handle the dependencies of C modules, so it is best +-- used on C modules without dependencies (e.g. LuaSocket, LFS, +-- etc.). +-- +-- The `amalg.lua` [source code][6] is available on GitHub, and is +-- released under the [MIT license][7]. You can view [a nice HTML +-- version][8] of this file rendered by [Docco][9] on the GitHub +-- pages. +-- +-- As already mentioned, there are alternatives to this program: See +-- [squish][1], [LOOP][2], [soar][3], [luac.lua][4], and +-- [bundle.lua][5] (and probably some more). +-- +-- [1]: http://matthewwild.co.uk/projects/squish/home +-- [2]: http://loop.luaforge.net/release/preload.html +-- [3]: http://lua-users.org/lists/lua-l/2012-02/msg00609.html +-- [4]: http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/5.1/luac.lua +-- [5]: https://github.com/akavel/scissors/blob/master/tools/bundle/bundle.lua +-- [6]: http://github.com/siffiejoe/lua-amalg +-- [7]: http://opensource.org/licenses/MIT +-- [8]: http://siffiejoe.github.io/lua-amalg/ +-- [9]: http://jashkenas.github.io/docco/ +-- +-- +-- ## Getting Started +-- +-- You can bundle a collection of Lua modules in a single file by +-- calling the `amalg.lua` script and passing the module names on the +-- command line: +-- +-- ./amalg.lua module1 module2 +-- +-- The modules are collected using `package.path`, so they have to be +-- available there. The resulting merged Lua code will be written to +-- the standard output stream. You have to actually run the resulting +-- code to make the embedded Lua modules available for `require`. +-- +-- You can specify an output file to use instead of the standard +-- output stream: +-- +-- ./amalg.lua -o out.lua module1 module2 +-- +-- You can also embed the main script of your application in the +-- merged Lua code as well. Of course, the embedded Lua modules can be +-- `require`d from the embedded main script. +-- +-- ./amalg.lua -o out.lua -s main.lua module1 module2 +-- +-- If you want the original file names and line numbers to appear in +-- error messages, you have to activate debug mode. This will require +-- slightly more memory, though. +-- +-- ./amalg.lua -o out.lua -d -s main.lua module1 module2 +-- +-- To collect all Lua (and C) modules used by a program, you can load +-- the `amalg.lua` script as a module, and it will intercept calls to +-- `require` (more specifically the Lua module searchers) and save the +-- necessary Lua module names in a file `amalg.cache` in the current +-- directory: +-- +-- lua -lamalg main.lua +-- +-- Multiple calls will add to this module cache. But don't access it +-- from multiple concurrent processes (the cache isn't protected +-- against race conditions)! +-- +-- You can use the cache (in addition to all module names given on the +-- command line) using the `-c` flag: +-- +-- ./amalg.lua -o out.lua -s main.lua -c +-- +-- However, this will only embed the Lua modules. To also embed the C +-- modules (both from the cache and from the command line), you have +-- to specify the `-x` flag: +-- +-- ./amalg.lua -o out.lua -s main.lua -c -x +-- +-- This will make the amalgamated script platform- and Lua version +-- dependent, obviously! +-- +-- In some cases you may want to ignore automatically listed modules +-- in the cache without editing the cache file. Use the `-i` option +-- for that and specify a Lua pattern: +-- +-- ./amalg.lua -o out.lua -s main.lua -c -i "^luarocks%." +-- +-- The `-i` option can be used multiple times to specify multiple +-- patterns. +-- +-- Usually, the amalgamated modules take precedence over locally +-- installed (possibly newer) versions of the same modules. If you +-- want to use local modules when available and only fall back to the +-- amalgamated code otherwise, you can specify the `-f` flag. +-- +-- ./amalg.lua -o out.lua -s main.lua -c -f +-- +-- This installs another searcher/loader function at the end of +-- `package.searchers` (or `package.loaders` on Lua 5.1) and adds +-- a new table `package.postload` that serves the same purpose as the +-- standard `package.preload` table. +-- +-- To fix a compatibility issue with Lua 5.1's vararg handling, +-- `amalg.lua` by default adds a local alias to the global `arg` table +-- to every loaded module. If for some reason you don't want that, use +-- the `-a` flag (but be aware that in Lua 5.1 with `LUA_COMPAT_VARARG` +-- defined (the default) your modules can only access the global `arg` +-- table as `_G.arg`). +-- +-- ./amalg.lua -o out.lua -a -s main.lua -c +-- +-- That's it. For further info consult the source. +-- +-- +-- ## Implementation +-- + +-- The name of the script used in warning messages and the name of the +-- cache file can be configured here by changing these local +-- variables: +local prog = "amalg.lua" +local cache = "amalg.cache" + + +-- Wrong use of the command line may cause warnings to be printed to +-- the console. This function is for printing those warnings: +local function warn( ... ) + io.stderr:write( "WARNING ", prog, ": " ) + local n = select( '#', ... ) + for i = 1, n do + local v = tostring( (select( i, ... )) ) + io.stderr:write( v, i == n and '\n' or '\t' ) + end +end + + +-- Function for parsing the command line of `amalg.lua` when invoked +-- as a script. The following flags are supported: +-- +-- * `-o `: specify output file (default is `stdout`) +-- * `-s `: specify main script to bundle +-- * `-c`: add the modules listed in the cache file `amalg.cache` +-- * `-i `: ignore modules in the cache file matching the +-- given pattern (can be given multiple times) +-- * `-d`: enable debug mode (file names and line numbers in error +-- messages will point to the original location) +-- * `-a`: do *not* apply the `arg` fix (local alias for the global +-- `arg` table) +-- * `-x`: also embed compiled C modules +-- * `--`: stop parsing command line flags (all remaining arguments +-- are considered module names) +-- +-- Other arguments are assumed to be module names. For an inconsistent +-- command line (e.g. duplicate options) a warning is printed to the +-- console. +local function parse_cmdline( ... ) + local modules, afix, ignores, tname, use_cache, cmods, dbg, script, oname = + {}, true, {}, "preload" + + local function set_oname( v ) + if v then + if oname then + warn( "Resetting output file `"..oname.."'! Using `"..v.."' now!" ) + end + oname = v + else + warn( "Missing argument for -o option!" ) + end + end + + local function set_script( v ) + if v then + if script then + warn( "Resetting main script `"..script.."'! Using `"..v.."' now!" ) + end + script = v + else + warn( "Missing argument for -s option!" ) + end + end + + local function add_ignore( v ) + if v then + if not pcall( string.match, "", v ) then + warn( "Invalid Lua pattern: `"..v.."'" ) + else + ignores[ #ignores+1 ] = v + end + else + warn( "Missing argument for -i option!" ) + end + end + + local i, n = 1, select( '#', ... ) + while i <= n do + local a = select( i, ... ) + if a == "--" then + for j = i+1, n do + modules[ select( j, ... ) ] = true + end + break + elseif a == "-o" then + i = i + 1 + set_oname( i <= n and select( i, ... ) ) + elseif a == "-s" then + i = i + 1 + set_script( i <= n and select( i, ... ) ) + elseif a == "-i" then + i = i + 1 + add_ignore( i <= n and select( i, ... ) ) + elseif a == "-f" then + tname = "postload" + elseif a == "-c" then + use_cache = true + elseif a == "-x" then + cmods = true + elseif a == "-d" then + dbg = true + elseif a == "-a" then + afix = false + else + local prefix = a:sub( 1, 2 ) + if prefix == "-o" then + set_oname( a:sub( 3 ) ) + elseif prefix == "-s" then + set_script( a:sub( 3 ) ) + elseif prefix == "-i" then + add_ignore( a:sub( 3 ) ) + elseif a:sub( 1, 1 ) == "-" then + warn( "Unknown command line flag: "..a ) + else + modules[ a ] = true + end + end + i = i + 1 + end + return oname, script, dbg, afix, use_cache, tname, ignores, cmods, modules +end + + +-- The approach for embedding precompiled Lua files is different from +-- the normal way of pasting the source code, so this function detects +-- whether a file is a binary file (Lua bytecode starts with the `ESC` +-- character): +local function is_bytecode( path ) + local f, res = io.open( path, "rb" ), false + if f then + res = f:read( 1 ) == "\027" + f:close() + end + return res +end + + +-- Read the whole contents of a file into memory without any +-- processing. +local function readfile( path, is_bin ) + local f = assert( io.open( path, is_bin and "rb" or "r" ) ) + local s = assert( f:read( "*a" ) ) + f:close() + return s +end + + +-- Lua files to be embedded into the resulting amalgamation are read +-- into memory in a single go, because under some circumstances (e.g. +-- binary chunks, shebang lines, `-d` command line flag) some +-- preprocessing/escaping is necessary. This function reads a whole +-- Lua file and returns the contents as a Lua string. +local function readluafile( path ) + local is_bin = is_bytecode( path ) + local s = readfile( path, is_bin ) + local shebang + if not is_bin then + -- Shebang lines are only supported by Lua at the very beginning + -- of a source file, so they have to be removed before the source + -- code can be embedded in the output. + shebang = s:match( "^(#![^\n]*)" ) + s = s:gsub( "^#[^\n]*", "" ) + end + return s, is_bin, shebang +end + + +-- Lua 5.1's `string.format("%q")` doesn't convert all control +-- characters to decimal escape sequences like the newer Lua versions +-- do. This might cause problems on some platforms (i.e. Windows) when +-- loading a Lua script (opened in text mode) that contains binary +-- code. +local function qformat( code ) + local s = ("%q"):format( code ) + return (s:gsub( "(%c)(%d?)", function( c, d ) + if c ~= "\n" then + return (d~="" and "\\%03d" or "\\%d"):format( c:byte() )..d + end + end )) +end + + +-- When the `-c` command line flag is given, the contents of the cache +-- file `amalg.cache` are used to specify the modules to embed. This +-- function is used to load the cache file: +local function readcache() + local chunk = loadfile( cache, "t", {} ) + if chunk then + if setfenv then setfenv( chunk, {} ) end + local result = chunk() + if type( result ) == "table" then + return result + end + end +end + + +-- When loaded as a module, `amalg.lua` collects Lua modules and C +-- modules that are `require`d and updates the cache file +-- `amalg.cache`. This function saves the updated cache contents to +-- the file: +local function writecache( c ) + local f = assert( io.open( cache, "w" ) ) + f:write( "return {\n" ) + for k,v in pairs( c ) do + if type( k ) == "string" and type( v ) == "string" then + f:write( " [ ", qformat( k ), " ] = ", qformat( v ), ",\n" ) + end + end + f:write( "}\n" ) + f:close() +end + + +-- The standard Lua function `package.searchpath` available in Lua 5.2 +-- and up is used to locate the source files for Lua modules and +-- library files for C modules. For Lua 5.1 a backport is provided. +local searchpath = package.searchpath +if not searchpath then + local delim = package.config:match( "^(.-)\n" ):gsub( "%%", "%%%%" ) + + function searchpath( name, path ) + local pname = name:gsub( "%.", delim ):gsub( "%%", "%%%%" ) + local msg = {} + for subpath in path:gmatch( "[^;]+" ) do + local fpath = subpath:gsub( "%?", pname ) + local f = io.open( fpath, "r" ) + if f then + f:close() + return fpath + end + msg[ #msg+1 ] = "\n\tno file '"..fpath.."'" + end + return nil, table.concat( msg ) + end +end + + +-- This is the main function for the use case where `amalg.lua` is run +-- as a script. It parses the command line, creates the output files, +-- collects the module and script sources, and writes the amalgamated +-- source. +local function amalgamate( ... ) + local oname, script, dbg, afix, use_cache, tname, ignores, cmods, modules = + parse_cmdline( ... ) + local errors = {} + + -- When instructed to on the command line, the cache file is loaded, + -- and the modules are added to the ones listed on the command line + -- unless they are ignored via the `-i` command line option. + if use_cache then + local c = readcache() + for k,v in pairs( c or {} ) do + local addmodule = true + for _,p in ipairs( ignores ) do + if k:match( p ) then + addmodule = false + break + end + end + if addmodule then + modules[ k ] = v + end + end + end + + local out = io.stdout + if oname then + out = assert( io.open( oname, "w" ) ) + end + + -- If a main script is to be embedded, this includes the same + -- shebang line that was used in the main script, so that the + -- resulting amalgamation can be run without explicitly + -- specifying the interpreter on unixoid systems (if a shebang + -- line was specified in the first place, that is). + local script_bytes, script_binary, shebang + if script then + script_bytes, script_binary, shebang = readluafile( script ) + if shebang then + out:write( shebang, "\n\n" ) + end + out:write( "do\n\n" ) + end + + -- If fallback loading is requested, the module loaders of the + -- amalgamated module are registered in table `package.postload`, + -- and an extra searcher function is added at the end of + -- `package.searchers`. + if tname == "postload" then + out:write([=[ +do + local assert = assert + local type = assert( type ) + local searchers = package.searchers or package.loaders + local postload = {} + package.postload = postload + searchers[ #searchers+1 ] = function( mod ) + assert( type( mod ) == "string", "module name must be a string" ) + local loader = postload[ mod ] + if loader == nil then + return "\n\tno field package.postload['"..mod.."']" + else + return loader + end + end +end + +]=] ) + end + + -- Sort modules alphabetically. Modules will be embedded in + -- alphabetical order. This ensures deterministic output. + local module_names = {} + for m in pairs( modules ) do + module_names[ #module_names+1 ] = m + end + table.sort( module_names ) + + -- Every module given on the command line and/or in the cache file + -- is processed. + for _,m in ipairs( module_names ) do + local t = modules[ m ] + -- Only Lua modules are handled for now, so modules that are + -- definitely C modules are skipped and handled later. + if t ~= "C" then + local path, msg = searchpath( m, package.path ) + if not path and (t == "L" or not cmods) then + -- The module is supposed to be a Lua module, but it cannot + -- be found, so an error is raised. + error( "module `"..m.."' not found:"..msg ) + elseif not path then + -- Module possibly is a C module, so it is tried again later. + -- But the current error message is saved in case the given + -- name isn't a C module either. + modules[ m ], errors[ m ] = "C", msg + else + local bytes, is_bin = readluafile( path ) + if is_bin or dbg then + -- Precompiled Lua modules are loaded via the standard Lua + -- function `load` (or `loadstring` in Lua 5.1). Since this + -- preserves file name and line number information, this + -- approach is used for all files if the debug mode is active + -- (`-d` command line option). + out:write( "package.", tname, "[ ", qformat( m ), + " ] = assert( (loadstring or load)(\n", + qformat( bytes ), "\n, '@'..", + qformat( path ), " ) )\n\n" ) + else + -- Under normal circumstances Lua files are pasted into a + -- new anonymous vararg function, which then is put into + -- `package.preload` so that `require` can find it. Each + -- function gets its own `_ENV` upvalue (on Lua 5.2+), and + -- special care is taken that `_ENV` always is the first + -- upvalue (important for the `module` function on Lua 5.2). + -- Lua 5.1 compiled with `LUA_COMPAT_VARARG` (the default) will + -- create a local `arg` variable to emulate the vararg handling + -- of Lua 5.0. This might interfere with Lua modules that access + -- command line arguments via the `arg` global. As a workaround + -- `amalg.lua` adds a local alias to the global `arg` table + -- unless the `-a` command line flag is specified. + out:write( "do\nlocal _ENV = _ENV\n", + "package.", tname, "[ ", qformat( m ), + " ] = function( ... ) ", + afix and "local arg = _G.arg;\n" or "_ENV = _ENV;\n", + bytes, "\nend\nend\n\n" ) + end + end + end + end + + -- If the `-x` command line flag is active, C modules are embedded + -- as strings, and written out to temporary files on demand by the + -- amalgamated code. + if cmods then + local nfuncs = {} + -- To make the loading of C modules more robust, the necessary + -- global functions are saved in upvalues (because user-supplied + -- code might be run before a C module is loaded). The upvalues + -- are local to a `do ... end` block, so they aren't visible in + -- the main script code. + -- + -- On Windows the result of `os.tmpname()` is not an absolute + -- path by default. If that's the case the value of the `TMP` + -- environment variable is prepended to make it absolute. + local prefix = [=[ +local assert = assert +local newproxy = newproxy +local getmetatable = assert( getmetatable ) +local setmetatable = assert( setmetatable ) +local os_tmpname = assert( os.tmpname ) +local os_getenv = assert( os.getenv ) +local os_remove = assert( os.remove ) +local io_open = assert( io.open ) +local string_match = assert( string.match ) +local string_sub = assert( string.sub ) +local package_loadlib = assert( package.loadlib ) + +local dirsep = package.config:match( "^([^\n]+)" ) +local tmpdir +local function newdllname() + local tmpname = assert( os_tmpname() ) + if dirsep == "\\" then + if not string_match( tmpname, "[\\/][^\\/]+[\\/]" ) then + tmpdir = tmpdir or assert( os_getenv( "TMP" ) or + os_getenv( "TEMP" ), + "could not detect temp directory" ) + local first = string_sub( tmpname, 1, 1 ) + local hassep = first == "\\" or first == "/" + tmpname = tmpdir..((hassep) and "" or "\\")..tmpname + end + end + return tmpname +end +local dllnames = {} + +]=] + for _,m in ipairs( module_names ) do + local t = modules[ m ] + if t == "C" then + -- Try a search strategy similar to the standard C module + -- searcher first and then the all-in-one strategy to locate + -- the library files for the C modules to embed. + local path, msg = searchpath( m, package.cpath ) + if not path then + errors[ m ] = (errors[ m ] or "") .. msg + path, msg = searchpath( m:gsub( "%..*$", "" ), package.cpath ) + if not path then + error( "module `"..m.."' not found:"..errors[ m ]..msg ) + end + end + local qpath = qformat( path ) + -- Build the symbol(s) to look for in the dynamic library. + -- There may be multiple candidates because of optional + -- version information in the module names and the different + -- approaches of the different Lua versions in handling that. + local openf = m:gsub( "%.", "_" ) + local openf1, openf2 = openf:match( "^([^%-]*)%-(.*)$" ) + -- The amalgamation of C modules is split into two parts: + -- One part generates a temporary file name for the C library + -- and writes the binary code stored in the amalgamation to + -- that file, while the second loads the resulting dynamic + -- library using `package.loadlib`. The split is necessary + -- because multiple modules could be loaded from the same + -- library, and the amalgamated code has to simulate that. + -- Shared dynamic libraries are embedded only once. + -- + -- The temporary dynamic library files may or may not be + -- cleaned up when the amalgamated code exits (this probably + -- works on POSIX machines (all Lua versions) and on Windows + -- with Lua 5.1). The reason is that starting with version 5.2 + -- Lua ensures that libraries aren't unloaded before normal + -- user-supplied `__gc` metamethods have run to avoid a case + -- where such a metamethod would call an unloaded C function. + -- As a consequence the amalgamated code tries to remove the + -- temporary library files *before* they are actually + -- unloaded. + if not nfuncs[ path ] then + local code = readfile( path, true ) + nfuncs[ path ] = true + local qcode = qformat( code ) + out:write( prefix, "dllnames[ ", qpath, [=[ ] = function() + local dll = newdllname() + local f = assert( io_open( dll, "wb" ) ) + f:write( ]=], qcode, [=[ ) + f:close() + local sentinel = newproxy and newproxy( true ) + or setmetatable( {}, { __gc = true } ) + getmetatable( sentinel ).__gc = function() os_remove( dll ) end + dllnames[ ]=], qpath, [=[ ] = function() + local _ = sentinel + return dll + end + return dll +end + +]=] ) + prefix = "" + end -- shared libary not embedded already + -- Add a function to `package.preload` to load the temporary + -- DLL or shared object file. This function tries to mimic the + -- behavior of Lua 5.3 which is to strip version information + -- from the module name at the end first, and then at the + -- beginning if that failed. + local qm = qformat( m ) + out:write( "package.", tname, "[ ", qm, " ] = function()\n", + " local dll = dllnames[ ", qpath, " ]()\n" ) + if openf1 then + out:write( " local loader = package_loadlib( dll, ", + qformat( "luaopen_"..openf1 ), " )\n", + " if not loader then\n", + " loader = assert( package_loadlib( dll, ", + qformat( "luaopen_"..openf2 ), + " ) )\n end\n" ) + else + out:write( " local loader = assert( package_loadlib( dll, ", + qformat( "luaopen_"..openf ), " ) )\n" ) + end + out:write( " return loader( ", qm, ", dll )\nend\n\n" ) + end -- is a C module + end -- for all given module names + end -- if cmods + + -- If a main script is specified on the command line (`-s` flag), + -- embed it now that all dependent modules are available to + -- `require`. + if script then + out:write( "end\n\n" ) + if script_binary or dbg then + out:write( "assert( (loadstring or load)(\n", + qformat( script_bytes ), "\n, '@'..", + qformat( script ), " ) )( ... )\n\n" ) + else + out:write( script_bytes ) + end + end + + if oname then + out:close() + end +end + + +-- If `amalg.lua` is loaded as a module, it intercepts `require` calls +-- (more specifically calls to the searcher functions) to collect all +-- `require`d module names and store them in the cache. The cache file +-- `amalg.cache` is updated when the program terminates. +local function collect() + local searchers = package.searchers or package.loaders + -- When the searchers table has been modified, it is unknown which + -- elements in the table to replace, so `amalg.lua` bails out with + -- an error. The `luarocks.loader` module which inserts itself at + -- position 1 in the `package.searchers` table is explicitly + -- supported, though! + local off = 0 + if package.loaded[ "luarocks.loader" ] then off = 1 end + assert( #searchers == 4+off, "package.searchers has been modified" ) + local c = readcache() or {} + -- The updated cache is written to disk when the following value is + -- garbage collected, which should happen at `lua_close()`. + local sentinel = newproxy and newproxy( true ) + or setmetatable( {}, { __gc = true } ) + getmetatable( sentinel ).__gc = function() writecache( c ) end + local lua_searcher = searchers[ 2+off ] + local c_searcher = searchers[ 3+off ] + local aio_searcher = searchers[ 4+off ] -- all in one searcher + + local function rv_handler( tag, mname, ... ) + if type( (...) ) == "function" then + c[ mname ] = tag + end + return ... + end + + -- The replacement searchers just forward to the original versions, + -- but also update the cache if the search was successful. + searchers[ 2+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "L", ..., lua_searcher( ... ) ) + end + searchers[ 3+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "C", ..., c_searcher( ... ) ) + end + searchers[ 4+off ] = function( ... ) + local _ = sentinel -- make sure that sentinel is an upvalue + return rv_handler( "C", ..., aio_searcher( ... ) ) + end + + -- Since calling `os.exit` might skip the `lua_close()` call, the + -- `os.exit` function is monkey-patched to also save the updated + -- cache to the cache file on disk. + if type( os ) == "table" and type( os.exit ) == "function" then + local os_exit = os.exit + function os.exit( ... ) + writecache( c ) + return os_exit( ... ) + end + end +end + + +-- To determine whether `amalg.lua` is run as a script or loaded as a +-- module it uses the debug module to walk the call stack looking for +-- a `require` call. If such a call is found, `amalg.lua` has been +-- `require`d as a module. +local function is_script() + local i = 3 + local info = debug.getinfo( i, "f" ) + while info do + if info.func == require then + return false + end + i = i + 1 + info = debug.getinfo( i, "f" ) + end + return true +end + + +-- This checks whether `amalg.lua` has been called as a script or +-- loaded as a module and acts accordingly, by calling the +-- corresponding main function: +if is_script() then + amalgamate( ... ) +else + collect() +end + diff --git a/build/build.lua b/build/build.lua new file mode 100644 index 0000000..bdd70d7 --- /dev/null +++ b/build/build.lua @@ -0,0 +1,53 @@ +local amalg = loadfile("./build/amalg.lua") + +local function collect_requires(entrypoint) + local requires = {} + local ok, content = fs.safe_read_file(entrypoint) + if not ok then + return requires + end + for require in content:gmatch("require%s*%(?%s*['\"](.-)['\"]%s*%)?") do + if not table.includes(requires, require) then + -- change require to path + local file = require:gsub("%.", "/") .. ".lua" + if fs.file_type(file) == "file" then + table.insert(requires, require) + local subRequires = collect_requires(file) + requires = util.merge_arrays(requires, subRequires) --[[ @as table ]] + end + end + end + return requires +end + +local function inject_license(filePath) + local _content = fs.read_file(filePath) + local _, _shebangEnd = _content:find("#!/%S*") + local _license = [[ +-- Copyright (C) 2024 tez.capital + +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as published +-- by the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. + +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. + +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + ]] + local _contentWithLicense = _content:sub(1, _shebangEnd + 1) .. _license .. _content:sub(_shebangEnd + 1) + fs.write_file(filePath, _contentWithLicense) +end + +os.chdir("src") + +fs.mkdir("../bin") + +local tezboxEntrypoint = "tezbox.lua" +local tezboxOutput = "../bin/tezbox" +amalg("-o", tezboxOutput, "-s", tezboxEntrypoint, table.unpack(collect_requires(tezboxEntrypoint))) +inject_license(tezboxOutput) \ No newline at end of file diff --git a/configuration/accounts.hjson b/configuration/accounts.hjson new file mode 100644 index 0000000..36e0400 --- /dev/null +++ b/configuration/accounts.hjson @@ -0,0 +1,20 @@ +{ + alice: { + pkh: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb + pk: edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn + sk: unencrypted:edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq + balance: 2000000000000 + } + bob: { + pkh: tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6 + pk: edpkurPsQ8eUApnLUJ9ZPDvu98E8VNj4KtJa1aZr16Cr5ow5VHKnz4 + sk: unencrypted:edsk3RFfvaFaxbHx8BMtEW1rKQcPtDML3LXjNqMNLCzC3wLC1bWbAt + balance: 2000000000000 + } + eve: { + pkh: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx + pk: edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav + sk: unencrypted:edsk3gUfUPyBSfrS9CCgmCiQsTCHGkviBDusMxDJstFtojtc1zcpsh + balance: 2000000000000 + } +} \ No newline at end of file diff --git a/configuration/genesis.hjson b/configuration/genesis.hjson new file mode 100644 index 0000000..7df4d70 --- /dev/null +++ b/configuration/genesis.hjson @@ -0,0 +1,14 @@ +{ + "genesis": { + "timestamp": "2024-04-06T13:33:14Z", + "block": "BL7aN4FRNydwCVRo4KB16FibBmu6LdzuTC4hxGqyziCVRhkdFBp", + "protocol": "ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im" + }, + "genesis_parameters": { + "values": { + "genesis_pubkey": "edpkuSLWfVU1Vq7Jg9FucPyKmma6otcMHac9zG4oU1KMHSTBpJuGQ2" + } + }, + "chain_name": "TEZBOX", + "sandboxed_chain_name": "SANDBOXED_TEZBOX" +} \ No newline at end of file diff --git a/configuration/init-config-args.hjson b/configuration/init-config-args.hjson new file mode 100644 index 0000000..a3371f4 --- /dev/null +++ b/configuration/init-config-args.hjson @@ -0,0 +1,8 @@ +[ + --network=/tezbox/context/genesis.json + --net-addr=0.0.0.0:9732 + --rpc-addr=0.0.0.0:8732 + --expected-pow=0.0 + --connections=0 + --history-mode=archive +] \ No newline at end of file diff --git a/configuration/protocols/Proxford.hjson b/configuration/protocols/Proxford.hjson new file mode 100644 index 0000000..82fc1d3 --- /dev/null +++ b/configuration/protocols/Proxford.hjson @@ -0,0 +1,13 @@ +id: proxford +short: Proxford +hash: ProxfordYmVfjWnRcgjWH36fW6PArwqykTFzotUxRs6gmTcZDuH +aliases: [ + oxfordbox +] +vote-file: { + liquidity_baking_toggle_vote: pass + adaptive_issuance_vote: pass +} +sandbox-overrides: { + +} diff --git a/configuration/sandbox-parameters.hjson b/configuration/sandbox-parameters.hjson new file mode 100644 index 0000000..63805b5 --- /dev/null +++ b/configuration/sandbox-parameters.hjson @@ -0,0 +1,110 @@ +{ + "preserved_cycles": 2, + "blocks_per_cycle": 8, + "blocks_per_commitment": 4, + "nonce_revelation_threshold": 4, + "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, + "hard_gas_limit_per_operation": "1040000", + "hard_gas_limit_per_block": "2600000", + "proof_of_work_threshold": "4611686018427387903", + "minimal_stake": "6000000000", + "minimal_frozen_stake": "600000000", + "vdf_difficulty": "50000", + "origination_size": 257, + "issuance_weights": { + "base_total_issued_per_minute": "85007812", + "baking_reward_fixed_portion_weight": 5120, + "baking_reward_bonus_weight": 5120, + "attesting_reward_weight": 10240, + "liquidity_baking_subsidy_weight": 1280, + "seed_nonce_revelation_tip_weight": 1, + "vdf_revelation_tip_weight": 1 + }, + "cost_per_byte": "250", + "hard_storage_limit_per_operation": "60000", + "quorum_min": 2000, + "quorum_max": 7000, + "min_proposal_quorum": 500, + "liquidity_baking_toggle_ema_threshold": 1000000000, + "max_operations_time_to_live": 240, + "minimal_block_delay": "5", + "delay_increment_per_round": "1", + "consensus_committee_size": 256, + "consensus_threshold": 0, + "minimal_participation_ratio": { + "numerator": 2, + "denominator": 3 + }, + "limit_of_delegation_over_baking": 19, + "percentage_of_frozen_deposits_slashed_per_double_baking": 5, + "percentage_of_frozen_deposits_slashed_per_double_attestation": 50, + "cache_script_size": 100000000, + "cache_stake_distribution_cycles": 8, + "cache_sampler_state_cycles": 8, + "dal_parametric": { + "feature_enable": false, + "number_of_slots": 16, + "attestation_lag": 4, + "attestation_threshold": 50, + "blocks_per_epoch": 1, + "redundancy_factor": 8, + "page_size": 128, + "slot_size": 32768, + "number_of_shards": 64 + }, + "smart_rollup_arith_pvm_enable": false, + "smart_rollup_origination_size": 6314, + "smart_rollup_challenge_window_in_blocks": 80640, + "smart_rollup_stake_amount": "10000000000", + "smart_rollup_commitment_period_in_blocks": 60, + "smart_rollup_max_lookahead_in_blocks": 172800, + "smart_rollup_max_active_outbox_levels": 80640, + "smart_rollup_max_outbox_messages_per_level": 100, + "smart_rollup_number_of_sections_in_dissection": 32, + "smart_rollup_timeout_period_in_blocks": 40320, + "smart_rollup_max_number_of_cemented_commitments": 5, + "smart_rollup_max_number_of_parallel_games": 32, + "smart_rollup_reveal_activation_level": { + "raw_data": { + "Blake2B": 0 + }, + "metadata": 0, + "dal_page": 2147483646, + "dal_parameters": 2147483646 + }, + "smart_rollup_private_enable": true, + "smart_rollup_riscv_pvm_enable": false, + "zk_rollup_enable": false, + "zk_rollup_origination_size": 4000, + "zk_rollup_min_pending_to_process": 10, + "zk_rollup_max_ticket_payload_size": 2048, + "global_limit_of_staking_over_baking": 5, + "edge_of_staking_over_delegation": 2, + "adaptive_issuance_launch_ema_threshold": 1600000000, + "adaptive_rewards_params": { + "issuance_ratio_min": { + "numerator": "1", + "denominator": "2000" + }, + "issuance_ratio_max": { + "numerator": "1", + "denominator": "20" + }, + "max_bonus": "50000000000000", + "growth_rate": { + "numerator": "1", + "denominator": "100" + }, + "center_dz": { + "numerator": "1", + "denominator": "2" + }, + "radius_dz": { + "numerator": "1", + "denominator": "50" + } + }, + "adaptive_issuance_activation_vote_enable": false, + "autostaking_enable": true +} \ No newline at end of file diff --git a/configuration/services/baker.hjson b/configuration/services/baker.hjson new file mode 100644 index 0000000..93ece37 --- /dev/null +++ b/configuration/services/baker.hjson @@ -0,0 +1,17 @@ +{ + executable: octez-baker-${PROTOCOL_SHORT} + args: [ + run + remotely + --votefile + ${VOTE_FILE} + ] + environment: { + HOME: ${HOME} + TEZOS_LOG: "* -> info" + } + restart: always + restart_delay: 20 + stop_timeout: 300 + user: ${USER} +} \ No newline at end of file diff --git a/configuration/services/node.hjson b/configuration/services/node.hjson new file mode 100644 index 0000000..9032776 --- /dev/null +++ b/configuration/services/node.hjson @@ -0,0 +1,20 @@ +{ + executable: octez-node + args: [ + run + --allow-all-rpc=0.0.0.0 + --metadata-size-limit=unlimited + --synchronisation-threshold=0 + --no-bootstrap-peers + --peer=localhost:9732 + --private-mode + ] + environment: { + HOME: ${HOME} + TEZOS_LOG: "* -> info" + } + restart: always + restart_delay: 20 + stop_timeout: 300 + user: ${USER} +} \ No newline at end of file diff --git a/containers/minimal/Containerfile b/containers/minimal/Containerfile new file mode 100644 index 0000000..e36cf24 --- /dev/null +++ b/containers/minimal/Containerfile @@ -0,0 +1,18 @@ +ARG IMAGE_TAG=latest + +FROM docker.io/tezos/tezos:${IMAGE_TAG} + +ENV ASCEND_SERVICES=/ascend/services +ENV ASCEND_HEALTHCHECKS=/ascend/healthchecks +ENV ASCEND_SOCKET=/var/run/ascend.socket +ENV ASCEND_LOGS=/var/log/ascend + +RUN printf '#!/bin/sh\n\ +if [ -z "$GITHUB_TOKEN" ]; then\n\ + wget "$@" \n\ +else\n\ + wget --header "Authorization: token $GITHUB_TOKEN" "$@" \n\ +fi\n' > /usr/local/bin/auth_wget && chmod +x /usr/local/bin/auth_wget + +# Use auth_wget for downloading files with optional authentication +RUN auth_wget https://raw.githubusercontent.com/alis-is/ascend/main/tools/setup/standalone-linux.sh -O /tmp/setup-ascend.sh && sh /tmp/setup-ascend.sh --prerelease diff --git a/containers/tezos/Containerfile b/containers/tezos/Containerfile new file mode 100644 index 0000000..3f6b12b --- /dev/null +++ b/containers/tezos/Containerfile @@ -0,0 +1,33 @@ +ARG IMAGE_TAG=v19.1 + +FROM docker.io/tezos/tezos:${IMAGE_TAG} + +USER root + +ENV ASCEND_SERVICES=/ascend/services +ENV ASCEND_HEALTHCHECKS=/ascend/healthchecks +ENV ASCEND_SOCKET=/var/run/ascend.socket +ENV ASCEND_LOGS=/var/log/ascend +ENV ASCEND_INIT="tezbox init --setup-services" +ENV TEZBOX_USER=tezos + +RUN printf '#!/bin/sh\n\ +if [ -z "$GITHUB_TOKEN" ]; then\n\ + wget "$@" \n\ +else\n\ + wget --header "Authorization: token $GITHUB_TOKEN" "$@" \n\ +fi\n' > /usr/local/bin/auth_wget && chmod +x /usr/local/bin/auth_wget + +# Use auth_wget for downloading files with optional authentication +RUN auth_wget https://raw.githubusercontent.com/alis-is/ascend/main/tools/setup/standalone-linux.sh -O /tmp/setup-ascend.sh && sh /tmp/setup-ascend.sh --prerelease + +RUN mkdir -p /tezbox /tezbox/configuration /tezbox/overrides /tezbox/overrides/context +COPY configuration /tezbox/configuration +RUN chown -R tezos:tezos /tezbox + +COPY bin/tezbox /usr/local/bin/tezbox +RUN chown -R tezos:nogroup /usr/local/bin/tezbox + +USER root +ENTRYPOINT [ "ascend" ] +CMD [""] \ No newline at end of file diff --git a/src/box/constants.lua b/src/box/constants.lua new file mode 100644 index 0000000..1dba447 --- /dev/null +++ b/src/box/constants.lua @@ -0,0 +1,6 @@ +return { + activatorAccount = { + pk = "edpkuSLWfVU1Vq7Jg9FucPyKmma6otcMHac9zG4oU1KMHSTBpJuGQ2", + sk = "unencrypted:edsk31vznjHSSpGExDMHYASz45VZqXN4DPxvsa4hAyY8dHM28cZzp6" + } +} \ No newline at end of file diff --git a/src/box/context.lua b/src/box/context.lua new file mode 100644 index 0000000..fe5d808 --- /dev/null +++ b/src/box/context.lua @@ -0,0 +1,147 @@ +local env = require "box.env" +local hjson = require "hjson" +local constants = require "box.constants" + +local context = { + protocols = {} +} + +function context.build() + -- build context + local configurationFiles = fs.read_dir(env.configurationDirectory, { + recurse = true, + returnFullPaths = false, + asDirEntries = false, + }) --[=[@as string[]]=] + + local configurationOverrideFiles = fs.read_dir(env.configurationOverridesDirectory, { + recurse = true, + returnFullPaths = false, + asDirEntries = false, + }) --[=[@as string[]]=] + + for _, configurationFile in ipairs(configurationFiles) do + local configurationFilePath = path.combine(env.configurationDirectory, configurationFile) + if fs.file_type(configurationFilePath) == "directory" then + fs.mkdirp(configurationFilePath) + goto continue + end + + local configurationFileContent = fs.read_file(configurationFilePath) + local configuration = hjson.decode(configurationFileContent) + + local configurationOverridesFile = path.combine(env.configurationOverridesDirectory, configurationFile) + local ok, configurationOverridesFileContent = fs.safe_read_file(configurationOverridesFile) + if ok then + local configurationOverrides = hjson.decode(configurationOverridesFileContent) + configuration = util.merge_tables(configuration, configurationOverrides, + { overwrite = true, arrayMergeStrategy = "prefer-t2" }) + end + + local contextFile = path.combine(env.contextDirectory, configurationFile) + fs.mkdirp(path.dir(contextFile)) + if contextFile:match("%.hjson$") then + contextFile = contextFile:sub(1, -6) -- remove .hjson extension + contextFile = contextFile .. "json" -- add .json extension + end + fs.write_file(contextFile, hjson.encode_to_json(configuration)) + ::continue:: + end + + for _, configurationOverrideFile in ipairs(configurationOverrideFiles) do + local ovverrideFilePath = path.combine(env.configurationOverridesDirectory, configurationOverrideFile) + local newOverrideFilePath = path.combine(env.contextDirectory, configurationOverrideFile) + if fs.file_type(ovverrideFilePath) == "directory" then + fs.mkdirp(ovverrideFilePath) + goto continue + end + if not fs.exists(newOverrideFilePath) then -- only if it wasnt applied through merge + fs.copy_file(path.combine(env.configurationOverridesDirectory, configurationOverrideFile), + newOverrideFilePath) + end + ::continue:: + end + + -- load protocols + local protocolDirectory = path.combine(env.contextDirectory, "protocols") + local protocolFiles = fs.read_dir(protocolDirectory, { + recurse = false, + returnFullPaths = true, + asDirEntries = false, + }) --[=[@as string[]]=] + + local protocols = {} + for _, protocolFile in ipairs(protocolFiles) do + local protocolFileContent = fs.read_file(protocolFile) + local protocol = hjson.decode(protocolFileContent) + if type(protocol.id) ~= "string" then + log_warn("valid protocol id not found in protocol file: " .. protocolFile .. ", skipping") + goto continue + end + if protocols[protocol.id] then + log_warn("duplicate protocol id found: " .. protocol.id .. ", skipping") + goto continue + end + if type(protocol.short) ~= "string" then + log_warn("valid protocol short name not found in protocol file: " .. protocolFile .. ", skipping") + goto continue + end + if type(protocol.hash) ~= "string" then + log_warn("valid protocol hash not found in protocol file: " .. protocolFile .. ", skipping") + goto continue + end + + if type(protocol["vote-file"]) ~= "table" then + log_warn("valid protocol vote file not found in protocol file: " .. protocolFile .. ", skipping") + goto continue + end + + protocols[protocol.id] = protocol + protocols[protocol.hash] = protocol + for _, alias in ipairs(protocol.aliases or {}) do + if protocols[alias] then + log_warn("duplicate protocol alias found: " .. alias .. ", skipping") + goto continue + end + protocols[alias] = protocol + ::continue:: + end + + ::continue:: + end + + context.protocols = protocols + + -- inject accounts to sandbox parameters + local ok, accountsHjson = fs.safe_read_file(path.combine(env.contextDirectory, "accounts.json")) + if ok then + local accounts = hjson.decode(accountsHjson) + if type(accounts) == "table" and #table.keys(accounts) > 0 then + local parametersFile = path.combine(env.contextDirectory, env.sandboxParametersFile) + local parametersHjson = fs.read_file(parametersFile) + local parameters = hjson.decode(parametersHjson) + + parameters.bootstrap_accounts = {} + for _, account in pairs(accounts) do + if type(account.balance) ~= "number" or type(account.pk) ~= "string" then + goto continue + end + table.insert(parameters.bootstrap_accounts, { + account.pk, + tostring(account.balance) + }) + ::continue:: + end + + fs.write_file(parametersFile, hjson.encode_to_json(parameters)) + end + end + + -- create sandbox.json + local sandboxJson = hjson.encode_to_json({ + genesis_pubkey = constants.activatorAccount.pk + }) + fs.write_file(path.combine(env.contextDirectory, "sandbox.json"), sandboxJson) +end + +return context diff --git a/src/box/core.lua b/src/box/core.lua new file mode 100644 index 0000000..4bbf323 --- /dev/null +++ b/src/box/core.lua @@ -0,0 +1,168 @@ +local hjson = require 'hjson' +local octez = require 'box.octez' +local env = require 'box.env' +local context = require 'box.context' +local constants = require 'box.constants' + +local core = {} + +---@class ProtocolDefinition +---@field id string +---@field short string +---@field hash string +---@field aliases string[]? +---@field vote-file table? +---@field sandbox-overrides table? + +---@param protocol ProtocolDefinition +local function inject_ascend_services(protocol) + local servicesDirectory = os.getenv "ASCEND_SERVICES" + if not servicesDirectory then + log_error("ASCEND_SERVICES environment variable not set") + os.exit(1) + end + + local vars = require "eli.env".environment() + local contextVars = { + PROTOCOL_SHORT = protocol.short, + PROTOCOL_HASH = protocol.hash, + SANDBOX_FILE = path.combine(env.contextDirectory, env.sandboxParametersFile), + HOME = env.homeDirectory, + VOTE_FILE = env.voteFile, + USER = env.user, + } + vars = util.merge_tables(vars, contextVars, { overwrite = true }) + + local serviceTemplatesDirectory = path.combine(env.contextDirectory, "services") + local serviceTemplateFiles = fs.read_dir(serviceTemplatesDirectory, { + recurse = true, + returnFullPaths = false, + asDirEntries = false, + }) --[=[@as string[]]=] + + for _, serviceTemplateFileName in ipairs(serviceTemplateFiles) do + local serviceTemplate = fs.read_file(path.combine(serviceTemplatesDirectory, serviceTemplateFileName)) + local service = string.interpolate(serviceTemplate, vars) + local serviceFilePath = path.combine(servicesDirectory, serviceTemplateFileName) + if serviceFilePath:match("%.json$") then -- services have to be hjson files + serviceFilePath = serviceFilePath:sub(1, -5) -- remove .json ext + serviceFilePath = serviceFilePath .. "hjson" + end + local ok = fs.safe_write_file(serviceFilePath, service) + if not ok then + log_error("failed to write service file " .. serviceFilePath) + os.exit(1) + end + end +end + +---@class TezboxInitializeOptions +---@field injectServices boolean? + +---@param protocol string +---@param options TezboxInitializeOptions +function core.initialize(protocol, options) + if type(options) ~= "table" then options = {} end + + local ok, initializedProtocol = fs.safe_read_file(path.combine(env.tezboxDirectory, "tezbox-initialized")) + + if ok then + if initializedProtocol == protocol then + log_info("tezbox already initialized for protocol " .. initializedProtocol) + return + end + log_info("found tezbox-initialized file, but protocol is different, reinitializing") + end + + log_debug("resetting state") + octez.reset() -- reset octez state + context.build() -- rebuild context + + local proto = context.protocols[protocol] --[[@as ProtocolDefinition?]] + if not proto then + log_error("protocol " .. protocol .. " not found in context") + os.exit(1) + end + + log_info("generating node identity") + local result = octez.node.generate_identity() + if result.exitcode ~= 0 then + log_error("failed to generate node identity") + os.exit(1) + end + + log_info("initializing node configuration") + local initConfigArgsHjson = fs.read_file(path.combine(env.contextDirectory, 'init-config-args.json')) + local initConfigArgs = hjson.parse(initConfigArgsHjson) + local result = octez.node.init_config(initConfigArgs) + if result.exitcode ~= 0 then + log_error("failed to initialize node configuration") + os.exit(1) + end + + log_info("importing accounts") + local accountsHjson = fs.read_file(path.combine(env.contextDirectory, 'accounts.json')) + local accounts = hjson.decode(accountsHjson) + + accounts.activator = constants.activatorAccount + + for accountId, account in pairs(accounts) do + if not account.sk then + log_debug("skipped importing account " .. accountId .. " (no secret key)") + goto continue + end + log_debug("importing account " .. accountId) + local result = octez.client.import_account(accountId, account.sk) + if result.exitcode ~= 0 then + log_error("failed to import account " .. accountId) + os.exit(1) + end + ::continue:: + end + + log_info("activating protocol " .. proto.hash) + octez.exec_with_node_running(function() + local result = octez.client.run({ "-block", "genesis", "activate", "protocol", proto.hash, "with", "fitness", "1", + "and", + "key", "activator", "and", "parameters", path.combine(env.contextDirectory, env.sandboxParametersFile) }) + if result.exitcode ~= 0 then + error("failed to activate protocol " .. proto.hash) + end + end) + + if type(proto["andbox-overrides"]) == "table" then + log_info("injecting protocol sandbox overrides") + + if type(accounts) == "table" and #table.keys(accounts) > 0 then + local parametersFile = path.combine(env.contextDirectory, env.sandboxParametersFile) + local parametersHjson = fs.read_file(parametersFile) + local parameters = hjson.decode(parametersHjson) + + parameters = util.merge_tables(parameters, proto["sandbox-overrides"], + { arrayMergeStrategy = "prefer-t2", overwrite = true }) + + fs.write_file(parametersFile, hjson.encode_to_json(parameters)) + end + end + + -- create vote file + if not fs.exists(env.voteFile) then + local voteFileJson = hjson.encode_to_json(proto["vote-file"]) + fs.write_file(env.voteFile, voteFileJson) + end + + -- patch services + if options.injectServices then + inject_ascend_services(proto) + end + + -- finalize + fs.write_file("tezbox-initialized", protocol) +end + +function core.run() + error("not implemented") + -- // TODO: run for setup without container +end + +return core diff --git a/src/box/env.lua b/src/box/env.lua new file mode 100644 index 0000000..5b22c8b --- /dev/null +++ b/src/box/env.lua @@ -0,0 +1,56 @@ +local args = require "util.args" + +local tezboxDirectory = os.getenv("TEZBOX_DIRECTORY") or "/tezbox" +local tezboxDataDirectory = path.combine(tezboxDirectory, "data") +local tezboxContextDirectory = path.combine(tezboxDirectory, "context") +local defaultEnv = { + tezboxDirectory = tezboxDirectory, + configurationDirectory = path.combine(tezboxDirectory, "configuration"), + configurationOverridesDirectory = path.combine(tezboxDirectory, "overrides"), + contextDirectory = tezboxContextDirectory, + sandboxParametersFile = "sandbox-parameters.json", + protocol = "proxford", + + -- octez directories + homeDirectory = tezboxDataDirectory, + + octezNodeBinary = "octez-node", + octezClientBinary = "octez-client", + voteFile = path.combine(tezboxContextDirectory, "vote.json"), + + user = "tezbox", +} + +local env = util.merge_tables({ + configurationDirectory = args.options["configuration-directory"] or env.get_env("CONFIGURATION_DIRECTORY"), + configurationOverridesDirectory = args.options["configuration-overrides-directory"] or + env.get_env("CONFIGURATION_OVERRIDES_DIRECTORY"), + contextDirectory = args.options["context-directory"] or env.get_env("CONTEXT_DIRECTORY"), + sandboxParametersFile = args.options["sandbox-parameters-file"] or env.get_env("SANDBOX_PARAMETERS_FILE"), + protocol = args.options["protocol"] or env.get_env("PROTOCOL"), + + homeDirectory = args.options["home-directory"] or env.get_env("HOME_DIRECTORY"), + + octezNodeBinary = args.options["octez-node-binary"] or env.get_env("OCTEZ_NODE_BINARY"), + octezClientBinary = args.options["octez-client-binary"] or env.get_env("OCTEZ_CLIENT_BINARY"), + voteFile = args.options["vote-file"] or env.get_env("VOTE_FILE"), + + user = args.options["user"] or env.get_env("USER"), +}, defaultEnv) + +fs.mkdirp(env.configurationDirectory) +fs.mkdirp(env.configurationOverridesDirectory) +fs.mkdirp(env.contextDirectory) +fs.mkdirp(env.homeDirectory) + +if env.user ~= "" and env.user ~= "root" then + local ok, uid = fs.safe_getuid(env.user) + if not ok then + log_error("user " .. env.user .. " does not exist") + os.exit(1) + end + + fs.chown(env.tezboxDirectory, uid, uid, { recurse = true }) +end + +return env diff --git a/src/box/octez.lua b/src/box/octez.lua new file mode 100644 index 0000000..8cd9760 --- /dev/null +++ b/src/box/octez.lua @@ -0,0 +1,144 @@ +local signal = require "os.signal" +local env = require "box.env" + +local octez = { + client = {}, + node = {} +} + +---@param overrides table +local function buildEnv(overrides) + return util.merge_tables(require"eli.env".environment(), overrides, { overwrite = true }) +end + +---@class RunClientOptions +---@field user string + +---runs octez client +---@param args string[] +---@param user string +---@return SpawnResult +function octez.client.run(args, options) + if type(options) ~= "table" then options = {} end + if type(args) ~= "table" then args = {} end + + return proc.spawn(env.octezClientBinary, args, { + username = options.user or env.user, + wait = true, + stdio = "inherit", + env = buildEnv( { HOME = env.homeDirectory } ), + }) --[[@as SpawnResult]] +end + +---@param id string +---@param secret string +function octez.client.import_account(id, secret) + return octez.client.run({ "--protocol", "ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK", "import", "secret", "key", id, secret }) +end + +---@class RunNodeOptions +---@field user string? + + +---runs octez node +---@param args string[] +---@param options RunNodeOptions +---@return SpawnResult +function octez.node.run(args, options) + if type(options) ~= "table" then options = {} end + if type(args) ~= "table" then args = {} end + + return proc.spawn(env.octezNodeBinary, args, { + username = options.user or env.user, + wait = true, + stdio = "inherit", + env = buildEnv( { HOME = env.homeDirectory } ), + }) --[[@as SpawnResult]] +end + +---@param configOptionsArgs table +---@param options RunNodeOptions? +function octez.node.init_config(configOptionsArgs, options) + if type(configOptionsArgs) ~= "table" then configOptionsArgs = {} end + if type(options) ~= "table" then options = {} end + + local args = { + "config", + "init", + } + if table.is_array(configOptionsArgs) then + for _, value in ipairs(configOptionsArgs) do + table.insert(args, value) + end + elseif type(configOptionsArgs) == "table" then + for key, value in pairs(configOptionsArgs) do + table.insert(args, "--" .. key .. "=" .. value) + end + end + return octez.node.run(args, options) +end + +---@class FooOptions: RunNodeOptions +---@field timeout integer? + +---@param exec fun() +---@param options FooOptions? +function octez.exec_with_node_running(exec, options) + if type(options) ~= "table" then options = {} end + if type(exec) ~= "function" then + return + end + + local NODE_DIR = path.combine(env.homeDirectory, ".tezos-node") + local time = os.time() + (options.timeout or 60) + local args = { + "run", + "--data-dir=" .. NODE_DIR, + "--synchronisation-threshold=0", + "--connections=0", + "--allow-all-rpc=0.0.0.0", + "--no-bootstrap-peers", + "--private-mode", + "--sandbox=" .. path.combine(env.contextDirectory, "sandbox.json"), + } + + local nodeProc = proc.spawn(env.octezNodeBinary, args, { + username = options.user or env.user, + stdio = "inherit" + }) --[[@as EliProcess]] + + local executed = false + while nodeProc:wait(100, 1000) == -1 do + local ok = net.safe_download_string("http://127.0.0.1:8732/chains/main/blocks/head/metadata") + if ok then + exec() + executed = true + break + end + if os.time() > time then + break + end + end + nodeProc:kill(signal.SIGTERM) + local exitCode = nodeProc:wait(30) + if exitCode < 0 then + nodeProc:kill(signal.SIGKILL) + end + return executed +end + +---@param options RunNodeOptions? +function octez.node.generate_identity(options) + if type(options) ~= "table" then options = {} end + return octez.node.run({ "identity", "generate", "0.0" }, options) +end + +function octez.reset() + local NODE_DIR = path.combine(env.homeDirectory, ".tezos-node") + local CLIENT_DIR = path.combine(env.homeDirectory, ".tezos-client") + + fs.remove(CLIENT_DIR, { recurse = true, contentOnly = true }) + fs.remove(NODE_DIR, { recurse = true, contentOnly = true }) +end + +return octez diff --git a/src/tezbox.lua b/src/tezbox.lua new file mode 100644 index 0000000..9b783ab --- /dev/null +++ b/src/tezbox.lua @@ -0,0 +1,36 @@ +#!/usr/sbin/eli +require "util.log" ("tezbox") + +local args = require "util.args" + +if args.command == "version" or args.options["version"] then + print(string.interpolate("tezbox ${version}", { version = require "version-info".VERSION })) + os.exit(0) +end + +GLOBAL_LOGGER.options.level = args.options["log-level"] or "info" + +local core = require "box.core" + +if args.command == "init" or args.command == "initialize" then + if #args.parameters < 1 then + log_error("missing protocol") + os.exit(1) + end + + local protocol = args.parameters[1] + local options = {} + if args.options["setup-services"] then + options.injectServices = true + end + + core.initialize(protocol, options) + os.exit(0) +end + +if args.command == "run" then + core.run() + os.exit(0) +end + +os.exit(1) diff --git a/src/util/args.lua b/src/util/args.lua new file mode 100644 index 0000000..a4a3872 --- /dev/null +++ b/src/util/args.lua @@ -0,0 +1,31 @@ +local args = cli.parse_args(arg) + +local optionAliases = { + ["l"] = "log-level", + ["t"] = "timeout" +} + +local parameters = {} +local options = {} +local command = nil + +for _, v in ipairs(args) do + if v.type == "parameter" then + if command == nil then + command = tostring(v.value) + else + table.insert(parameters, v.value) + end + elseif v.type == "option" then + if optionAliases[v.id] then + v.id = optionAliases[v.id] + end + options[v.id] = v.value + end +end + +return { + command = command, + parameters = parameters, + options = options +} \ No newline at end of file diff --git a/src/util/log.lua b/src/util/log.lua new file mode 100644 index 0000000..a5f4efa --- /dev/null +++ b/src/util/log.lua @@ -0,0 +1,40 @@ +--- Log module +---@param id string +return function(id) + ---#DES log_success + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_success, + ---#DES log_trace + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_trace, + ---#DES log_debug + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_debug, + ---#DES log_info + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_info, + ---#DES log_warn + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_warn, + ---#DES log_error + --- + ---@diagnostic disable-next-line: undefined-doc-param + ---@param msg LogMessage|string + ---@param vars table? + log_error = util.global_log_factory(id, "success", "trace", "debug", "info", "warn", "error") +end diff --git a/src/version-info.lua b/src/version-info.lua new file mode 100644 index 0000000..544c149 --- /dev/null +++ b/src/version-info.lua @@ -0,0 +1,5 @@ +local TEZBOX_VERSIOn = "0.0.6" + +return { + VERSION = TEZBOX_VERSIOn, +} diff --git a/tests/all.lua b/tests/all.lua new file mode 100644 index 0000000..e69de29