diff --git a/.github/workflows/build_ledger_wallet.yml b/.github/workflows/build_ledger_wallet.yml index 8cc0caae28..e44bf3c0bc 100644 --- a/.github/workflows/build_ledger_wallet.yml +++ b/.github/workflows/build_ledger_wallet.yml @@ -12,16 +12,30 @@ name: Build minotari_ledger_wallet - cron: "05 00 * * *" workflow_dispatch: +env: + TS_FILENAME: "minotari_ledger_wallet" + SHARUN: "shasum --algorithm 256" + +concurrency: + # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} + permissions: {} jobs: - build: + builds: name: building ${{ matrix.ledger_target }} - continue-on-error: true + continue-on-error: ${{ matrix.best_effort || false }} strategy: fail-fast: false matrix: - ledger_target: [nanox, nanosplus] + include: + - ledger_target: nanos + best_effort: true + - ledger_target: nanox + best_effort: true + - ledger_target: nanosplus permissions: packages: write @@ -44,16 +58,67 @@ jobs: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder \ cargo ledger build ${{ matrix.ledger_target }} - - name: List ledger firmware + - name: env Setup + env: + SHA_SHORT: ${GITHUB_SHA::7} + shell: bash + run: | + BINFILE="${TS_FILENAME}-${{ matrix.ledger_target }}-${{ github.ref_name }}-${{ env.SHA_SHORT }}" + echo "BINFILE=${BINFILE}" >> $GITHUB_ENV + + - name: archive ledger firmware for ${{ matrix.ledger_target }} shell: bash run: | + # set -xo pipefail + mkdir -p "${GITHUB_WORKSPACE}/dist" + cd "${GITHUB_WORKSPACE}/dist" + echo "Copying files for ${{ env.BINFILE }} to $(pwd)" ls -la ${{ github.workspace }}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/ + cp -vf ${GITHUB_WORKSPACE}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/*.json . + cp -vf ${GITHUB_WORKSPACE}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/key_14x14.gif . + cp -vf ${GITHUB_WORKSPACE}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/minotari_ledger_wallet.* . + echo "Compute files shasum" + ${SHARUN} * >> "${{ env.BINFILE }}.sha256" + echo "Show the shasum" + cat "${{ env.BINFILE }}.sha256" + echo "Checksum verification for files is " + ${SHARUN} --check "${{ env.BINFILE }}.sha256" + 7z a "${{ env.BINFILE }}.zip" * + echo "Compute archive shasum" + ${SHARUN} "${{ env.BINFILE }}.zip" >> "${{ env.BINFILE }}.zip.sha256" + echo "Show the shasum" + cat "${{ env.BINFILE }}.zip.sha256" + echo "Checksum verification archive is " + ${SHARUN} --check "${{ env.BINFILE }}.zip.sha256" - name: Artifact upload for ${{ matrix.ledger_target }} uses: actions/upload-artifact@v4 with: - name: minotari_ledger_wallet-${{ matrix.ledger_target }}-${{ github.ref_name }}-${{ github.sha }} - path: | - ${{ github.workspace }}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/*.json - ${{ github.workspace }}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/key_14x14.gif - ${{ github.workspace }}/applications/minotari_ledger_wallet/wallet/target/${{ matrix.ledger_target }}/release/minotari_ledger_wallet.* + name: ${{ env.BINFILE }} + path: "${{ github.workspace }}/dist/${{ env.BINFILE }}.zip*" + + create-release: + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + needs: [ builds ] + + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Download binaries + uses: actions/download-artifact@v4 + with: + pattern: "${{ env.TS_FILENAME }}*" + + - name: Create release + uses: ncipollo/release-action@v1 + with: + artifacts: "${{ env.TS_FILENAME }}*/**/*" + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true + draft: true + allowUpdates: true + updateOnlyUnreleased: true + replacesArtifacts: true diff --git a/applications/minotari_ledger_wallet/wallet/README.md b/applications/minotari_ledger_wallet/wallet/README.md index 39cdce7193..b808c47185 100644 --- a/applications/minotari_ledger_wallet/wallet/README.md +++ b/applications/minotari_ledger_wallet/wallet/README.md @@ -70,27 +70,51 @@ For more information see [LedgerCTL](https://github.com/LedgerHQ/ledgerctl). ## Building It is recommended to build the Ledger application via the official `ledger-app-builder` Docker image, as the Docker -image is properly supported and always kept up to date. +image is properly setup, supported and always kept up to date. ### Option 1: Using Docker Ensure Docker Desktop is installed and running on your machine. -The following command has to be run from the root of the Tari repository: +The following command has to be run from the root of the Tari repository. +Replace ```{TARGET}``` with the appropriate value (e.g., `nanosplus`, `nanos`, etc.). + +Compiled resources will be found in `applications/minotari_ledger_wallet/wallet/target/{TARGET}/release` + +Single all-inclusive command without requiring manual interaction with the Docker container. + +#### Unix/MacOS: +```bash +docker run --rm -it \ + -v ".:/app" \ + -w /app/applications/minotari_ledger_wallet/wallet \ + ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder \ + cargo ledger build {TARGET} ``` -docker run --rm -it -v ".:/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder + +#### Windows: +```DOS +docker run --rm -it -v ".:/app" -w /app/applications/minotari_ledger_wallet/wallet ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder cargo ledger build {TARGET} ``` -This will load the Docker vm where you can now build the ledger app. Navigate to the wallet directory -`applications/minotari_ledger_wallet/wallet` to build. +or +If one would rather run smaller command snippets individually: +* start with running a temporary docker container for building the ledger wallet: +```DOS +docker run --rm -it -v ".:/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder ``` +* change to the folder where the ledger wallet code can be found inside of the temporary docker container: +```DOS +cd /app/applications/minotari_ledger_wallet/wallet +``` +* now build the ledger wallet then exit and close the temporary docker container: +```DOS cargo ledger build {TARGET} +exit ``` -where TARGET = nanosplus, nanos, etc. - **Notes:** - The application has to be installed on the device manually. Please see the _**Manual installation**_ section below. - If any issues are encountered, please try to follow the instructions at diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index 75465d69d6..843f629d2d 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -559,7 +559,7 @@ impl ServiceInitializer for P2pInitializer { .with_node_info(NodeNetworkInfo { major_version: MAJOR_NETWORK_VERSION, minor_version: MINOR_NETWORK_VERSION, - network_byte: self.network.as_byte(), + network_wire_byte: self.network.as_wire_byte(), user_agent: self.user_agent.clone(), }) .with_minimize_connections(if self.config.dht.minimize_connections { @@ -602,3 +602,14 @@ impl ServiceInitializer for P2pInitializer { Ok(()) } } + +#[cfg(test)] +mod test { + use tari_common::configuration::Network; + use tari_comms::connection_manager::WireMode; + #[test] + fn self_liveness_network_wire_byte_is_consistent() { + let wire_mode = WireMode::Liveness; + assert_eq!(wire_mode.as_byte(), Network::RESERVED_WIRE_BYTE); + } +} diff --git a/common/src/configuration/network.rs b/common/src/configuration/network.rs index bf265fa9b1..28dcd5e01f 100644 --- a/common/src/configuration/network.rs +++ b/common/src/configuration/network.rs @@ -49,6 +49,9 @@ pub enum Network { } impl Network { + /// The reserved wire byte for liveness ('LIVENESS_WIRE_MODE') + pub const RESERVED_WIRE_BYTE: u8 = 0xa7; + pub fn get_current_or_user_setting_or_default() -> Self { match CURRENT_NETWORK.get() { Some(&network) => network, @@ -86,6 +89,30 @@ impl Network { LocalNet => "localnet", } } + + /// This function returns the network wire byte for any chosen network. Increase these numbers for any given network + /// when network traffic separation is required. + /// Note: Do not re-use previous values. + pub fn as_wire_byte(self) -> u8 { + let wire_byte = match self { + // Choose a value in 'MAIN_NET_RANGE' or assign 'self.as_byte()' + Network::MainNet => self.as_byte(), + // Choose a value in 'STAGE_NET_RANGE' or assign 'self.as_byte()' + Network::StageNet => self.as_byte(), + // Choose a value in 'NEXT_NET_RANGE' or assign 'self.as_byte()' + Network::NextNet => self.as_byte(), + // Choose a value in 'LOCAL_NET_RANGE' or assign 'self.as_byte()' + Network::LocalNet => self.as_byte(), + // Choose a value in 'IGOR_RANGE' or assign 'self.as_byte()' + Network::Igor => self.as_byte(), + // Choose a value in 'ESMERALDA_RANGE' or assign 'self.as_byte()' + Network::Esmeralda => self.as_byte(), + }; + // The reserved wire byte for liveness ('LIVENESS_WIRE_MODE') is defined in another module, which is not + // accessible from here. + debug_assert!(wire_byte != Network::RESERVED_WIRE_BYTE); + wire_byte + } } /// The default network for all applications @@ -236,4 +263,151 @@ mod test { assert_eq!(Network::try_from(0x24).unwrap(), Network::Igor); assert_eq!(Network::try_from(0x26).unwrap(), Network::Esmeralda); } + + // Do not change these ranges + const MAIN_NET_RANGE: std::ops::Range = 0..40; + const STAGE_NET_RANGE: std::ops::Range = 40..80; + const NEXT_NET_RANGE: std::ops::Range = 80..120; + const LOCAL_NET_RANGE: std::ops::Range = 120..160; + const IGOR_RANGE: std::ops::Range = 160..200; + const ESMERALDA_RANGE: std::ops::Range = 200..240; + const LEGACY_RANGE: [u8; 6] = [0x00, 0x01, 0x02, 0x10, 0x24, 0x26]; + + /// Helper function to verify the network wire byte range + pub fn verify_network_wire_byte_range(network_wire_byte: u8, network: Network) -> Result<(), String> { + if network_wire_byte == Network::RESERVED_WIRE_BYTE { + return Err(format!( + "Invalid network wire byte, cannot be '{}', reserved for 'LIVENESS_WIRE_MODE'", + Network::RESERVED_WIRE_BYTE + )); + } + + // Legacy compatibility + if network_wire_byte == network.as_byte() { + return Ok(()); + } + if LEGACY_RANGE.contains(&network_wire_byte) { + return Err(format!( + "Invalid network wire byte `{}` for network `{}`", + network_wire_byte, network + )); + } + + // Verify binned values + let valid = match network { + Network::MainNet => MAIN_NET_RANGE.contains(&network_wire_byte), + Network::StageNet => STAGE_NET_RANGE.contains(&network_wire_byte), + Network::NextNet => NEXT_NET_RANGE.contains(&network_wire_byte), + Network::LocalNet => LOCAL_NET_RANGE.contains(&network_wire_byte), + Network::Igor => IGOR_RANGE.contains(&network_wire_byte), + Network::Esmeralda => ESMERALDA_RANGE.contains(&network_wire_byte), + }; + if !valid { + return Err(format!( + "Invalid network wire byte `{}` for network `{}`", + network_wire_byte, network + )); + } + Ok(()) + } + + #[test] + fn test_as_wire_byte() { + for network in [ + Network::MainNet, + Network::StageNet, + Network::NextNet, + Network::LocalNet, + Network::Igor, + Network::Esmeralda, + ] { + assert!(verify_network_wire_byte_range(Network::RESERVED_WIRE_BYTE, network).is_err()); + + let wire_byte = Network::as_wire_byte(network); + assert!(verify_network_wire_byte_range(wire_byte, network).is_ok()); + + for val in 0..255 { + match network { + Network::MainNet => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::MainNet.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if MAIN_NET_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + Network::StageNet => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::StageNet.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if STAGE_NET_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + Network::NextNet => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::NextNet.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if NEXT_NET_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + Network::LocalNet => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::LocalNet.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if LOCAL_NET_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + Network::Igor => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::Igor.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if IGOR_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + Network::Esmeralda => { + if val == Network::RESERVED_WIRE_BYTE { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if val == Network::Esmeralda.as_byte() { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else if LEGACY_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } else if ESMERALDA_RANGE.contains(&val) { + assert!(verify_network_wire_byte_range(val, network).is_ok()); + } else { + assert!(verify_network_wire_byte_range(val, network).is_err()); + } + }, + } + } + } + } } diff --git a/comms/core/src/builder/mod.rs b/comms/core/src/builder/mod.rs index 43b78874b0..5cae88e774 100644 --- a/comms/core/src/builder/mod.rs +++ b/comms/core/src/builder/mod.rs @@ -174,7 +174,7 @@ impl CommsBuilder { /// Set a network byte as per [RFC-173 Versioning](https://rfc.tari.com/RFC-0173_Versioning.html) pub fn with_network_byte(mut self, network_byte: u8) -> Self { - self.connection_manager_config.network_info.network_byte = network_byte; + self.connection_manager_config.network_info.network_wire_byte = network_byte; self } diff --git a/comms/core/src/connection_manager/dialer.rs b/comms/core/src/connection_manager/dialer.rs index 0451d8b5b4..bd7315b024 100644 --- a/comms/core/src/connection_manager/dialer.rs +++ b/comms/core/src/connection_manager/dialer.rs @@ -515,7 +515,7 @@ where tokio::select! { _ = delay => { debug!(target: LOG_TARGET, "[Attempt {}] Connecting to peer '{}'", current_state.num_attempts(), current_state.peer().node_id.short_str()); - match Self::dial_peer(current_state, &noise_config, ¤t_transport, config.network_info.network_byte).await { + match Self::dial_peer(current_state, &noise_config, ¤t_transport, config.network_info.network_wire_byte).await { (state, Ok((socket, addr))) => { debug!(target: LOG_TARGET, "Dial succeeded for peer '{}' after {} attempt(s)", state.peer().node_id.short_str(), state.num_attempts()); break (state, Ok((socket, addr))); diff --git a/comms/core/src/connection_manager/listener.rs b/comms/core/src/connection_manager/listener.rs index f5f6b9c684..16e1cd7e7a 100644 --- a/comms/core/src/connection_manager/listener.rs +++ b/comms/core/src/connection_manager/listener.rs @@ -244,7 +244,7 @@ where #[cfg(feature = "metrics")] metrics::pending_connections(None, ConnectionDirection::Inbound).inc(); match Self::read_wire_format(&mut socket, config.time_to_first_byte).await { - Ok(WireMode::Comms(byte)) if byte == config.network_info.network_byte => { + Ok(WireMode::Comms(byte)) if byte == config.network_info.network_wire_byte => { let this_node_id_str = node_identity.node_id().short_str(); let result = Self::perform_socket_upgrade_procedure( &node_identity, @@ -290,7 +290,7 @@ where target: LOG_TARGET, "Peer at address '{}' sent invalid wire format byte. Expected {:x?} got: {:x?} ", peer_addr, - config.network_info.network_byte, + config.network_info.network_wire_byte, byte, ); let _result = socket.shutdown().await; @@ -320,7 +320,7 @@ where "Peer at address '{}' failed to send its wire format. Expected network byte {:x?} or liveness \ byte {:x?} not received. Error: {}", peer_addr, - config.network_info.network_byte, + config.network_info.network_wire_byte, LIVENESS_WIRE_MODE, err ); diff --git a/comms/core/src/connection_manager/mod.rs b/comms/core/src/connection_manager/mod.rs index 3ca92fe339..beb68a4995 100644 --- a/comms/core/src/connection_manager/mod.rs +++ b/comms/core/src/connection_manager/mod.rs @@ -56,6 +56,7 @@ pub(crate) use self_liveness::SelfLivenessCheck; pub use self_liveness::SelfLivenessStatus; mod wire_mode; +pub use wire_mode::WireMode; #[cfg(test)] mod tests; diff --git a/comms/core/src/protocol/network_info.rs b/comms/core/src/protocol/network_info.rs index 90968f8e65..ddcb0551cb 100644 --- a/comms/core/src/protocol/network_info.rs +++ b/comms/core/src/protocol/network_info.rs @@ -30,9 +30,9 @@ pub struct NodeNetworkInfo { /// NOT reject the connection if a remote peer advertises a different minor version number. pub minor_version: u8, /// The byte that MUST be sent (outbound connections) or MUST be received (inbound connections) for a connection to - /// be established. This byte cannot be 0x46 (E) because that is reserved for liveness. + /// be established. This byte cannot be `LIVENESS_WIRE_MODE` (E) because that is reserved for liveness. /// Default: 0x00 - pub network_byte: u8, + pub network_wire_byte: u8, /// The user agent string for this node pub user_agent: String, } diff --git a/comms/dht/examples/propagation/node.rs b/comms/dht/examples/propagation/node.rs index 3e06162a8d..54a492c69e 100644 --- a/comms/dht/examples/propagation/node.rs +++ b/comms/dht/examples/propagation/node.rs @@ -83,7 +83,7 @@ pub async fn create>( .with_node_info(NodeNetworkInfo { major_version: 0, minor_version: 0, - network_byte: 0x25, + network_wire_byte: 0x25, user_agent: "/tari/propagator/0.0.1".to_string(), }) .with_node_identity(node_identity.clone())