#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WIRE_SYSIO_DIR="${WIRE_SYSIO_DIR:-${ROOT_DIR}/wire-sysio}" BUILD_DIR="${WIRE_SYSIO_BUILD_DIR:-${WIRE_SYSIO_DIR}/build/debug-docker}" VCPKG_DIR="${BUILD_DIR}/vcpkg_installed/x64-linux" WIRE_CDT_DIR="${WIRE_CDT_DIR:-${ROOT_DIR}/wire-cdt}" WIRE_CDT_BUILD_DIR="${WIRE_CDT_BUILD_DIR:-${WIRE_CDT_DIR}/build/debug-docker}" WIRE_SYSIO_SUBMODULE_FALLBACK_DIR="${WIRE_SYSIO_SUBMODULE_FALLBACK_DIR:-${ROOT_DIR}/wire-sysio}" WIRE_SYSIO_BUILD_FALLBACK_DIR="${WIRE_SYSIO_BUILD_FALLBACK_DIR:-${ROOT_DIR}/wire-sysio/build/debug-docker}" ROUTE="${1:-${WIRE_BATCH_OPERATOR_ROUTE:-}}" INDEX="${2:-${WIRE_BATCH_OPERATOR_INDEX:-1}}" OP_ROOT="${WIRE_BATCH_OPERATOR_ROOT:-/srv/wire-batch-operators}" WIRE_BATCH_OPERATOR_ARTIFACTS_DIR="${WIRE_BATCH_OPERATOR_ARTIFACTS_DIR:-${ROOT_DIR}/.local/e2e}" CONFIG_DIR="${OP_ROOT}/${ROUTE}/${INDEX}/config" DATA_DIR="${OP_ROOT}/${ROUTE}/${INDEX}/data" LOG_DIR="${OP_ROOT}/${ROUTE}/${INDEX}/log" LOG_FILE="${LOG_DIR}/relay.log" BUILD_LOCK_FILE="${BUILD_DIR}/batch-operator-relay.lock" CMAKE_COMMON_ARGS=( -S "${WIRE_SYSIO_DIR}" -B "${BUILD_DIR}" -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="${WIRE_SYSIO_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DCMAKE_C_COMPILER=/opt/clang/clang-18/bin/clang -DCMAKE_CXX_COMPILER=/opt/clang/clang-18/bin/clang++ -DCMAKE_INSTALL_PREFIX=/opt/wire -DCMAKE_PREFIX_PATH="/opt/wire;/opt/clang/clang-18;${WIRE_CDT_BUILD_DIR}" -DENABLE_TESTS=OFF -DBUILD_SYSTEM_CONTRACTS=ON -DBUILD_TEST_CONTRACTS=OFF -DCDT_ROOT="${WIRE_CDT_BUILD_DIR}" ) dir_is_empty() { local path="$1" [[ -d "${path}" ]] && [[ -z "$(find "${path}" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]] } link_submodule_from_fallback() { local relative_path="$1" local target_path="${WIRE_SYSIO_DIR}/${relative_path}" local source_path="${WIRE_SYSIO_SUBMODULE_FALLBACK_DIR}/${relative_path}" local link_source if [[ ! -e "${source_path}" ]]; then return fi link_source="$(realpath --relative-to "$(dirname "${target_path}")" "${source_path}")" if [[ -L "${target_path}" ]] && [[ "$(readlink "${target_path}")" == "${link_source}" ]]; then return fi if [[ -L "${target_path}" ]]; then ln -sfn "${link_source}" "${target_path}" return fi if dir_is_empty "${target_path}"; then rmdir "${target_path}" fi if [[ ! -e "${target_path}" ]]; then ln -sfn "${link_source}" "${target_path}" fi } hydrate_wire_submodules() { link_submodule_from_fallback "libraries/appbase" link_submodule_from_fallback "vcpkg" } hydrate_wire_build_cache() { local target_install_dir="${BUILD_DIR}/vcpkg_installed" local source_install_dir="${WIRE_SYSIO_BUILD_FALLBACK_DIR}/vcpkg_installed" local link_source if [[ ! -d "${source_install_dir}" ]]; then return fi mkdir -p "$(dirname "${target_install_dir}")" link_source="$(realpath --relative-to "$(dirname "${target_install_dir}")" "${source_install_dir}")" if [[ -L "${target_install_dir}" ]] && [[ "$(readlink "${target_install_dir}")" == "${link_source}" ]]; then return fi if [[ -L "${target_install_dir}" ]]; then ln -sfn "${link_source}" "${target_install_dir}" return fi if [[ -d "${target_install_dir}" ]] && [[ ! -f "${BUILD_DIR}/bin/batch-operator-relay" ]]; then mv "${target_install_dir}" "${target_install_dir}.partial.$(date +%s)" fi if [[ ! -e "${target_install_dir}" ]]; then ln -s "${link_source}" "${target_install_dir}" fi } require_wire_submodules() { hydrate_wire_submodules if [[ ! -f "${WIRE_SYSIO_DIR}/vcpkg/bootstrap-vcpkg.sh" ]] || [[ ! -f "${WIRE_SYSIO_DIR}/libraries/appbase/CMakeLists.txt" ]]; then echo "wire-sysio submodules are missing. Run: git -C ${WIRE_SYSIO_DIR} submodule update --init --recursive" >&2 exit 1 fi } write_pkgconfig_shims() { local pc_dir="$1" local lib_dir="$2" mkdir -p "${pc_dir}" cat >"${pc_dir}/libssl.pc" <"${pc_dir}/libcrypto.pc" </dev/null 2>&1 || true write_pkgconfig_shims "${VCPKG_DIR}/lib/pkgconfig" "${VCPKG_DIR}/lib" write_pkgconfig_shims "${VCPKG_DIR}/debug/lib/pkgconfig" "${VCPKG_DIR}/debug/lib" cmake \ "${CMAKE_COMMON_ARGS[@]}" \ -DOPENSSL_INCLUDE_DIR="${VCPKG_DIR}/include" \ -DOPENSSL_SSL_LIBRARY="${VCPKG_DIR}/debug/lib/libssl.a" \ -DOPENSSL_CRYPTO_LIBRARY="${VCPKG_DIR}/debug/lib/libbscrypto.a" } build_batch_operator() { cmake --build "${BUILD_DIR}" --target batch-operator-relay -- -j"$(nproc)" } require_var() { local name="$1" if [[ -z "${!name:-}" ]]; then echo "Missing required environment variable: ${name}" >&2 exit 1 fi } prepare_dirs() { mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${LOG_DIR}" } append_log() { printf '[%s] %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$*" >>"${LOG_FILE}" } configure_and_build_batch_operator() { mkdir -p "${BUILD_DIR}" append_log "Waiting for build lock: ${BUILD_LOCK_FILE}" { flock 9 append_log "Acquired build lock" configure_wire_sysio >>"${LOG_FILE}" 2>&1 build_batch_operator >>"${LOG_FILE}" 2>&1 } 9>"${BUILD_LOCK_FILE}" } wait_for_artifact() { local path="$1" local attempts="${2:-120}" for _ in $(seq 1 "${attempts}"); do if [[ -f "${path}" ]]; then return 0 fi sleep 1 done echo "Timed out waiting for artifact: ${path}" >&2 exit 1 } source_env_file() { local path="$1" set -a # shellcheck disable=SC1090 source "${path}" set +a } load_generated_artifacts() { local wire_env="${WIRE_BATCH_OPERATOR_ARTIFACTS_DIR}/wire.env" local source_env wait_for_artifact "${wire_env}" source_env_file "${wire_env}" case "${ROUTE}" in eth) source_env="${WIRE_BATCH_OPERATOR_ARTIFACTS_DIR}/ethereum.env" ;; sol) source_env="${WIRE_BATCH_OPERATOR_ARTIFACTS_DIR}/solana.env" ;; *) echo "Unsupported route '${ROUTE}'. Expected 'eth' or 'sol'." >&2 exit 1 ;; esac wait_for_artifact "${source_env}" source_env_file "${source_env}" if [[ "${ROUTE}" == "eth" ]]; then [[ -n "${WIRE_BATCH_OPERATOR_WIRE_RPC_URL:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_RPC_URL="${LOCAL_WIRE_RPC_URL:-http://wire-nodeop:8887}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_DEPOT_ACCOUNT:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_DEPOT_ACCOUNT="${LOCAL_WIRE_ETH_DEPOT_ACCOUNT:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_ACCOUNT:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_ACCOUNT="${LOCAL_WIRE_ETH_OPERATOR_ACCOUNT:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID="${LOCAL_WIRE_ETH_OPERATOR_PROVIDER_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC="${LOCAL_WIRE_ETH_OPERATOR_PROVIDER_SPEC:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID="${LOCAL_ETH_CLIENT_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID="${LOCAL_ETH_PROVIDER_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC="${LOCAL_ETH_PROVIDER_SPEC:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_RPC_URL:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_RPC_URL="${LOCAL_ETH_RPC_URL:-}" [[ -n "${WIRE_BATCH_OPERATOR_ETH_ABI_FILE:-}" ]] || WIRE_BATCH_OPERATOR_ETH_ABI_FILE="${LOCAL_ETH_ABI_FILE:-}" [[ -n "${WIRE_BATCH_OPERATOR_ETH_OPP_ADDRESS:-}" ]] || WIRE_BATCH_OPERATOR_ETH_OPP_ADDRESS="${LOCAL_ETH_OPP_ADDRESS:-}" [[ -n "${WIRE_BATCH_OPERATOR_ETH_OPP_INBOUND_ADDRESS:-}" ]] || WIRE_BATCH_OPERATOR_ETH_OPP_INBOUND_ADDRESS="${LOCAL_ETH_OPP_INBOUND_ADDRESS:-}" else [[ -n "${WIRE_BATCH_OPERATOR_WIRE_RPC_URL:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_RPC_URL="${LOCAL_WIRE_RPC_URL:-http://wire-nodeop:8887}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_DEPOT_ACCOUNT:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_DEPOT_ACCOUNT="${LOCAL_WIRE_SOL_DEPOT_ACCOUNT:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_ACCOUNT:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_ACCOUNT="${LOCAL_WIRE_SOL_OPERATOR_ACCOUNT:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID="${LOCAL_WIRE_SOL_OPERATOR_PROVIDER_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC:-}" ]] || WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC="${LOCAL_WIRE_SOL_OPERATOR_PROVIDER_SPEC:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID="${LOCAL_SOL_CLIENT_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID="${LOCAL_SOL_PROVIDER_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC="${LOCAL_SOL_PROVIDER_SPEC:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOURCE_RPC_URL:-}" ]] || WIRE_BATCH_OPERATOR_SOURCE_RPC_URL="${LOCAL_SOL_RPC_URL:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOL_PROGRAM_ID:-}" ]] || WIRE_BATCH_OPERATOR_SOL_PROGRAM_ID="${LOCAL_SOL_PROGRAM_ID:-}" [[ -n "${WIRE_BATCH_OPERATOR_SOL_IDL_FILE:-}" ]] || WIRE_BATCH_OPERATOR_SOL_IDL_FILE="${LOCAL_SOL_IDL_FILE:-}" fi } build_args() { local -n out_args=$1 local route="$2" require_var WIRE_BATCH_OPERATOR_WIRE_ACCOUNT require_var WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID require_var WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC require_var WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID require_var WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID require_var WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC require_var WIRE_BATCH_OPERATOR_SOURCE_RPC_URL out_args=( --config-dir "${CONFIG_DIR}" --data-dir "${DATA_DIR}" --batch-operator-wire-rpc-url "${WIRE_BATCH_OPERATOR_WIRE_RPC_URL:-http://wire-nodeop:8887}" --batch-operator-wire-depot-account "${WIRE_BATCH_OPERATOR_WIRE_DEPOT_ACCOUNT:-sysio.depot}" --batch-operator-wire-operator "${WIRE_BATCH_OPERATOR_WIRE_ACCOUNT},${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_ID}" --batch-operator-poll-interval-ms "${WIRE_BATCH_OPERATOR_POLL_INTERVAL_MS:-5000}" --signature-provider "${WIRE_BATCH_OPERATOR_WIRE_PROVIDER_SPEC}" --signature-provider "${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_SPEC}" ) if [[ "${route}" == "eth" ]]; then require_var WIRE_BATCH_OPERATOR_ETH_OPP_ADDRESS require_var WIRE_BATCH_OPERATOR_ETH_OPP_INBOUND_ADDRESS require_var WIRE_BATCH_OPERATOR_ETH_ABI_FILE out_args+=( --outpost-ethereum-client "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID},${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID},${WIRE_BATCH_OPERATOR_SOURCE_RPC_URL},${WIRE_BATCH_OPERATOR_ETH_CHAIN_ID:-31337}" --ethereum-abi-file "${WIRE_BATCH_OPERATOR_ETH_ABI_FILE}" --batch-operator-ethereum-client-id "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID}" --batch-operator-ethereum-opp-address "${WIRE_BATCH_OPERATOR_ETH_OPP_ADDRESS}" --batch-operator-ethereum-opp-inbound-address "${WIRE_BATCH_OPERATOR_ETH_OPP_INBOUND_ADDRESS}" ) elif [[ "${route}" == "sol" ]]; then require_var WIRE_BATCH_OPERATOR_SOL_PROGRAM_ID require_var WIRE_BATCH_OPERATOR_SOL_IDL_FILE out_args+=( --outpost-solana-client "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID},${WIRE_BATCH_OPERATOR_SOURCE_PROVIDER_ID},${WIRE_BATCH_OPERATOR_SOURCE_RPC_URL}" --solana-idl-file "${WIRE_BATCH_OPERATOR_SOL_IDL_FILE}" --batch-operator-solana-client-id "${WIRE_BATCH_OPERATOR_SOURCE_CLIENT_ID}" --batch-operator-solana-program-id "${WIRE_BATCH_OPERATOR_SOL_PROGRAM_ID}" ) else echo "Unsupported route '${route}'. Expected 'eth' or 'sol'." >&2 exit 1 fi } main() { require_wire_submodules prepare_dirs load_generated_artifacts append_log "Loaded generated artifacts for route=${ROUTE} index=${INDEX}" configure_and_build_batch_operator local binary="${BUILD_DIR}/bin/batch-operator-relay" local args=() build_args args "${ROUTE}" append_log "Starting batch-operator route=${ROUTE} index=${INDEX}" append_log "Config dir: ${CONFIG_DIR}" append_log "Data dir: ${DATA_DIR}" exec "${binary}" "${args[@]}" >>"${LOG_FILE}" 2>&1 } main "$@"