From c2323e1325e4fa4c87a615082bb5117054a35ff8 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:04:12 +0100 Subject: [PATCH 01/14] feat: fix ci and tests Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- README.md | 2 +- ci/Dockerfile.bridge | 2 +- ci/Dockerfile.lightnode | 2 +- ci/Dockerfile.validator | 2 +- ci/docker-compose.yml | 31 +++++++++-------------- ci/run-bridge.sh | 42 ++++++++++++++++++++++++++++++++ ci/run-lightnode.sh | 41 ++++++++++++++++++++++++++++++- crates/tests/src/lib.rs | 4 +-- elf/riscv32im-succinct-zkvm-elf | Bin 1928548 -> 1928632 bytes justfile | 33 ++++++++++++++++++++----- 10 files changed, 127 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 35bc0c86..c43df154 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ We are currently experimenting with various proof systems and have handwritten g ### Install Dependencies -We use `just` as a task runner. Once installed, you can install the rest of the dependencies with: +We use [`just`](https://github.com/casey/just?tab=readme-ov-file#packages) as a task runner. Once installed, you can install the rest of the dependencies with: ```bash just install-deps diff --git a/ci/Dockerfile.bridge b/ci/Dockerfile.bridge index c7c1cbfb..60944d81 100644 --- a/ci/Dockerfile.bridge +++ b/ci/Dockerfile.bridge @@ -3,7 +3,7 @@ # A dockerfile for the celestia bridge node in DA layer # Based on: # https://github.com/celestiaorg/celestia-node/blob/main/Dockerfile -FROM docker.io/alpine:3.19.1 +FROM docker.io/alpine:3.21.0 ENV CELESTIA_HOME=/root diff --git a/ci/Dockerfile.lightnode b/ci/Dockerfile.lightnode index dac4537d..2f299c08 100644 --- a/ci/Dockerfile.lightnode +++ b/ci/Dockerfile.lightnode @@ -3,7 +3,7 @@ # A dockerfile for the celestia bridge node in DA layer # Based on: # https://github.com/celestiaorg/celestia-node/blob/main/Dockerfile -FROM docker.io/alpine:3.19.1 +FROM docker.io/alpine:3.21.0 ENV CELESTIA_HOME=/root diff --git a/ci/Dockerfile.validator b/ci/Dockerfile.validator index 68c5b8f2..b704c54e 100644 --- a/ci/Dockerfile.validator +++ b/ci/Dockerfile.validator @@ -3,7 +3,7 @@ # A dockerfile for the celestia validator in consensus layer # Based on: # https://github.com/celestiaorg/celestia-app/blob/main/Dockerfile -FROM docker.io/alpine:3.19.1 +FROM docker.io/alpine:3.21.0 ENV CELESTIA_HOME=/root diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 6535ede6..be2599fd 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -23,7 +23,11 @@ services: # provide an id for the bridge node (default: 0) # each node should have a next natural number starting from 0 - NODE_ID=0 + # setting SKIP_AUTH to true disables the use of JWT for authentication - SKIP_AUTH=true + # this must match the service name in the docker-compose.yml file + # used for the trusted peers string for the light node + - CONTAINER_NAME=bridge-0 ports: - 26658:26658 volumes: @@ -42,6 +46,9 @@ services: - NODE_ID=1 # setting SKIP_AUTH to true disables the use of JWT for authentication - SKIP_AUTH=true + # this must match the service name in the docker-compose.yml file + # used for the trusted peers string for the light node + - CONTAINER_NAME=bridge-1 ports: - 36658:26658 volumes: @@ -55,34 +62,20 @@ services: context: . dockerfile: Dockerfile.lightnode environment: - # provide an id for the bridge node (default: 0) + # provide an id for the light node (default: 0) # each node should have a next natural number starting from 0 - NODE_ID=0 + # setting SKIP_AUTH to true disables the use of JWT for authentication - SKIP_AUTH=true + # depending on the number of bridge nodes, provide the count + # is used for the trusted peers string for the light node + - BRIDGE_COUNT=2 ports: - 46658:26658 volumes: - credentials:/credentials - genesis:/genesis - # Uncomment for another bridge node - # remember to adjust services.validator.command - # bridge-1: - # image: bridge - # platform: "linux/amd64" - # build: - # context: . - # dockerfile: Dockerfile.bridge - # environment: - # # provide an id for the bridge node (default: 0) - # # each node should have a next natural number starting from 0 - # - NODE_ID=1 - # ports: - # - 36658:26658 - # volumes: - # - credentials:/credentials - # - genesis:/genesis - volumes: # local volume where node's credentials can persist credentials: diff --git a/ci/run-bridge.sh b/ci/run-bridge.sh index ba4e2830..471e2ac7 100755 --- a/ci/run-bridge.sh +++ b/ci/run-bridge.sh @@ -6,6 +6,7 @@ set -euo pipefail # Name for this node or `bridge-0` if not provided NODE_ID="${NODE_ID:-0}" SKIP_AUTH="${SKIP_AUTH:-false}" +CONTAINER_NAME="${CONTAINER_NAME:-bridge-$NODE_ID}" NODE_NAME="bridge-$NODE_ID" # a private local network P2P_NETWORK="private" @@ -19,6 +20,7 @@ NODE_JWT_FILE="$CREDENTIALS_DIR/$NODE_NAME.jwt" # directory where validator will write the genesis hash GENESIS_DIR="/genesis" GENESIS_HASH_FILE="$GENESIS_DIR/genesis_hash" +TRUSTED_PEERS_FILE="$GENESIS_DIR/trusted_peers" # Wait for the validator to set up and provision us via shared dir wait_for_provision() { @@ -53,6 +55,44 @@ write_jwt_token() { celestia bridge auth admin --p2p.network "$P2P_NETWORK" > "$NODE_JWT_FILE" } +append_trusted_peers() { + peer_id="" + start_time=$(date +%s) + timeout=30 + + while [[ -z "$peer_id" ]]; do + peer_id=$(celestia p2p info | jq -r '.result.id' || echo "") + if [[ -z "$peer_id" ]]; then + echo "Node is not running yet. Retrying..." + sleep 1 + fi + + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + if [[ $elapsed -ge $timeout ]]; then + echo "Failed to retrieve Peer ID after $timeout seconds. Exiting." + exit 1 + fi + done + + #multiaddr: /dns/$CONTAINER_NAME/tcp/$RPC_PORT/p2p/$peer_id + multiaddr="/dns/$CONTAINER_NAME/tcp/2121/p2p/$peer_id" + echo "Appending trusted peer: $multiaddr" + + # Read existing peers into a variable + existing_peers="" + if [[ -s "$TRUSTED_PEERS_FILE" ]]; then + existing_peers=$(cat "$TRUSTED_PEERS_FILE") + fi + + # Append the new multiaddr to the existing peers + if [[ -n "$existing_peers" ]]; then + echo "$existing_peers,$multiaddr" > "$TRUSTED_PEERS_FILE" + else + echo "$multiaddr" > "$TRUSTED_PEERS_FILE" + fi +} + main() { # Initialize the bridge node celestia bridge init --p2p.network "$P2P_NETWORK" @@ -64,6 +104,8 @@ main() { add_trusted_genesis # Update the JWT token write_jwt_token + # Append the peer multiaddr to the trusted peers (run in background, as the node needs to be running) + append_trusted_peers & # give validator some time to set up sleep 4 # Start the bridge node diff --git a/ci/run-lightnode.sh b/ci/run-lightnode.sh index 7cecacf1..655cd698 100755 --- a/ci/run-lightnode.sh +++ b/ci/run-lightnode.sh @@ -6,6 +6,7 @@ set -euo pipefail # Name for this node or `light-0` if not provided NODE_ID="${NODE_ID:-0}" SKIP_AUTH="${SKIP_AUTH:-false}" +BRIDGE_COUNT="${BRIDGE_COUNT}" NODE_NAME="light-$NODE_ID" # a private local network P2P_NETWORK="private" @@ -19,6 +20,7 @@ NODE_JWT_FILE="$CREDENTIALS_DIR/$NODE_NAME.jwt" # directory where validator will write the genesis hash GENESIS_DIR="/genesis" GENESIS_HASH_FILE="$GENESIS_DIR/genesis_hash" +TRUSTED_PEERS_FILE="$GENESIS_DIR/trusted_peers" # Wait for the validator to set up and provision us via shared dir wait_for_provision() { @@ -26,8 +28,35 @@ wait_for_provision() { while [[ ! ( -e "$GENESIS_HASH_FILE" && -e "$NODE_KEY_FILE" ) ]]; do sleep 0.1 done - echo "Validator is ready" + + echo "Waiting for $BRIDGE_COUNT bridge nodes to start" + start_time=$(date +%s) + timeout=30 + + while true; do + if [[ -e "$TRUSTED_PEERS_FILE" ]]; then + trusted_peers="$(cat "$TRUSTED_PEERS_FILE")" + comma_count=$(echo "$trusted_peers" | grep -o "," | wc -l) + if [[ $comma_count -eq $((BRIDGE_COUNT - 1)) ]]; then + echo "$BRIDGE_COUNT bridge nodes are ready" + break + else + echo "Trusted peers file does not contain the expected number of commas. Retrying..." + fi + else + echo "Trusted peers file does not exist yet. Retrying..." + fi + + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + if [[ $elapsed -ge $timeout ]]; then + echo "Timeout reached. Exiting." + exit 1 + fi + + sleep 1 + done } # Import the test account key shared by the validator @@ -43,11 +72,19 @@ add_trusted_genesis() { # Read the hash of the genesis block genesis_hash="$(cat "$GENESIS_HASH_FILE")" + trusted_peers="$(cat "$TRUSTED_PEERS_FILE")" # and make it trusted in the node's config echo "Trusting a genesis: $genesis_hash" sed -i'.bak' "s/TrustedHash = .*/TrustedHash = $genesis_hash/" "$CONFIG_DIR/config.toml" } +add_trusted_peers() { + local trusted_peers="$(cat "$TRUSTED_PEERS_FILE")" + local formatted_peers=$(echo "$trusted_peers" | sed 's/\([^,]*\)/"\1"/g') + echo "Trusting peers: $formatted_peers" + sed -i'.bak' "s|TrustedPeers = .*|TrustedPeers = [$formatted_peers]|" "$CONFIG_DIR/config.toml" +} + write_jwt_token() { echo "Saving jwt token to $NODE_JWT_FILE" celestia light auth admin --p2p.network "$P2P_NETWORK" > "$NODE_JWT_FILE" @@ -62,6 +99,8 @@ main() { import_shared_key # Trust the private blockchain add_trusted_genesis + # Trust the bridge nodes + add_trusted_peers # Update the JWT token write_jwt_token # give validator some time to set up diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index 555e7bc0..81aeb5e6 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -34,11 +34,11 @@ async fn test_light_client_prover_talking() -> Result<()> { pretty_env_logger::init(); let bridge_cfg = CelestiaConfig { - connection_string: "ws://0.0.0.0:36658".to_string(), + connection_string: "ws://localhost:36658".to_string(), ..CelestiaConfig::default() }; let lc_cfg = CelestiaConfig { - connection_string: "ws://0.0.0.0:26658".to_string(), + connection_string: "ws://localhost:46658".to_string(), ..CelestiaConfig::default() }; diff --git a/elf/riscv32im-succinct-zkvm-elf b/elf/riscv32im-succinct-zkvm-elf index c3d1060ad304cbb599b38b0cfbd947faa834356e..ccb2b931fe455d1070ae2e941a6451392a28fd93 100755 GIT binary patch delta 83441 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 delta 83177 zcmZtP4}6x>`@r$@Jgrt)we-i9Xv*Y1?bDe+pIp;q2{ruTl^5wOgH(Wa{FEQcf z{E7LAiTTNi`E@4dXH3kmJ2AiBqWsK^7IiPb>dGP4M1`kio)L{{kr{bcw94$e_>z{H zhtx3%(NDRVr&!w z;@|T#FUSlP9JzSPL7B}GHrqGHXLgFVcFgP=CoaxAIP+kiVR1>P%pKZO(oc@26=pUz zwMEp{GO8%dJk1v_J~VT;7A`(K^TdX=TUz|yv6+9@ZhuUu@`&i3;>=#wd!L|Pi_#}d ze-Dc8I3aVk$+Y&t=@!ZU7ZL=HB&HPHo zT{7JE+xeONSD*hH@Lxmz+l&9Q_^%QFHRiu2{FlvtP5Ezc{%gj6&H1kd|Fz`5eHQ}9x5t30>fmp|>Egez#B=Qg`^JmvPfH{t5v5lb+q?SSt;!OvJU`FPs~0!$1TO24=LYx&51XEF&Aa8f;oh&~ zC8oY=y!~K0ly2|vguCND&hYR&*DWKiaGxmQ#vQ{M`U+;@Nyjk)-WiMAeVH@0e(I(P z2{+?R&cvVD$G0Y2@o9N(x%Xw06Rz}>;I6kP-0YL{+(sWy+`(v{l;`?*e}KE?hZD`7 zl5p8r2jXm8+CR^Y(vh1?!T?iXHLh=b_niqhx@YL%;ddq6gdTaW!Z-LHcBkjL8Ghss zVJasa5E{H1SJ2>?xIuT&jD*{CR-S8}b}HdUkx)f~OpUCa_4hL(bRY-I)Kxx^aLe(L z-bD{4+;f3T#CE-{6(`^8l3P@O#7_##}Y21Z=Ne8 zL44Qa30H)Ry+6Y}=>TgrJtB9{VFZW|_VF)pu^EBZPcY|a=cUhB>YMT;BZOI+>H2@i zMX^)qgiGeKXh_g0n2w8akuT8cDb@gv=Pcpw$J6j&zXldO%^dg3bJKkOTb^OaO^4pb ztIo}H-F^O1^C+K=r{Xhuk%ZyrgbGi4HsRW}ZtvJW(r0`p&S1!O3O>ReiL+g#<4vDq z(P6#3&&E|)M{GK7PxDg$HCU!V7Ig(4z?7!6g2NXi+%OWf1J~h=(nDY1Wju{IS6RA( zDtr%a=zaVP2{+S}ACFgIt$!u1z&e8UUre~X{PwAugwseUHdn!2VhU)FKgLVVh%{fA za95LGFSAMbPpqr_ecYcp))8sEDB*@;y(-SarER09%X6L{lEo#NeSV%>%gM-sEy0`d za_=&^WG~2bOT7niMa~`=+>G_tdQhHQ<>Tk#-v0?6xp>i!IZf)Oejd*(j124eZ@eAr zh0%c9SOp&K>&d~Jsh2G=-LHRcV_gr z3ukj1xhT)AU>(Vcp35Nh!FpkpF(^BRaMz$hiEqRc@F?$smuQ*%dEU!#9u4TaPAq29 zLWAx^T!Q0SKRf9rl2B?g9Pt`w%x2-N-yYJ`_yuM=?nUC=B_BA8pvfto_j`adQ3vXs*)o$$!9 zyUfXNQh(^@MQ#}hIR}LT?k%1`#)dugA-seNJJKP!0KdlDu`bs(D-x~~9XQIz2jejv z!lwS{;&y-JOs$(g^JJTD{eN*MbL%+iEAk(bUvure_6?5{hvz%? z9_d#PH}%_u+f|fbv+GR#jtb4%{|I*~%y&6;;*NLP%H6nYe$B2ldhyBzxo6f(-LsCV zIx4^BIi~RstZCzO@FuLg+bp~t=lPNT36~um;%Ag6+$^l|8}SOPOXoSfD>&u8A)(VT zAw%|$jGXZaxXk#!c$M+vxXO4fF6|!0$kwN=7qTYk1(>%H^cBwxYazw=XuY-bFpq0XYF7FjDN#RaG}rt^q+f<&_?Xz z@w04?T{Q{mt8Q*BH~wqS_-Jew+2ghRNi8S--ZOt;jZ^W}aeqxh?Bwn}3yi4cwY7X~ z)t)2rXf4%)Ha^=2;Ho5Pfk|Cx)T zTg|O4<=!Hp;HYqq9k7!MZU|S+xqon3mxjyyWY)k!yu^1PoBMmWTk{=DCp{or>LlGr z8qD_b&KXHJ&iHyfrN&&w%So77laO!?>L%ShZ z#_!_M6Z{CvWqfF6(oJGW=lTZj$E$7$Q;?{ibcGC=KKJzy6QAMpKaEG4_%__yxKo3q z>uG!i9^l?QVFn2@r{Z55M1SPvK77{7^=v?QIWV5FPjTCZdwz*U5q{`YR zUHzBy-Dp3v+i>xl;nvyw0A}`;eAmx!PgmgPN4Ix9{b@Xp7qYSjdspH$OY@z)(3DH3 z1N&NU;#8kL?aT1&uo9cOr%C8XgS~wR{^`89Ui;j}byGJsOuF(9@?9|{rKXj*pnF)u znS0UFM`7PP0#{(&_lDtJ#%0*832S^ME;9ZD4-Zbc_Pm!iGbFeHc#ZL7obhp}@D<$6 zcpDyxb)(I1lyvhs0e!tT6wfjz^aNg7<5bf9NWx4i&?nvjjgxNLC*f9dJ0ARL@KRj3 zmW#*_lS%-`D>(R1)f<0*KlS}vM5Nyz#<6xf9a8h6g-D#m*6y9AG0 zAN(-x^F{Dyxb=oGMMpPfN{wgZPFO}PeeY|yH&+!ya-Ls3eXwSbHSt0%=QP_hep4-P zuH{pk@41G?2FLflxgjCF2tTjodM);>pldBZT+5j)_sl=4mcOX6zxVanXU_u9V7mss zs^um*dk*n6wfuQ4ACtRh{zufbpFZO{t@fOwD{A@kT0UXlJ@Y?P%Pm^(xkgT}<%!;l zvkG&&HAuY%^Ac@c77u_jte0wfv2$fD+tREY z3AYbk)w6y2(K+Fgt_PlN;=OT3uMocmFEsJ7c!hBauQi^CHwUNO9VF~96~2TkLI$@K z7oHRv`~g>)_|JHxiRTP;uKqsZg!adCP5czxye7^=#F->)HW_Zfc{LfhZQ_x}x8k|R zQ*d!kXz(7q-FOBr%MI~|araimXWHWavkZ_U7us^Qim2rHq^6@AW?}%?PF2rTVN8nk;-SP9r zJ@AUOrG0wTn}oF{!x?z1aX-8Zr!PnHr@Xa;6LI|$+s7n+2QD(c8&5Ni@GNn<9Mm@(|EP<0{n^b68ydKGF)!_Hr~;?eN6{Hz`qcW+ouC-NvOwluV?fn&NKcF z7aISFdm2~Ze#U>|p~nB>D~#(7cWz8i-q+R!vq-p^4Cx(^4jt*O@#FBlCVrW>#z)~f zCjOwe#vj8=i0c%7kantl9G4LwL3TbZ@Fm`13e>wSw#FM_*$LInaiQ_PxY)QY9%+0K z9+xr+g(S>1J`yi6J`QijdIpzyYX?SQ*-6#c<2*Jt&HtFU#-G9|*;+MWJ_!R&fiJx^ z{vDRBR{bNMVe&V>JhsO7#j^FP+v2q*|LK>r{v<&&^dp08#p?5MHanU6B3xj68SZ90 z3imO-9uGFY3Cqr?^-a8-^*6yJOd&%LA9gO&-HXFfS^Zf@o*UTv^(A%Q!8`|GeM$We zUY_07O{p8-mN$1|`)$&;=5^3HhwyC8Rz8wSBjcjpsi!Er+c&w z56EhpUNq9fqmSbCi}7SU1{e7JALE%Oo_jP8@3_8?UxXJLKZBRzj5wZhJ4sj`65R2} z&||D`9!|!c8U=rf%W#=LqvN|LT?d9(J9OKzJj$}OrB^*qRwo?K6!5$tmJ@mz55)1i zFx>x(imA|KxCzS>j}};ky<i{%MNquoiaq?RBuMbxZNrA*m%EF_gpiV*75>u7qL5SPd`v|S#$ko zxXS9+NT|PG+xRsxPdr<_>5p{DZNpW>MUW?^2lK7W+ecMpsCcMkU_ZsP3PVbQa2t4JGkpCDg z9f%LE3M?J)_ur&DnFQ&8W;h)`i`$VP9T?-S@td%8P(1-}#I1e)=e;$)2up|6uVUAm z`+rM*N&{QtgqjT7v7AwSB;9YghnH;4yCi?jQCa=3u}o?H70zXK4kK_N&NKCO!UKDo z`~P7i3^f_L;d#c#VcUV8I2@JL(+k;H$e*4v7V&y4<#kc7Ixd;+&_cg)Z91KMNs0_| z^N>}4z!}MO2YP#-Y8sF;x}sJGt}$^(e5|qLpNW@u2_x_*o_%V2H(Tmw#CPFT@6$p; z-z#~BGY^Raa3Av+J_s+R!|8A2P{CyHcodRuD*k|Y`l|%QU-8!Xa=g*Rw_VBllLXCB zNrr7$p8;E66#wXxb#PuqB{wmhLBxr_3WHz#hr;a^zo)_2x5)zgS+8IKHkGyUJ7p|NjZxVOfy#P<`|%l3Y6jX#2) zC$1fQ&s*bb@Tx!i1Uyv?|&rSUf0G6nxQG4ZW`$Bt??dM zb~1HuEE}2n3~aaRemESJ)tNj6%g)$c?tk)7_#cx&w$2-I6&Wg$@%=vgy4Z0g3D*K| zKP+smt;BSoCmG~~dg9i{g!m~w9+ywLGjO_t@%=Z`olQbGDyz4*$c`-BmhfCn1ij)&r!`~Nf&W||E5;n~JB@h0QPaQY0= zz0P!VaX2cg&pwmS>gZ5TGVbubSjuOUNp5y)G3}*lZkH`exP6jAZ=Y@Y^Gt|4`T~Qo z^jMzTrGiJXbWpzt^qlE{jL1iL_6cD`*5THt`wlQg)wrS$_kZ2#4!T|v!jN^r|6+Zj zInO&j%cQ#yONHuT*pA2u9FEHBtBs|D_u!0FkI=)2gd*dac%bR=+jyvnzmG=}*PGQ& zTx#Nf<0|87+^=V7ukIMGzfR^1nvgKcWXQoKXS8?waT>C!PsCG9ybqpprvFkJ@$2zi z6EDSYQC?n}N%?2+3KL&AhV^#>6`bKSd_#s!CPO)%Nd?V)yxCaJ$i!RY`NVanI~nKn z3TIe?_oIHjm;4VGnfQdUtp6*hpo?$dc`}rk3`_72#ATM{48F%BP5fuvmFMm3oZQ|#qNH~xT`}z#yv7M4hSk^#%B;8$D=3HHd#~43=#~IJUYmJ}4 zn~hWRNXX&~f$>q-y?}R^3`=kZBcKhwjth-f;9}$Vu&f!)zXr<)t3SgsrQSUMksx!Z z8NS1^Xw>Ccra=7*mLXT~z%s|`zp;#{+Wn6YQjF_inUeV6{+~sHtYR&&H-61H2RAPX z@%^#A&I@ojDyyG9i&xK#C_6P9Rb4l&FE7{g!WVQu!BSs(CzRL!&CllLFxP1z1!QkO z70ZwxoUVX3ld(*RZk?;KOo944c0!p#URR_Wy!ITnSCjuGEOV~;|HK)-z4Yt<A-#ilIc0s0vBRgR0Dj6W?>nje%>9;2*}QM5?;KfEzTedX*tGNmm|erfM0 zyn0Am-v6Z*nWLM1LR@LW-HNNoQ0(JNy*2&@Ue1W5*9bLi!-Z!Bx44NDxG0=JD=hir zBkA(+niB5+I^?Iu2{jo?@TxYUz&ThN&ZtwcYKyf_ZOB9sjIPcNL{y-m)rPI5~Th@Z;c;;Q_@3C=uU$4P~8LD zA?}UCQCWS4u?+F$xWbIURXAg47?IIWdKsP+k_&8j=F~ob~C1#3F#XV}y zFw>Rba8#zM&oK#d#{b0wO@-Iv*58H(#^KH12j7gVwglgX2mcU!C+<S(xa7~aQk`6X3HL4u?~y?tqkqR6v2L~dPly|k5;DjI_<7>Ifsk(S4DWQ9ct5<5 zI5U!t-{h_F30OKQ8YGFPVZb zaQcY%pUUcQG=Wr^g59+WBqsU-=^wmq z8cTs|e1W(dnQn}UOa2>;CI17p^3O7H$^S&`xP2+`R;>c>)++FkvE<)gEB|jME)DE5 zmi#Sl-E)NY#eV+ffB3*4ogn`oPro5?8f=Me`PMibmDO#HrTlStoGE|eZCwAhf>TTZ>A;!BQo#sp^IvV^lK*;R$$xLH z{P&x< zg$Emt!K01G;|a!7@QmP;yN`s`A;HbUn~a~vJB%0N`u~J8d>ywoUWE&dKfyhXH{t;` z=Kb%jB#f*{;MXj1sqx>q%s6B6ozf`yS<;sq-%wHoA?2^L@oFKP9zM_ z1l$!5Ha-pyH$EAUGA_YmjL*ZR#uwp9#v|}FZ`OapT}#3YlOcs?8BfM@jqk>y5v_n~cB5yNrLvRmOi}I|cvXjOs8&b#BM~ zWQx+)f6_G~p@YfL5-%k~X1aSxSB#5H{1n`~e~6F9#U_3uK9sn=w>%e@nD_!*pLnDA z{F81Y34=|BEqIjiFSrE-^a&~V4(8az+u~`)2jllRfrkDB`r}z9J_sLAeW?aM!(<<&vWm692UvD4^`}@iCiNFst|Ij} zSaw477AzZ&cgk%eLAFxOP>Id%~^EyNRSbD$Xnv9|D=1IgjQro z`V1d>O9qLr#jhGStQEaZd*-U9%_^K)RdR|U#6>1 zLO3d`8yic3j-fz0|Dh%>PtDzoC4awK`Tt|$l7DcGXZ>l|*KNq3hvW>Wnz+>W3?60bn~%$lU&2*lIe(dzr6kxPeG`YHvU-)VG_b8!{z?;< z4*qE@`CHw)XZ>xk9C7<(DDVlACQbj-o!fCJ1)=P3&15A=KkN61Um)C z;BZt{pI|HvTv99l6(%kn8f`53AHtm)9^lHk%;W=zfAGK*J&ccpt25oX;(EaiaCv5M z6I{PRa0@IIYWaO}^G3n>xQNSke=^8*-v^JvdwXAxQ+-W_$4D5Dg6)WI!{Ml`uAHvxk5n0=M)&blYtCSA-0j@(4A>oaFunx4CI`4p z{;k&sz2ipsb$(pmS8P>q&_J{f*qnhI2@JLeT`*=uEbNCg$`a*tNa*~Ul!qO!}%Hx`FFe? zZ&Wv$xYU=rpM>6~M~~F1aE>V;6+UAu6?}$EKM5WB5-&C0geOsd0cRy6m^FjD>!&sO zQ*Lh(Btv{8T@JP*vOf+78*d)A$Sm;SJNTx|C2~qQCz{=rde%q&S0dn& z>@Jt{U0u#7BN_V+EN3*SPVDVi8cYvyl0`WpnLdG8zP^p7K^d{k`&p#_#wW!3Pq-{e z7!dCNN8lo#LE_zUYvS}LJ%^Xz<-`lI>}1b*$7jXgc)%5={42PVDZd%#Ri`^3`+vgy zM1pi6K6vehr9t%{z5;a>?r!4A2Y7L5TptfUFO+YL?G!Y}@geOqBoz#qoH4Hv(^JrB z6bliz_U@16jM8V!6y1U43^M!_lw8Bd?{r`m`Q?Mfd$DvNJ!1GXEFHYX$6IL!q?Il%{U*?D~bPgnIy5~@sr z_wcBKP{BtMZx_5)Drg_P0rQ6`{AniKCfwS1E6(Z|;@fd?>X4A|8wpbm4c>*z?2uwP zgZSVPd!}=Xj2q(S{|WJITwvT1ml(IksWB#@EeVs1J77EHopCrSt8+&)W#sQbqOAI* zScbe9%M@OB9p4=$u2;blEFC)7=l|=vWGdZb&2aSfY{#a+I4oy)fiF;GIv{g=A#Qzm z7_nh^>2bj$@Gh;7MSCBffz$QjH)eAENkV)i-8;U3`U7l7;8Prq%IYtSr9&AH?U}zJ z4hNs07)$;`QzX8R!&}V|<@Fak9SL*Ekbxz` zG2W0P;fiPR#6r9XN&ISW$iZbci-%a#p)b58D)FypaXTg+AN+|X+^1LUlw|mcgq(q4 zj#@m*MrK@$d*e>jAVWF=ulE&5%GJ2!q|m_iSVkZ|lI})qKLxu5FCjiCKL4b9)+cCz z7qN6m{W6vgsh8nyrh`9uYrFzWht+>zJH=HvJ~)55tdfs;^F!wN0KA?v%EL0`BfR5W z{Ovfri@1!4#6R%X_$RpFoeOqkrIlS036&{Pn^rnaDBjI}B zo>VBSSUPY8E;OEjho2JiKaAys;)Ad1$>jThk!#1HfFLEai4inA{8eoD_DVt6VLbY zU%fTH6Hg|-pO5D~8C&BA;wMZ8&Jw3c&Oa@fLGgf0CdX_9a0Ul{z2GqEWZUvPRU0U|B@!!?7$<^-)+BvHE!2 zy>)P}xvc*@GBhEFEQ(QN5Nm>XYy4R(i&Xt0ma9VjGM1}Cy=*S) zuZO3+skD*|;i#;B-&ppApYVKExm<>F-4{I-J1)azjF%G6^YL4}H9iS%H03`@`2@}I zIW9Ms(MH^TaQMKW-qXC_e`>hQ_QEU788yfKO#b6>^V359lW~!8>P!+Iq(Ck`kSVwU z_cR&C43dZ6x6CaOf8sCBKh}?~5nRtZj_t6pI^Z$oPC^tj+B<@+0fxmZ$M{)-B zeFwh8qfLA>UPxS@aGK1c116q>XPfwmxcTY619JcGLqZ2l!2NM|<3V_gsbDgmVdD4V z6~ue{4!(|;n)oW*;S4?Fg!=^R3A6tBO=S|6w+WZU*EowI*PBKCXJb=8N76OMxy0Kd zSu@?d(_wrZK8AS0$A^1s{7Ni~nnU(~Ufq%)t2RDZgjg1xx)jSIQ{ReZ(W<9lSwrf3 zu&f#N3@nRK{V*Pa8%q0f2JiX=&F~SHIaja4qj8c1$?u+vt?_zTrbwNICt;O!hd@S;>m_=ALzCPNjL2DCtOe%ye% zK9&a5jj?oC-5g7Y)vd6dvV834U$RtIFCNQZy`X|@Brm6v|Ksgae5Q8?Dc}zo@}9LS zJjKMNzB7#_{|G$0B#iLYI91U%BwSCz1T(~s;@)S6_>;KEoYAvBf83{}dlB0+d>MzM zvid|$NIKAm3rOA{$Q#G&f2pv9TynJhFd?pN7{OpM~wdaV{=1Q*;4--}qwOi4MgFe`)11666dt;VLX= zpuQH%8L0n9M*UmNQZxj7OX2_(QRsq9buQDy!!+f-*vdB+7MvY>21QFSpODRl$WJ14a01 zwXqEO&3N^|a7MS{F?29Jr<~Ay@3<35_Y$@pT#Dl(egCPf{)??wD%2jnS(71tKj3Rz zWg7e*Z@M6Kpd8N~6#O%`4gQY9!MEvYPx7yl_GL)kW^0rRS9$MZ$fUu|-hY`2WCWTo z;?3y`LxZ_^h4KE_cHjUUj>_tTywm3|74{;b%2aS_tqRVpRY5;vDL z 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 diff --git a/justfile b/justfile index 322cc7ce..6d5dfdc8 100644 --- a/justfile +++ b/justfile @@ -8,6 +8,9 @@ celestia-up: echo "Cleaning up any existing Docker resources..." docker-compose -f {{DOCKER_COMPOSE_FILE}} down -v --remove-orphans + echo "Building Docker images..." + docker-compose -f {{DOCKER_COMPOSE_FILE}} build + echo "Spinning up a fresh Docker Compose stack..." docker-compose -f {{DOCKER_COMPOSE_FILE}} up -d --force-recreate --renew-anon-volumes @@ -65,7 +68,9 @@ integration-test: just celestia-up echo "Running integration tests..." - cargo test -p prism-tests --lib --release --features mock_prover + if ! cargo test -p prism-tests --lib --release --features mock_prover; then + echo "Integration tests failed." + fi just celestia-down @@ -86,10 +91,19 @@ unit-test: cargo test --lib --release --features "mock_prover" -- --skip test_light_client_prover_talking coverage: - @echo "Generating coverage report..." - cargo llvm-cov nextest --html --output-dir coverage_report --lib --features "mock_prover" --release --workspace --exclude prism-cli --exclude-from-report prism-sp1 --ignore-filename-regex sp1 - @echo "Coverage report generated in 'coverage_report' directory" + #!/usr/bin/env bash + set -euo pipefail + just celestia-up + + echo "Generating coverage report..." + if ! cargo llvm-cov nextest --html --output-dir coverage_report --lib --features "mock_prover" --release --workspace --exclude prism-cli --exclude-from-report prism-sp1 --ignore-filename-regex sp1; then + echo "Coverage report generation failed." + else + echo "Coverage report generated in 'coverage_report' directory" + fi + + just celestia-down install-deps: #!/usr/bin/env bash @@ -139,13 +153,11 @@ install-deps: echo "Protobuf is already installed."; \ fi - if ! command -v cargo prove > /dev/null; then \ echo "Installing SP1..." curl -L https://sp1.succinct.xyz | bash; \ source ~/.bashrc || source ~/.bash_profile || source ~/.zshrc; \ - echo "Running sp1up to install SP1 toolchain..." sp1up @@ -159,4 +171,13 @@ install-deps: echo "SP1 is already installed."; \ fi + for tool in cargo-udeps cargo-llvm-cov cargo-nextest; do \ + if ! command -v $tool > /dev/null; then \ + echo "Installing $tool..."; \ + cargo install $tool; \ + else \ + echo "$tool is already installed."; \ + fi; \ + done + echo "All dependencies installed successfully!" From ed8fff469ff35db6d44394bbdff48aca6bf7eed0 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:50:39 +0100 Subject: [PATCH 02/14] fix: race condition Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- ci/run-bridge.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ci/run-bridge.sh b/ci/run-bridge.sh index 471e2ac7..cd836ca5 100755 --- a/ci/run-bridge.sh +++ b/ci/run-bridge.sh @@ -79,6 +79,10 @@ append_trusted_peers() { multiaddr="/dns/$CONTAINER_NAME/tcp/2121/p2p/$peer_id" echo "Appending trusted peer: $multiaddr" + # Lock the file to prevent race conditions + exec 9>"$TRUSTED_PEERS_FILE.lock" + flock -x 9 + # Read existing peers into a variable existing_peers="" if [[ -s "$TRUSTED_PEERS_FILE" ]]; then @@ -91,6 +95,10 @@ append_trusted_peers() { else echo "$multiaddr" > "$TRUSTED_PEERS_FILE" fi + + # Unlock the file + flock -u 9 + exec 9>&- } main() { From 8cd0a405ff44cea59a4ac9726d3796815d6ee5ad Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:18:15 +0100 Subject: [PATCH 03/14] fix: 0.0.0.0 for github ci Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- crates/da/src/celestia.rs | 2 +- crates/tests/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/da/src/celestia.rs b/crates/da/src/celestia.rs index 2c7b1312..bd333730 100644 --- a/crates/da/src/celestia.rs +++ b/crates/da/src/celestia.rs @@ -43,7 +43,7 @@ pub struct CelestiaConfig { impl Default for CelestiaConfig { fn default() -> Self { CelestiaConfig { - connection_string: "ws://localhost:26658".to_string(), + connection_string: "ws://0.0.0.0:26658".to_string(), start_height: 1, snark_namespace_id: "00000000000000de1008".to_string(), operation_namespace_id: "00000000000000de1009".to_string(), diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index 81aeb5e6..5a754e0c 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -34,11 +34,11 @@ async fn test_light_client_prover_talking() -> Result<()> { pretty_env_logger::init(); let bridge_cfg = CelestiaConfig { - connection_string: "ws://localhost:36658".to_string(), + connection_string: "ws://0.0.0.0:36658".to_string(), ..CelestiaConfig::default() }; let lc_cfg = CelestiaConfig { - connection_string: "ws://localhost:46658".to_string(), + connection_string: "ws://0.0.0.0:46658".to_string(), ..CelestiaConfig::default() }; From 8f39da8930aa7ee131d620a7fb8d4c9402946d1a Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:21:57 +0100 Subject: [PATCH 04/14] Revert "fix: 0.0.0.0 for github ci" This reverts commit 8cd0a405ff44cea59a4ac9726d3796815d6ee5ad. --- crates/da/src/celestia.rs | 2 +- crates/tests/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/da/src/celestia.rs b/crates/da/src/celestia.rs index bd333730..2c7b1312 100644 --- a/crates/da/src/celestia.rs +++ b/crates/da/src/celestia.rs @@ -43,7 +43,7 @@ pub struct CelestiaConfig { impl Default for CelestiaConfig { fn default() -> Self { CelestiaConfig { - connection_string: "ws://0.0.0.0:26658".to_string(), + connection_string: "ws://localhost:26658".to_string(), start_height: 1, snark_namespace_id: "00000000000000de1008".to_string(), operation_namespace_id: "00000000000000de1009".to_string(), diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index 5a754e0c..81aeb5e6 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -34,11 +34,11 @@ async fn test_light_client_prover_talking() -> Result<()> { pretty_env_logger::init(); let bridge_cfg = CelestiaConfig { - connection_string: "ws://0.0.0.0:36658".to_string(), + connection_string: "ws://localhost:36658".to_string(), ..CelestiaConfig::default() }; let lc_cfg = CelestiaConfig { - connection_string: "ws://0.0.0.0:46658".to_string(), + connection_string: "ws://localhost:46658".to_string(), ..CelestiaConfig::default() }; From 3ebe2b571a4d622747c1a8d99cb6bfbe9b888d4f Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:24:05 +0100 Subject: [PATCH 05/14] fix: github CI wait for light node to run Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3009f29f..dd39ea10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,11 @@ jobs: docker compose -f ci/docker-compose.yml logs -f | awk '/Configuration finished. Running a bridge node/ {print;exit}' + - name: Wait for light node to start + run: | + docker compose -f ci/docker-compose.yml logs -f | + awk '/Configuration finished. Running a light node/ {print;exit}' + - name: Run integration tests run: cargo test -p prism-tests --lib --release --features mock_prover From 7cf1cd6c40ac97e28bae1f7b51ea0c090798ec14 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:36:09 +0100 Subject: [PATCH 06/14] fix(ci): wait for each node and show logs Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd39ea10..2c780aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,14 +82,19 @@ jobs: - name: Run the docker-compose stack run: docker compose -f ci/docker-compose.yml up --no-build -d - - name: Wait for bridge node to start + - name: Wait for bridge node 0 to start run: | - docker compose -f ci/docker-compose.yml logs -f | + docker compose -f ci/docker-compose.yml logs bridge-0 -f | tee /dev/tty | awk '/Configuration finished. Running a bridge node/ {print;exit}' - - name: Wait for light node to start + - name: Wait for bridge node 1 to start run: | - docker compose -f ci/docker-compose.yml logs -f | + docker compose -f ci/docker-compose.yml logs bridge-1 -f | tee /dev/tty | + awk '/Configuration finished. Running a bridge node/ {print;exit}' + + - name: Wait for light node 0 to start + run: | + docker compose -f ci/docker-compose.yml logs light-0 -f | tee /dev/tty | awk '/Configuration finished. Running a light node/ {print;exit}' - name: Run integration tests From fb10fdcd5a552bc593cf28d00425c6abf5e9df49 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:42:44 +0100 Subject: [PATCH 07/14] fix(ci): tty not avaiable on github Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c780aed..da8c2b07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,17 +84,17 @@ jobs: - name: Wait for bridge node 0 to start run: | - docker compose -f ci/docker-compose.yml logs bridge-0 -f | tee /dev/tty | + docker compose -f ci/docker-compose.yml logs bridge-0 -f | awk '/Configuration finished. Running a bridge node/ {print;exit}' - name: Wait for bridge node 1 to start run: | - docker compose -f ci/docker-compose.yml logs bridge-1 -f | tee /dev/tty | + docker compose -f ci/docker-compose.yml logs bridge-1 -f | awk '/Configuration finished. Running a bridge node/ {print;exit}' - name: Wait for light node 0 to start run: | - docker compose -f ci/docker-compose.yml logs light-0 -f | tee /dev/tty | + docker compose -f ci/docker-compose.yml logs light-0 -f | awk '/Configuration finished. Running a light node/ {print;exit}' - name: Run integration tests From 91be5a4bf7a91ff3a94772040643aff4421323ae Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:46:13 +0100 Subject: [PATCH 08/14] feat(ci): read logs and sleep Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da8c2b07..f038c068 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,18 +84,21 @@ jobs: - name: Wait for bridge node 0 to start run: | - docker compose -f ci/docker-compose.yml logs bridge-0 -f | - awk '/Configuration finished. Running a bridge node/ {print;exit}' + while ! docker compose -f ci/docker-compose.yml logs bridge-0 | grep -q 'Configuration finished. Running a bridge node'; do + sleep 1 + done - name: Wait for bridge node 1 to start run: | - docker compose -f ci/docker-compose.yml logs bridge-1 -f | - awk '/Configuration finished. Running a bridge node/ {print;exit}' + while ! docker compose -f ci/docker-compose.yml logs bridge-1 | grep -q 'Configuration finished. Running a bridge node'; do + sleep 1 + done - name: Wait for light node 0 to start run: | - docker compose -f ci/docker-compose.yml logs light-0 -f | - awk '/Configuration finished. Running a light node/ {print;exit}' + while ! docker compose -f ci/docker-compose.yml logs light-0 | grep -q 'Configuration finished. Running a light node'; do + sleep 1 + done - name: Run integration tests run: cargo test -p prism-tests --lib --release --features mock_prover From 629a182f660431e1fa4c44dec28911b440b3c731 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:50:47 +0100 Subject: [PATCH 09/14] debug(ci): looking at light logs Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f038c068..29236596 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,9 @@ jobs: sleep 1 done + - name: Show and follow logs of light node 0 + run: docker compose -f ci/docker-compose.yml logs -f light-0 + - name: Wait for light node 0 to start run: | while ! docker compose -f ci/docker-compose.yml logs light-0 | grep -q 'Configuration finished. Running a light node'; do From f21bc98a12af2bd48978c8035e5b3eb5730937a4 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:53:31 +0100 Subject: [PATCH 10/14] debug(ci): sleep 30 and then logs Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29236596..e2078ac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,9 @@ jobs: sleep 1 done + - name: Sleep for 30 seconds + run: sleep 30 + - name: Show and follow logs of light node 0 run: docker compose -f ci/docker-compose.yml logs -f light-0 From b6ad595cc0ab12c7632218b9b06f9a7d25db8d76 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:59:29 +0100 Subject: [PATCH 11/14] debug(ci): only one DA BN Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 14 +++---------- ci/docker-compose.yml | 44 ++++++++++++++++++++-------------------- crates/tests/src/lib.rs | 2 +- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2078ac5..2d435190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,11 +64,6 @@ jobs: "cache-to": ["type=gha,mode=max,scope=bridge-0"], "output": ["type=docker"] }, - "bridge-1": { - "cache-from": ["type=gha,scope=bridge-1"], - "cache-to": ["type=gha,mode=max,scope=bridge-1"], - "output": ["type=docker"] - }, "light-0": { "cache-from": ["type=gha,scope=light-0"], "cache-to": ["type=gha,mode=max,scope=light-0"], @@ -88,15 +83,12 @@ jobs: sleep 1 done - - name: Wait for bridge node 1 to start - run: | - while ! docker compose -f ci/docker-compose.yml logs bridge-1 | grep -q 'Configuration finished. Running a bridge node'; do - sleep 1 - done - - name: Sleep for 30 seconds run: sleep 30 + - name: List running containers + run: docker compose ps + - name: Show and follow logs of light node 0 run: docker compose -f ci/docker-compose.yml logs -f light-0 diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index be2599fd..9c564f95 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -7,7 +7,7 @@ services: dockerfile: Dockerfile.validator environment: # provide amount of bridge nodes to provision (default: 1) - - BRIDGE_COUNT=2 + - BRIDGE_COUNT=1 - LIGHT_COUNT=1 volumes: - credentials:/credentials @@ -34,26 +34,26 @@ services: - credentials:/credentials - genesis:/genesis - bridge-1: - image: bridge - platform: "linux/amd64" - build: - context: . - dockerfile: Dockerfile.bridge - environment: - # provide an id for the bridge node (default: 0) - # each node should have a next natural number starting from 0 - - NODE_ID=1 - # setting SKIP_AUTH to true disables the use of JWT for authentication - - SKIP_AUTH=true - # this must match the service name in the docker-compose.yml file - # used for the trusted peers string for the light node - - CONTAINER_NAME=bridge-1 - ports: - - 36658:26658 - volumes: - - credentials:/credentials - - genesis:/genesis + # bridge-1: + # image: bridge + # platform: "linux/amd64" + # build: + # context: . + # dockerfile: Dockerfile.bridge + # environment: + # # provide an id for the bridge node (default: 0) + # # each node should have a next natural number starting from 0 + # - NODE_ID=1 + # # setting SKIP_AUTH to true disables the use of JWT for authentication + # - SKIP_AUTH=true + # # this must match the service name in the docker-compose.yml file + # # used for the trusted peers string for the light node + # - CONTAINER_NAME=bridge-1 + # ports: + # - 36658:26658 + # volumes: + # - credentials:/credentials + # - genesis:/genesis light-0: image: light @@ -69,7 +69,7 @@ services: - SKIP_AUTH=true # depending on the number of bridge nodes, provide the count # is used for the trusted peers string for the light node - - BRIDGE_COUNT=2 + - BRIDGE_COUNT=1 ports: - 46658:26658 volumes: diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index 81aeb5e6..b6027b43 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -34,7 +34,7 @@ async fn test_light_client_prover_talking() -> Result<()> { pretty_env_logger::init(); let bridge_cfg = CelestiaConfig { - connection_string: "ws://localhost:36658".to_string(), + connection_string: "ws://localhost:26658".to_string(), ..CelestiaConfig::default() }; let lc_cfg = CelestiaConfig { From 69270ca2fecb34b52220a58b23e5a666f979fa7c Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:21:59 +0100 Subject: [PATCH 12/14] debug(ci): should work now Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- ci/docker-compose.yml | 54 ++++++++++++++++++++-------------------- ci/run-bridge.sh | 8 +++--- ci/run-lightnode.sh | 17 ++++++++----- ci/run-validator.sh | 6 ++--- 5 files changed, 46 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d435190..f963f94d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: run: sleep 30 - name: List running containers - run: docker compose ps + run: docker compose -f ci/docker-compose.yml ps - name: Show and follow logs of light node 0 run: docker compose -f ci/docker-compose.yml logs -f light-0 diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 9c564f95..2395c47a 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -7,11 +7,11 @@ services: dockerfile: Dockerfile.validator environment: # provide amount of bridge nodes to provision (default: 1) - - BRIDGE_COUNT=1 + - BRIDGE_COUNT=2 - LIGHT_COUNT=1 volumes: - credentials:/credentials - - genesis:/genesis + - shared:/shared bridge-0: image: bridge @@ -32,28 +32,28 @@ services: - 26658:26658 volumes: - credentials:/credentials - - genesis:/genesis + - shared:/shared - # bridge-1: - # image: bridge - # platform: "linux/amd64" - # build: - # context: . - # dockerfile: Dockerfile.bridge - # environment: - # # provide an id for the bridge node (default: 0) - # # each node should have a next natural number starting from 0 - # - NODE_ID=1 - # # setting SKIP_AUTH to true disables the use of JWT for authentication - # - SKIP_AUTH=true - # # this must match the service name in the docker-compose.yml file - # # used for the trusted peers string for the light node - # - CONTAINER_NAME=bridge-1 - # ports: - # - 36658:26658 - # volumes: - # - credentials:/credentials - # - genesis:/genesis + bridge-1: + image: bridge + platform: "linux/amd64" + build: + context: . + dockerfile: Dockerfile.bridge + environment: + # provide an id for the bridge node (default: 0) + # each node should have a next natural number starting from 0 + - NODE_ID=1 + # setting SKIP_AUTH to true disables the use of JWT for authentication + - SKIP_AUTH=true + # this must match the service name in the docker-compose.yml file + # used for the trusted peers string for the light node + - CONTAINER_NAME=bridge-1 + ports: + - 36658:26658 + volumes: + - credentials:/credentials + - shared:/shared light-0: image: light @@ -69,12 +69,12 @@ services: - SKIP_AUTH=true # depending on the number of bridge nodes, provide the count # is used for the trusted peers string for the light node - - BRIDGE_COUNT=1 + - BRIDGE_COUNT=2 ports: - 46658:26658 volumes: - credentials:/credentials - - genesis:/genesis + - shared:/shared volumes: # local volume where node's credentials can persist @@ -84,8 +84,8 @@ volumes: type: "none" o: "bind" device: "./credentials" - # a temporary fs where the genesis hash is announced - genesis: + # a temporary fs where the nodes share data + shared: driver_opts: type: tmpfs device: tmpfs diff --git a/ci/run-bridge.sh b/ci/run-bridge.sh index cd836ca5..8bd125c1 100755 --- a/ci/run-bridge.sh +++ b/ci/run-bridge.sh @@ -17,10 +17,10 @@ CREDENTIALS_DIR="/credentials" # node credentials NODE_KEY_FILE="$CREDENTIALS_DIR/$NODE_NAME.key" NODE_JWT_FILE="$CREDENTIALS_DIR/$NODE_NAME.jwt" -# directory where validator will write the genesis hash -GENESIS_DIR="/genesis" -GENESIS_HASH_FILE="$GENESIS_DIR/genesis_hash" -TRUSTED_PEERS_FILE="$GENESIS_DIR/trusted_peers" +# directory where validator will write the genesis hash and the bridge node their peers addresses +SHARED_DIR="/shared" +GENESIS_HASH_FILE="$SHARED_DIR/genesis_hash" +TRUSTED_PEERS_FILE="$SHARED_DIR/trusted_peers" # Wait for the validator to set up and provision us via shared dir wait_for_provision() { diff --git a/ci/run-lightnode.sh b/ci/run-lightnode.sh index 655cd698..1fb880ff 100755 --- a/ci/run-lightnode.sh +++ b/ci/run-lightnode.sh @@ -17,10 +17,10 @@ CREDENTIALS_DIR="/credentials" # node credentials NODE_KEY_FILE="$CREDENTIALS_DIR/$NODE_NAME.key" NODE_JWT_FILE="$CREDENTIALS_DIR/$NODE_NAME.jwt" -# directory where validator will write the genesis hash -GENESIS_DIR="/genesis" -GENESIS_HASH_FILE="$GENESIS_DIR/genesis_hash" -TRUSTED_PEERS_FILE="$GENESIS_DIR/trusted_peers" +# directory where validator will write the genesis hash and the bridge node their peers addresses +SHARED_DIR="/shared" +GENESIS_HASH_FILE="$SHARED_DIR/genesis_hash" +TRUSTED_PEERS_FILE="$SHARED_DIR/trusted_peers" # Wait for the validator to set up and provision us via shared dir wait_for_provision() { @@ -36,9 +36,14 @@ wait_for_provision() { while true; do if [[ -e "$TRUSTED_PEERS_FILE" ]]; then + + echo "Trusted peers file exists" + trusted_peers="$(cat "$TRUSTED_PEERS_FILE")" - comma_count=$(echo "$trusted_peers" | grep -o "," | wc -l) - if [[ $comma_count -eq $((BRIDGE_COUNT - 1)) ]]; then + echo "Trusted peers: $trusted_peers" + peer_count=$(echo "$trusted_peers" | tr ',' '\n' | wc -l) + echo "Peer count: $peer_count" + if [[ $peer_count -eq $BRIDGE_COUNT ]]; then echo "$BRIDGE_COUNT bridge nodes are ready" break else diff --git a/ci/run-validator.sh b/ci/run-validator.sh index 88a49779..35ce5f4b 100755 --- a/ci/run-validator.sh +++ b/ci/run-validator.sh @@ -18,9 +18,9 @@ BRIDGE_COINS="200000000000000utia" VALIDATOR_COINS="1000000000000000utia" # a directory and the files shared with the bridge nodes CREDENTIALS_DIR="/credentials" -# directory where validator will write the genesis hash -GENESIS_DIR="/genesis" -GENESIS_HASH_FILE="$GENESIS_DIR/genesis_hash" +# directory where validator will write the genesis hash and the bridge node their peers addresses +SHARED_DIR="/shared" +GENESIS_HASH_FILE="$SHARED_DIR/genesis_hash" # Get the address of the node of given name node_address() { From a5170eba73b1277a9e2f415727dd66827061ead2 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:24:42 +0100 Subject: [PATCH 13/14] fix(ci): not beeing stuck Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f963f94d..3cedb085 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,14 +83,11 @@ jobs: sleep 1 done - - name: Sleep for 30 seconds - run: sleep 30 - - - name: List running containers - run: docker compose -f ci/docker-compose.yml ps - - - name: Show and follow logs of light node 0 - run: docker compose -f ci/docker-compose.yml logs -f light-0 + - name: Wait for bridge node 1 to start + run: | + while ! docker compose -f ci/docker-compose.yml logs bridge-1 | grep -q 'Configuration finished. Running a bridge node'; do + sleep 1 + done - name: Wait for light node 0 to start run: | From 4364107f4c13ee4bbadd4a5a906024f244f04737 Mon Sep 17 00:00:00 2001 From: Smuu <18609909+Smuu@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:05:47 +0100 Subject: [PATCH 14/14] fix(ci): caching Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cedb085..f038c068 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,11 @@ jobs: "cache-to": ["type=gha,mode=max,scope=bridge-0"], "output": ["type=docker"] }, + "bridge-1": { + "cache-from": ["type=gha,scope=bridge-1"], + "cache-to": ["type=gha,mode=max,scope=bridge-1"], + "output": ["type=docker"] + }, "light-0": { "cache-from": ["type=gha,scope=light-0"], "cache-to": ["type=gha,mode=max,scope=light-0"],