diff --git a/.github/actions/git-credentials/action.yml b/.github/actions/git-credentials/action.yml new file mode 100644 index 000000000..921b7702c --- /dev/null +++ b/.github/actions/git-credentials/action.yml @@ -0,0 +1,36 @@ +name: 'Set up git credentials for private UTEXO-Protocol repos' +description: 'Configure git to authenticate with private UTEXO-Protocol repositories' +inputs: + token: + description: 'GitHub PAT with read access to private UTEXO-Protocol repositories' + required: false + default: '' + rgb-lib-token: + description: 'Optional read-only token scoped to UTEXO-Protocol/rgb-lib-bfa' + required: false + default: '' +runs: + using: 'composite' + steps: + - name: Configure git credentials + shell: bash + env: + PRIVATE_REPO_TOKEN: ${{ inputs.token }} + BFA_READ_TOKEN: ${{ inputs.rgb-lib-token }} + run: | + set -euo pipefail + if [[ -z "$PRIVATE_REPO_TOKEN" && -z "$BFA_READ_TOKEN" ]]; then + echo "::error::Private dependency credentials are missing. Configure ORG_READ_TOKEN and/or BFA_READ_TOKEN as described in docs/bfa-ci.txt." + exit 1 + fi + if [[ -n "$PRIVATE_REPO_TOKEN" ]]; then + git config --global url."https://x-access-token:${PRIVATE_REPO_TOKEN}@github.com/UTEXO-Protocol/".insteadOf "https://github.com/UTEXO-Protocol/" + fi + if [[ -n "$BFA_READ_TOKEN" ]]; then + git config --global url."https://x-access-token:${BFA_READ_TOKEN}@github.com/UTEXO-Protocol/rgb-lib-bfa.git".insteadOf "https://github.com/UTEXO-Protocol/rgb-lib-bfa.git" + fi + echo "CARGO_NET_GIT_FETCH_WITH_CLI=true" >> "$GITHUB_ENV" + if ! GIT_TERMINAL_PROMPT=0 git -c http.https://github.com/.extraheader= ls-remote --exit-code https://github.com/UTEXO-Protocol/rgb-lib-bfa.git HEAD >/dev/null; then + echo "::error::CI cannot read UTEXO-Protocol/rgb-lib-bfa. Grant this dependency Contents: read access to ORG_READ_TOKEN, or add a BFA_READ_TOKEN secret. See docs/bfa-ci.txt." + exit 1 + fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 137b2d7d4..7c10d5ec1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,10 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 + - uses: ./.github/actions/git-credentials + with: + token: ${{ secrets.ORG_READ_TOKEN || secrets.RGB_LIB_RUST_LIGHTNING_PAT }} + rgb-lib-token: ${{ secrets.BFA_READ_TOKEN }} - name: Install Rust ${{ matrix.toolchain }} toolchain run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }} @@ -47,6 +51,10 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 + - uses: ./.github/actions/git-credentials + with: + token: ${{ secrets.ORG_READ_TOKEN || secrets.RGB_LIB_RUST_LIGHTNING_PAT }} + rgb-lib-token: ${{ secrets.BFA_READ_TOKEN }} - name: Install Rust ${{ env.TOOLCHAIN }} toolchain run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }} diff --git a/docs/bfa-ci.txt b/docs/bfa-ci.txt new file mode 100644 index 000000000..ebd1aa9fc --- /dev/null +++ b/docs/bfa-ci.txt @@ -0,0 +1,17 @@ +BFA draft CI + +Cargo needs read access to the private UTEXO-Protocol/rgb-lib-bfa repository. +The default GITHUB_TOKEN is limited to the current repository. Configure +ORG_READ_TOKEN with Contents: read access to the private dependencies, or +add a BFA_READ_TOKEN secret scoped to rgb-lib-bfa. The existing +RGB_LIB_RUST_LIGHTNING_PAT is used as a fallback for ORG_READ_TOKEN; it +still needs explicit access to the new dependency. Organization approval +or SSO authorization may be required. Never commit a token. + +The credential action verifies access before Cargo starts and fails with +a specific diagnostic for missing or unauthorized credentials. No secrets +are exposed to pull requests from forks. Format checks need no credentials. + +All build, lint and formatting checks remain enabled. This dependency +migration does not establish Lightning runtime support in the companion +on-chain Android build. diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 17bd7e517..b7884547c 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", optional = true, default-features = false, features = bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] } # RGB and related -rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.50-bfa", default-features = false } +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib-bfa.git", rev = "3f571ac58909400046d69143ebb535f4da1f4d42", default-features = false } [dev-dependencies] serde_json = { version = "1"} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index eeb54ddee..ed96ffeef 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -59,10 +59,11 @@ amplify = "4.8" bincode = "1.3" rgb-strict-encoding = "1.0.1" futures = "0.3" -rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib.git", tag = "v0.3.0-beta.50-bfa", default-features = false } +rgb-lib = { git = "https://github.com/UTEXO-Protocol/rgb-lib-bfa.git", rev = "3f571ac58909400046d69143ebb535f4da1f4d42", default-features = false } serde = { version = "^1.0", features = [ "derive", ] } +serde_json = "1.0" tokio = { version = "1.14.1", features = [ "macros", "rt-multi-thread", diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index 7c3c6640d..7ae2df403 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -28,7 +28,8 @@ use rgb_lib::{ keys::WitnessVersion, wallet::{ rust_only::{AssetColoringInfo, ColoringInfo}, - DatabaseType, OnlineOptions, RgbWalletOpsOffline, SinglesigKeys, Wallet, WalletData, + DatabaseType, IndexerConfig, OnlineOptions, RgbWalletOpsOffline, SinglesigKeys, Wallet, + WalletData, }, AssetSchema, Assignment, BitcoinNetwork, ConsignmentExt, ContractId, Error as RgbLibError, Fascia, FileContent, RgbTransfer, WitnessOrd, @@ -55,6 +56,8 @@ pub const INDEXER_URL_FNAME: &str = "indexer_url"; /// KVStore configuration key for the Ethereum RPC used to validate BFA funding consignments. /// Missing or empty values disable BFA support. pub const ETH_RPC_URL_FNAME: &str = "eth_rpc_url"; +/// EVM RPC endpoints by chain ID, serialized as JSON for the current unlock. +pub const EVM_RPC_URLS_FNAME: &str = "evm_rpc_urls"; /// Name of the file containing the account-level xPub of the vanilla-side of the wallet pub const WALLET_ACCOUNT_XPUB_VANILLA_FNAME: &str = "wallet_account_xpub_vanilla"; /// Name of the file containing the account-level xPub of the colored-side of the wallet @@ -195,6 +198,34 @@ fn _get_eth_rpc_url(kv_store: &dyn KVStoreSync) -> Option { } } +fn _get_evm_rpc_urls(kv_store: &dyn KVStoreSync) -> HashMap { + match kv_store.read_config(EVM_RPC_URLS_FNAME) { + Ok(value) => serde_json::from_str(&value).expect("invalid EVM RPC map in KVStore"), + Err(e) if e.kind() == io::ErrorKind::NotFound => HashMap::new(), + Err(e) => panic!("cannot read EVM RPC configuration: {e}"), + } +} + +/// Construct explicit indexer and EVM options for the protocol-1.0 BFA library. +pub fn rgb_online_options( + indexer_url: String, evm_rpc_urls: HashMap, skip_consistency_check: bool, +) -> OnlineOptions { + let indexer_config = + if indexer_url.starts_with("https://") || indexer_url.starts_with("http://") { + IndexerConfig::Esplora { url: indexer_url } + } else { + IndexerConfig::Electrum { url: indexer_url } + }; + OnlineOptions { + indexer_config, + skip_consistency_check, + vanilla_sync_lookback: VANILLA_SYNC_LOOKBACK, + failed_witness_receive_grace_secs: 86400, + proxy_auth_keys: HashMap::new(), + evm_rpc_urls, + } +} + #[cfg(test)] mod bfa_config_tests { use super::*; @@ -238,7 +269,7 @@ pub fn supported_asset_schemas(bitcoin_network: BitcoinNetwork, bfa: bool) -> Ve fn _new_rgb_wallet( data_dir: String, bitcoin_network: BitcoinNetwork, account_xpub_vanilla: String, - account_xpub_colored: String, master_fingerprint: String, reuse_addresses: bool, bfa: bool, + account_xpub_colored: String, master_fingerprint: String, _reuse_addresses: bool, bfa: bool, ) -> Wallet { let keys = SinglesigKeys { account_xpub_vanilla, @@ -255,7 +286,6 @@ fn _new_rgb_wallet( database_type: DatabaseType::Sqlite, max_allocations_per_utxo: 1, supported_schemas: supported_asset_schemas(bitcoin_network, bfa), - reuse_addresses, }, keys, ) @@ -282,7 +312,7 @@ fn _get_wallet_data( } async fn _get_rgb_wallet(ldk_data_dir: &Path, kv_store: &dyn KVStoreSync) -> Wallet { - let bfa = _get_eth_rpc_url(kv_store).is_some(); + let bfa = !_get_evm_rpc_urls(kv_store).is_empty(); let ( data_dir, bitcoin_network, @@ -328,7 +358,7 @@ async fn _accept_transfer( reuse_addresses, ) = _get_wallet_data(ldk_data_dir, kv_store); let indexer_url = _get_indexer_url(kv_store); - let eth_rpc_url = _get_eth_rpc_url(kv_store); + let evm_rpc_urls = _get_evm_rpc_urls(kv_store); // the consignment is received from the channel counterparty over the p2p link and written to disk let consignment_path = ldk_data_dir.join(format!("consignment_{funding_txid}")); tokio::task::spawn_blocking(move || { @@ -339,14 +369,9 @@ async fn _accept_transfer( account_xpub_colored, master_fingerprint, reuse_addresses, - eth_rpc_url.is_some(), + !evm_rpc_urls.is_empty(), ); - let online = wallet.go_online(OnlineOptions { - indexer_url, - skip_consistency_check: true, - vanilla_sync_lookback: VANILLA_SYNC_LOOKBACK, - eth_rpc_url, - })?; + let online = wallet.go_online(rgb_online_options(indexer_url, evm_rpc_urls, true))?; let (consignment, assignments, media_digests) = wallet.accept_transfer_consignment( online, consignment_path,