Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/test-write-program-buffer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Test write-program-buffer

on:
pull_request:
paths:
- "write-program-buffer/**"
- "start-test-validator/**"
- "setup-all/**"
- ".github/workflows/test-write-program-buffer.yaml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: test-write-program-buffer-${{ github.ref }}
cancel-in-progress: true

jobs:
integration:
name: ${{ matrix.scenario }} (solana ${{ matrix.solana-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
solana-version: ["4.1.2"]
scenario: [fresh, delta, clamp, authority, nearmax]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- uses: ./setup-all
with:
solana_version: ${{ matrix.solana-version }}

- name: Start local validator
id: validator
uses: ./start-test-validator

- name: Prepare scenario
id: prepare
shell: bash
env:
RPC_URL: ${{ steps.validator.outputs.rpc-url }}
run: ./write-program-buffer/tests/integration/prepare-${{ matrix.scenario }}.sh

- name: Run write-program-buffer action
id: run
uses: ./write-program-buffer
with:
program-id: ${{ steps.prepare.outputs.program-id }}
program: fixture-${{ matrix.scenario }}
rpc-url: ${{ steps.validator.outputs.rpc-url }}
keypair: ${{ steps.prepare.outputs.keypair }}
buffer-authority-address: ${{ steps.prepare.outputs.buffer-authority }}

- name: Assert scenario
shell: bash
env:
RPC_URL: ${{ steps.validator.outputs.rpc-url }}
BUFFER: ${{ steps.run.outputs.buffer }}
PROGRAM_ID: ${{ steps.prepare.outputs.program-id }}
DEPLOYER: ${{ steps.prepare.outputs.deployer }}
BUFFER_AUTHORITY: ${{ steps.prepare.outputs.buffer-authority }}
PRE_LEN: ${{ steps.prepare.outputs.pre-len }}
run: ./write-program-buffer/tests/integration/assert-${{ matrix.scenario }}.sh

- name: Dump validator logs on failure
if: failure()
shell: bash
run: |
tail -100 "$RUNNER_TEMP/validator-stdout.log" || true
tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || true
57 changes: 57 additions & 0 deletions start-test-validator/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: "Start Test Validator"
description: "Starts a local solana-test-validator in the background and waits until it is healthy"

inputs:
ledger-dir:
description: "Ledger directory (defaults to <runner temp>/test-ledger)"
required: false
default: ""
max-attempts:
description: "Maximum health check attempts, roughly one per second"
required: false
default: "60"

outputs:
rpc-url:
description: "RPC URL of the started validator"
value: ${{ steps.start.outputs.rpc-url }}
ledger-dir:
description: "Ledger directory of the started validator"
value: ${{ steps.start.outputs.ledger-dir }}

runs:
using: "composite"
steps:
- name: Start validator
id: start
shell: bash
env:
LEDGER_DIR_INPUT: ${{ inputs.ledger-dir }}
run: |
RPC_URL="http://127.0.0.1:8899"
LEDGER_DIR="$LEDGER_DIR_INPUT"
if [ -z "$LEDGER_DIR" ]; then
LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger"
fi

rm -rf "$LEDGER_DIR"
solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 &
echo "Validator started with PID $!"

echo "rpc-url=$RPC_URL" >> "$GITHUB_OUTPUT"
echo "ledger-dir=$LEDGER_DIR" >> "$GITHUB_OUTPUT"

- name: Wait for validator health
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_seconds: 5
max_attempts: ${{ inputs.max-attempts }}
retry_wait_seconds: 1
command: solana cluster-version -u ${{ steps.start.outputs.rpc-url }}

- name: Dump validator logs if unhealthy
if: failure()
shell: bash
run: |
tail -50 "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>/dev/null || true
tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added write-program-buffer/tests/fixtures/program-small.so
Binary file not shown.
7 changes: 7 additions & 0 deletions write-program-buffer/tests/fixtures/program/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions write-program-buffer/tests/fixtures/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "fixture-program"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[features]
medium = []
big = []
huge = []

[workspace]
25 changes: 25 additions & 0 deletions write-program-buffer/tests/fixtures/program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![no_std]

#[cfg(feature = "medium")]
#[no_mangle]
pub static PAD: [u8; 20_000] = [1; 20_000];

#[cfg(feature = "big")]
#[no_mangle]
pub static PAD: [u8; 4096] = [1; 4096];

#[cfg(feature = "huge")]
#[no_mangle]
pub static PAD: [u8; 10_481_816] = [1; 10_481_816];

#[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
#[cfg(any(feature = "medium", feature = "big", feature = "huge"))]
core::hint::black_box(&PAD);
0
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
47 changes: 47 additions & 0 deletions write-program-buffer/tests/fixtures/rebuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"

command -v cargo-build-sbf >/dev/null || {
echo "cargo-build-sbf is required, install the agave tool suite first" >&2
exit 1
}

build() {
local variant="$1" feature="$2"
local args=(--arch v3 --manifest-path program/Cargo.toml --sbf-out-dir program/out)
if [ -n "$feature" ]; then
args+=(--features "$feature")
fi
cargo-build-sbf "${args[@]}"
cp program/out/fixture_program.so "program-$variant.so"
rm -rf program/out program/target
}

build small ""
build medium medium
build big big
build huge huge

SMALL=$(wc -c < program-small.so | tr -d ' ')
MEDIUM=$(wc -c < program-medium.so | tr -d ' ')
BIG=$(wc -c < program-big.so | tr -d ' ')
HUGE=$(wc -c < program-huge.so | tr -d ' ')
echo "small=$SMALL medium=$MEDIUM big=$BIG huge=$HUGE"

if [ "$BIG" -le "$SMALL" ] || [ $((BIG - SMALL)) -ge 10240 ]; then
echo "Size band violated: big-small delta must be in (0, 10240)" >&2
exit 1
fi
if [ $((MEDIUM - SMALL)) -le 10240 ]; then
echo "Size band violated: medium-small delta must exceed 10240" >&2
exit 1
fi
if [ "$HUGE" -le 10477475 ] || [ "$HUGE" -gt 10485715 ]; then
echo "Size band violated: huge must be in (10477475, 10485715]" >&2
exit 1
fi

gzip -9 -n -c program-huge.so > program-huge.so.gz
rm program-huge.so
echo "Fixtures rebuilt"
27 changes: 27 additions & 0 deletions write-program-buffer/tests/integration/assert-authority.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="${RPC_URL:-http://127.0.0.1:8899}"
ARTIFACT="target/deploy/fixture-authority.so"

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${BUFFER_AUTHORITY:-}" ] || fail "BUFFER_AUTHORITY env not set"
[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set"

BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL")
echo "$BUFFER_INFO"
AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}')
[ "$AUTHORITY" = "$BUFFER_AUTHORITY" ] || fail "buffer authority is $AUTHORITY, expected $BUFFER_AUTHORITY"
[ "$AUTHORITY" != "$DEPLOYER" ] || fail "buffer authority still equals the deployer"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Authority transfer assertions passed"
26 changes: 26 additions & 0 deletions write-program-buffer/tests/integration/assert-clamp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="${RPC_URL:-http://127.0.0.1:8899}"
ARTIFACT="target/deploy/fixture-clamp.so"
MIN_EXTEND_SIZE=10240

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set"

POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1)
GROWTH=$((POST_LEN - PRE_LEN))
[ "$GROWTH" -eq "$MIN_EXTEND_SIZE" ] || fail "program grew by $GROWTH bytes, expected exactly $MIN_EXTEND_SIZE"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Clamp regression assertions passed: program grew by exactly $MIN_EXTEND_SIZE bytes"
30 changes: 30 additions & 0 deletions write-program-buffer/tests/integration/assert-delta.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="${RPC_URL:-http://127.0.0.1:8899}"
ARTIFACT="target/deploy/fixture-delta.so"
MIN_EXTEND_SIZE=10240

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set"

REQUIRED_SIZE=$(wc -c < "$ARTIFACT" | tr -d ' ')
EXPECTED_GROWTH=$((REQUIRED_SIZE - PRE_LEN))
[ "$EXPECTED_GROWTH" -gt "$MIN_EXTEND_SIZE" ] || fail "scenario setup invalid: expected growth $EXPECTED_GROWTH must exceed $MIN_EXTEND_SIZE"

POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1)
GROWTH=$((POST_LEN - PRE_LEN))
[ "$GROWTH" -eq "$EXPECTED_GROWTH" ] || fail "program grew by $GROWTH bytes, expected the exact delta $EXPECTED_GROWTH"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Plain-delta extend assertions passed: program grew by exactly $EXPECTED_GROWTH bytes"
31 changes: 31 additions & 0 deletions write-program-buffer/tests/integration/assert-fresh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="${RPC_URL:-http://127.0.0.1:8899}"
ARTIFACT="target/deploy/fixture-fresh.so"

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL")
echo "$BUFFER_INFO"
AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}')
[ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER"

ABSENCE_CHECK=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 || true)
if ! echo "$ABSENCE_CHECK" | grep -q "Unable to find the account"; then
fail "program $PROGRAM_ID unexpectedly exists or the absence check errored: $ABSENCE_CHECK"
fi

echo "Fresh-program scenario assertions passed"
28 changes: 28 additions & 0 deletions write-program-buffer/tests/integration/assert-nearmax.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="${RPC_URL:-http://127.0.0.1:8899}"
ARTIFACT="target/deploy/fixture-nearmax.so"
MAX_PERMITTED_DATA_LENGTH=10485760
PROGRAMDATA_METADATA_SIZE=45
MAX_PROGRAM_SIZE=$((MAX_PERMITTED_DATA_LENGTH - PROGRAMDATA_METADATA_SIZE))

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set"

POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1)
[ "$POST_LEN" -eq "$MAX_PROGRAM_SIZE" ] || fail "program data length is $POST_LEN, expected the exact maximum $MAX_PROGRAM_SIZE"
echo "Program extended from $PRE_LEN to $POST_LEN (exact headroom of $((POST_LEN - PRE_LEN)) bytes)"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Near-max extend assertions passed"
Loading
Loading