diff --git a/.dockerignore b/.dockerignore new file mode 120000 index 0000000000..3e4e48b0b5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7899e38af3..2f753fd1fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,23 +116,3 @@ jobs: - name: Test run: cargo test --all - - core: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - - - uses: Swatinem/rust-cache@v2 - - - name: Install Bitcoin Core - run: ./bin/install-bitcoin-core-linux - - - name: Test - run: cargo test --all -- --ignored diff --git a/.gitignore b/.gitignore index cb0a311337..632b51ef39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ /*.redb /.idea/ /.vagrant +/.vscode /docs/build +/env /fuzz/artifacts /fuzz/corpus /fuzz/coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index a266c81a3e..2d2f6a24dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,657 +1,760 @@ Changelog ========= +[0.16.0](https://github.com/ordinals/ord/releases/tag/0.16.0) - 2023-03-11 +-------------------------------------------------------------------------- + +### Added +- Document recursive endpoint backwards compatibility guarantees ([#3265](https://github.com/ordinals/ord/pull/3265) by [casey](https://github.com/casey)) +- Reserve inscription tag 15 ([#3256](https://github.com/ordinals/ord/pull/3256) by [casey](https://github.com/casey)) +- Display initial sync time on status page ([#3250](https://github.com/ordinals/ord/pull/3250) by [casey](https://github.com/casey)) +- Add content proxy ([#3216](https://github.com/ordinals/ord/pull/3216) by [raphjaph](https://github.com/raphjaph)) +- Allow configuring interval between commits to index ([#3186](https://github.com/ordinals/ord/pull/3186) by [bingryan](https://github.com/bingryan)) +- Print PSBT for dry run inscribe ([#3116](https://github.com/ordinals/ord/pull/3116) by [raphjaph](https://github.com/raphjaph)) +- Add parent preview to inscription page ([#3163](https://github.com/ordinals/ord/pull/3163) by [elocremarc](https://github.com/elocremarc)) +- Add `/r/inscription` endpoint for getting inscription details ([#2628](https://github.com/ordinals/ord/pull/2628) by [devords](https://github.com/devords)) +- Add optional HTTP authentication for server ([#3131](https://github.com/ordinals/ord/pull/3131) by [casey](https://github.com/casey)) +- Display inscription content type counts on /status ([#3127](https://github.com/ordinals/ord/pull/3127) by [casey](https://github.com/casey)) +- Add `ord env` to spin up a test bitcoin daemon and ord server ([#3146](https://github.com/ordinals/ord/pull/3146) by [casey](https://github.com/casey)) +- Emit inscription update events to channel ([#3137](https://github.com/ordinals/ord/pull/3137) by [mi-yu](https://github.com/mi-yu)) +- Allow inscribing AVIF images ([#3123](https://github.com/ordinals/ord/pull/3123) by [casey](https://github.com/casey)) +- Add `satpoints` batch inscribe mode ([#3115](https://github.com/ordinals/ord/pull/3115) by [raphjaph](https://github.com/raphjaph)) +- Add /r/blockinfo endpoint ([#3075](https://github.com/ordinals/ord/pull/3075) by [jerryfane](https://github.com/jerryfane)) +- Return signed PSBT from `ord wallet send` ([#3093](https://github.com/ordinals/ord/pull/3093) by [raphjaph](https://github.com/raphjaph)) +- Add /runes/balances ([#2978](https://github.com/ordinals/ord/pull/2978) by [lugondev](https://github.com/lugondev)) +- Dump and restore wallet from descriptors ([#3048](https://github.com/ordinals/ord/pull/3048) by [raphjaph](https://github.com/raphjaph)) +- Inscribe with delegate ([#3021](https://github.com/ordinals/ord/pull/3021) by [casey](https://github.com/casey)) +- Add option to retain sat index for spent outputs ([#2999](https://github.com/ordinals/ord/pull/2999) by [casey](https://github.com/casey)) +- Add `indexed` to output JSON ([#2971](https://github.com/ordinals/ord/pull/2971) by [raphjaph](https://github.com/raphjaph)) + +### Changed +- Add `id` inscription recursive JSON ([#3258](https://github.com/ordinals/ord/pull/3258) by [raphjaph](https://github.com/raphjaph)) +- Add more fields to /r/blockinfo ([#3260](https://github.com/ordinals/ord/pull/3260) by [raphjaph](https://github.com/raphjaph)) +- Load config from default data dir and configure `ord env ` using config ([#3240](https://github.com/ordinals/ord/pull/3240) by [casey](https://github.com/casey)) +- Overhaul settings ([#3188](https://github.com/ordinals/ord/pull/3188) by [casey](https://github.com/casey)) +- Improve configuration ([#3156](https://github.com/ordinals/ord/pull/3156) by [casey](https://github.com/casey)) +- Represent rune IDs as `BLOCK:TX` ([#3165](https://github.com/ordinals/ord/pull/3165) by [casey](https://github.com/casey)) +- Display parent above metadata on /inscription ([#3160](https://github.com/ordinals/ord/pull/3160) by [casey](https://github.com/casey)) +- Make `ord env` more user friendly ([#3153](https://github.com/ordinals/ord/pull/3153) by [casey](https://github.com/casey)) +- Use `image-rendering: auto` for AVIF inscriptions ([#3148](https://github.com/ordinals/ord/pull/3148) by [casey](https://github.com/casey)) +- Make wallet async ([#3142](https://github.com/ordinals/ord/pull/3142) by [raphjaph](https://github.com/raphjaph)) +- Use `image-rendering: auto` when downscaling images ([#3144](https://github.com/ordinals/ord/pull/3144) by [casey](https://github.com/casey)) +- Only allow mnemonic from stdin ([#3023](https://github.com/ordinals/ord/pull/3023) by [mj10021](https://github.com/mj10021)) +- Show reinscriptions in `ord wallet inscriptions` ([#3101](https://github.com/ordinals/ord/pull/3101) by [raphjaph](https://github.com/raphjaph)) +- Allow specifying satpoint in `same-sat` batch inscribe ([#3100](https://github.com/ordinals/ord/pull/3100) by [raphjaph](https://github.com/raphjaph)) +- Enable JSON API by default ([#3047](https://github.com/ordinals/ord/pull/3047) by [raphjaph](https://github.com/raphjaph)) +- Make wallet communicate with index via RPC ([#2929](https://github.com/ordinals/ord/pull/2929) by [raphjaph](https://github.com/raphjaph)) +- Add blocks and transaction JSON endpoints ([#3004](https://github.com/ordinals/ord/pull/3004) by [DaviRain-Su](https://github.com/DaviRain-Su)) +- Hide BVM Network inscriptions ([#3012](https://github.com/ordinals/ord/pull/3012) by [casey](https://github.com/casey)) +- Suppress empty command output ([#2995](https://github.com/ordinals/ord/pull/2995) by [casey](https://github.com/casey)) + +### Misc +- Rename genesis fee to inscription fee ([#3257](https://github.com/ordinals/ord/pull/3257) by [raphjaph](https://github.com/raphjaph)) +- Don't consider unconfirmed UTXOs as spent ([#3255](https://github.com/ordinals/ord/pull/3255) by [arik-so](https://github.com/arik-so)) +- Create tempdir in download-log recipe ([#3242](https://github.com/ordinals/ord/pull/3242) by [casey](https://github.com/casey)) +- Fix list numbering in handbook ([#3248](https://github.com/ordinals/ord/pull/3248) by [lugondev](https://github.com/lugondev)) +- Document `ord env` commands ([#3241](https://github.com/ordinals/ord/pull/3241) by [casey](https://github.com/casey)) +- Document `ord wallet restore` ([#3237](https://github.com/ordinals/ord/pull/3237) by [raphjaph](https://github.com/raphjaph)) +- Enable indexing runes on mainnet ([#3236](https://github.com/ordinals/ord/pull/3236) by [casey](https://github.com/casey)) +- Add libssl-dev to ubuntu install command ([#3235](https://github.com/ordinals/ord/pull/3235) by [andrewhong5297](https://github.com/andrewhong5297)) +- Test that runes can be minted with no edict ([#3231](https://github.com/ordinals/ord/pull/3231) by [casey](https://github.com/casey)) +- Rename index_envelopes to index_inscriptions ([#3233](https://github.com/ordinals/ord/pull/3233) by [casey](https://github.com/casey)) +- Check for duplicate satpoints in `satpoints` mode ([#3221](https://github.com/ordinals/ord/pull/3221) by [raphjaph](https://github.com/raphjaph)) +- Add reinscribe option to batch file ([#3220](https://github.com/ordinals/ord/pull/3220) by [raphjaph](https://github.com/raphjaph)) +- Encode claims as tag ([#3206](https://github.com/ordinals/ord/pull/3206) by [casey](https://github.com/casey)) +- Make nop and burn tags one byte ([#3207](https://github.com/ordinals/ord/pull/3207) by [casey](https://github.com/casey)) +- Make deploys noninteractive ([#3189](https://github.com/ordinals/ord/pull/3189) by [casey](https://github.com/casey)) +- Credit contributors in changelog ([#3187](https://github.com/ordinals/ord/pull/3187) by [casey](https://github.com/casey)) +- Update ordinals crate ([#3184](https://github.com/ordinals/ord/pull/3184) by [raphjaph](https://github.com/raphjaph)) +- Refactor test server to use arguments ([#3183](https://github.com/ordinals/ord/pull/3183) by [casey](https://github.com/casey)) +- Install openssl in docker image ([#3181](https://github.com/ordinals/ord/pull/3181) by [aekasitt](https://github.com/aekasitt)) +- Document `ord env` ([#3180](https://github.com/ordinals/ord/pull/3180) by [casey](https://github.com/casey)) +- Update docs to reflect wallet changes ([#3179](https://github.com/ordinals/ord/pull/3179) by [raphjaph](https://github.com/raphjaph)) +- Remove unnecessary lifetime from Formatter ([#3178](https://github.com/ordinals/ord/pull/3178) by [casey](https://github.com/casey)) +- Fix lints ([#3124](https://github.com/ordinals/ord/pull/3124) by [lugondev](https://github.com/lugondev)) +- Update inscription sat documentation ([#3114](https://github.com/ordinals/ord/pull/3114) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- Move JSON structs into api module ([#3167](https://github.com/ordinals/ord/pull/3167) by [casey](https://github.com/casey)) +- Make Options public ([#3138](https://github.com/ordinals/ord/pull/3138) by [mi-yu](https://github.com/mi-yu)) +- Fix spelling mistake in bip.mediawiki ([#3118](https://github.com/ordinals/ord/pull/3118) by [HarveyV](https://github.com/HarveyV)) +- Import multiple descriptors at a time ([#3091](https://github.com/ordinals/ord/pull/3091) by [raphjaph](https://github.com/raphjaph)) +- fix naming ([#3112](https://github.com/ordinals/ord/pull/3112) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- Move sat and friends into ordinals crate ([#3079](https://github.com/ordinals/ord/pull/3079) by [raphjaph](https://github.com/raphjaph)) +- Remove index parameter from index_block ([#3088](https://github.com/ordinals/ord/pull/3088) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- Make clippy stop complaining about insane repair callback ([#3104](https://github.com/ordinals/ord/pull/3104) by [casey](https://github.com/casey)) +- Use min instead of clamp ([#3081](https://github.com/ordinals/ord/pull/3081) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- [ordinals] Bump version: 0.0.1 → 0.0.2 ([#3078](https://github.com/ordinals/ord/pull/3078) by [casey](https://github.com/casey)) +- Move SatPoint into library ([#3077](https://github.com/ordinals/ord/pull/3077) by [casey](https://github.com/casey)) +- Use a flag to indicate a mint ([#3068](https://github.com/ordinals/ord/pull/3068) by [casey](https://github.com/casey)) +- Add dry run to send, print Outgoing and PSBT ([#3063](https://github.com/ordinals/ord/pull/3063) by [raphjaph](https://github.com/raphjaph)) +- Make invariant message more concise ([#3029](https://github.com/ordinals/ord/pull/3029) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- Forbid destinations in same-sat mode ([#3038](https://github.com/ordinals/ord/pull/3038) by [zhiqiangxu](https://github.com/zhiqiangxu)) +- Exclude unnecessary docs ([#3043](https://github.com/ordinals/ord/pull/3043) by [raphjaph](https://github.com/raphjaph)) +- Add documentation for reinscriptions ([#2963](https://github.com/ordinals/ord/pull/2963) by [mj10021](https://github.com/mj10021)) +- Better wallet error messages ([#3041](https://github.com/ordinals/ord/pull/3041) by [raphjaph](https://github.com/raphjaph)) +- Remove uneccessary allocations in Inscription Script Creation ([#3039](https://github.com/ordinals/ord/pull/3039) by [JeremyRubin](https://github.com/JeremyRubin)) +- Test fee-spent inscription numbering ([#3032](https://github.com/ordinals/ord/pull/3032) by [casey](https://github.com/casey)) +- Break deploy recipes into multiple lines ([#3026](https://github.com/ordinals/ord/pull/3026) by [casey](https://github.com/casey)) +- Use untyped table API to get table info ([#2747](https://github.com/ordinals/ord/pull/2747) by [casey](https://github.com/casey)) +- Use --name instead of --wallet in README ([#3010](https://github.com/ordinals/ord/pull/3010) by [RobertClarke](https://github.com/RobertClarke)) +- Don't use browser sniffing when serving favicon ([#3003](https://github.com/ordinals/ord/pull/3003) by [casey](https://github.com/casey)) +- Add minimal Dockerfile ([#2786](https://github.com/ordinals/ord/pull/2786) by [raphjaph](https://github.com/raphjaph)) +- Cache less aggressively ([#3002](https://github.com/ordinals/ord/pull/3002) by [casey](https://github.com/casey)) +- Remove dead link from README ([#3000](https://github.com/ordinals/ord/pull/3000) by [oxSaturn](https://github.com/oxSaturn)) +- Add crate to audit content security policy ([#2993](https://github.com/ordinals/ord/pull/2993) by [casey](https://github.com/casey)) +- Optimize get_inscription_ids_by_sat_paginated ([#2996](https://github.com/ordinals/ord/pull/2996) by [casey](https://github.com/casey)) +- Add recipe to deploy to all servers in fleet ([#2992](https://github.com/ordinals/ord/pull/2992) by [casey](https://github.com/casey)) + [0.15.0](https://github.com/ordinals/ord/releases/tag/0.15.0) - 2023-01-08 -------------------------------------------------------------------------- ### Added -- Add no sync option to server command (#2966) -- Vindicate cursed inscriptions (#2950) -- Add JSON endpoints for Runes (#2941) -- Add JSON endpoint for status (#2955) -- Add chain to status page (#2953) +- Add no sync option to server command ([#2966](https://github.com/ordinals/ord/pull/2966) by [raphjaph](https://github.com/raphjaph)) +- Vindicate cursed inscriptions ([#2950](https://github.com/ordinals/ord/pull/2950) by [casey](https://github.com/casey)) +- Add JSON endpoints for Runes ([#2941](https://github.com/ordinals/ord/pull/2941) by [lugondev](https://github.com/lugondev)) +- Add JSON endpoint for status ([#2955](https://github.com/ordinals/ord/pull/2955) by [raphjaph](https://github.com/raphjaph)) +- Add chain to status page ([#2953](https://github.com/ordinals/ord/pull/2953) by [raphjaph](https://github.com/raphjaph)) ### Changed -- Enter beta (#2973) +- Enter beta ([#2973](https://github.com/ordinals/ord/pull/2973) by [casey](https://github.com/casey)) ### Performance -- Avoid skip when getting paginated inscriptions (#2975) -- Dispatch requests to tokio thread pool (#2974) +- Avoid skip when getting paginated inscriptions ([#2975](https://github.com/ordinals/ord/pull/2975) by [casey](https://github.com/casey)) +- Dispatch requests to tokio thread pool ([#2974](https://github.com/ordinals/ord/pull/2974) by [casey](https://github.com/casey)) ### Misc -- Fix Project Board link (#2991) -- Update server names in justfile (#2954) -- Update delegate.md (#2976) -- Fix a typo (#2980) -- Use enums for runestone tags and flags (#2956) -- Make `FundRawTransactionOptions ` public (#2938) -- Deduplicate deploy script case statements (#2962) -- Remove quotes around key to allow shell expansion (#2951) -- Restart sshd in deploy script (#2952) +- Fix Project Board link ([#2991](https://github.com/ordinals/ord/pull/2991) by [raphjaph](https://github.com/raphjaph)) +- Update server names in justfile ([#2954](https://github.com/ordinals/ord/pull/2954) by [casey](https://github.com/casey)) +- Update delegate.md ([#2976](https://github.com/ordinals/ord/pull/2976) by [gmart7t2](https://github.com/gmart7t2)) +- Fix a typo ([#2980](https://github.com/ordinals/ord/pull/2980) by [GoodDaisy](https://github.com/GoodDaisy)) +- Use enums for runestone tags and flags ([#2956](https://github.com/ordinals/ord/pull/2956) by [casey](https://github.com/casey)) +- Make `FundRawTransactionOptions ` public ([#2938](https://github.com/ordinals/ord/pull/2938) by [lateminer](https://github.com/lateminer)) +- Deduplicate deploy script case statements ([#2962](https://github.com/ordinals/ord/pull/2962) by [casey](https://github.com/casey)) +- Remove quotes around key to allow shell expansion ([#2951](https://github.com/ordinals/ord/pull/2951) by [casey](https://github.com/casey)) +- Restart sshd in deploy script ([#2952](https://github.com/ordinals/ord/pull/2952) by [raphjaph](https://github.com/raphjaph)) [0.14.1](https://github.com/ordinals/ord/releases/tag/0.14.1) - 2023-01-03 -------------------------------------------------------------------------- ### Fixed -- Fix wallet create (#2943) +- Fix wallet create ([#2943](https://github.com/ordinals/ord/pull/2943) by [raphjaph](https://github.com/raphjaph)) ## Misc -- Clean up justfile (#2939) +- Clean up justfile ([#2939](https://github.com/ordinals/ord/pull/2939) by [casey](https://github.com/casey)) [0.14.0](https://github.com/ordinals/ord/releases/tag/0.14.0) - 2023-01-02 -------------------------------------------------------------------------- ### Fixed -- Keep inscriptions with unrecognized even fields unbound after jubilee (#2894) +- Keep inscriptions with unrecognized even fields unbound after jubilee ([#2894](https://github.com/ordinals/ord/pull/2894) by [casey](https://github.com/casey)) ### Added -- Allow inscriptions to nominate a delegate (#2912) -- Display number of times a rune has been minted (#2901) -- Optionally store transactions in index (#2885) -- Allow specifying destination for unallocated runes (#2899) -- Make inscriptions with tag 66 permanently unbound (#2906) -- Decode transactions from Bitcoin Core with `ord decode --txid` (#2907) -- Allow skpping indexing inscriptions (#2900) -- Add optional deadline to open etchings (#2875) +- Allow inscriptions to nominate a delegate ([#2912](https://github.com/ordinals/ord/pull/2912) by [casey](https://github.com/casey)) +- Display number of times a rune has been minted ([#2901](https://github.com/ordinals/ord/pull/2901) by [casey](https://github.com/casey)) +- Optionally store transactions in index ([#2885](https://github.com/ordinals/ord/pull/2885) by [casey](https://github.com/casey)) +- Allow specifying destination for unallocated runes ([#2899](https://github.com/ordinals/ord/pull/2899) by [casey](https://github.com/casey)) +- Make inscriptions with tag 66 permanently unbound ([#2906](https://github.com/ordinals/ord/pull/2906) by [casey](https://github.com/casey)) +- Decode transactions from Bitcoin Core with `ord decode --txid` ([#2907](https://github.com/ordinals/ord/pull/2907) by [casey](https://github.com/casey)) +- Allow skpping indexing inscriptions ([#2900](https://github.com/ordinals/ord/pull/2900) by [casey](https://github.com/casey)) +- Add optional deadline to open etchings ([#2875](https://github.com/ordinals/ord/pull/2875) by [casey](https://github.com/casey)) ### Changed -- Only store transactions with inscriptions in the database (#2926) -- Hide all inscriptions with /content/ content (#2908) -- Hide code, metaprotocol, and unknown media inscriptions (#2872) -- Display rune symbol to right of amount (#2871) +- Only store transactions with inscriptions in the database ([#2926](https://github.com/ordinals/ord/pull/2926) by [casey](https://github.com/casey)) +- Hide all inscriptions with /content/ content ([#2908](https://github.com/ordinals/ord/pull/2908) by [casey](https://github.com/casey)) +- Hide code, metaprotocol, and unknown media inscriptions ([#2872](https://github.com/ordinals/ord/pull/2872) by [casey](https://github.com/casey)) +- Display rune symbol to right of amount ([#2871](https://github.com/ordinals/ord/pull/2871) by [casey](https://github.com/casey)) ### Misc -- Use install to copy binary in deploy script (#2934) -- Don't index transactions on production servers (#2933) -- Add recipes to copy keys to servers (#2927) -- Clean deploy/save-ord-dev-state (#2932) -- Refactor bitcoin client for wallet (#2918) -- Use enum for inscription tags (#2921) -- Fix CSP origin for different deployments (#2923) -- Placate clippy (#2924) -- Display path to default datadir in help output (#2881) -- Add index repair progress bar (#2904) -- Listen on 127.0.0.1 to avoid firewall popup on macOS (#2911) -- Set correct statistic when indexing transactions (#2913) -- Show if transaction index is enabled on /status (#2910) -- Optimize /inscription endpoint (#2884) -- Show all inscription geneses on /tx (#2909) -- Serve HTTP/2 (#2895) -- Don't display trailing spacers in spaced runes (#2896) -- Split runes more evenly (#2897) -- Dispaly rune ID above height and index (#2874) -- Use transaction version 2 (#2873) +- Use install to copy binary in deploy script ([#2934](https://github.com/ordinals/ord/pull/2934) by [casey](https://github.com/casey)) +- Don't index transactions on production servers ([#2933](https://github.com/ordinals/ord/pull/2933) by [casey](https://github.com/casey)) +- Add recipes to copy keys to servers ([#2927](https://github.com/ordinals/ord/pull/2927) by [casey](https://github.com/casey)) +- Clean deploy/save-ord-dev-state ([#2932](https://github.com/ordinals/ord/pull/2932) by [casey](https://github.com/casey)) +- Refactor bitcoin client for wallet ([#2918](https://github.com/ordinals/ord/pull/2918) by [raphjaph](https://github.com/raphjaph)) +- Use enum for inscription tags ([#2921](https://github.com/ordinals/ord/pull/2921) by [casey](https://github.com/casey)) +- Fix CSP origin for different deployments ([#2923](https://github.com/ordinals/ord/pull/2923) by [raphjaph](https://github.com/raphjaph)) +- Placate clippy ([#2924](https://github.com/ordinals/ord/pull/2924) by [raphjaph](https://github.com/raphjaph)) +- Display path to default datadir in help output ([#2881](https://github.com/ordinals/ord/pull/2881) by [torkelrogstad](https://github.com/torkelrogstad)) +- Add index repair progress bar ([#2904](https://github.com/ordinals/ord/pull/2904) by [nikicat](https://github.com/nikicat)) +- Listen on 127.0.0.1 to avoid firewall popup on macOS ([#2911](https://github.com/ordinals/ord/pull/2911) by [casey](https://github.com/casey)) +- Set correct statistic when indexing transactions ([#2913](https://github.com/ordinals/ord/pull/2913) by [casey](https://github.com/casey)) +- Show if transaction index is enabled on /status ([#2910](https://github.com/ordinals/ord/pull/2910) by [casey](https://github.com/casey)) +- Optimize /inscription endpoint ([#2884](https://github.com/ordinals/ord/pull/2884) by [casey](https://github.com/casey)) +- Show all inscription geneses on /tx ([#2909](https://github.com/ordinals/ord/pull/2909) by [casey](https://github.com/casey)) +- Serve HTTP/2 ([#2895](https://github.com/ordinals/ord/pull/2895) by [casey](https://github.com/casey)) +- Don't display trailing spacers in spaced runes ([#2896](https://github.com/ordinals/ord/pull/2896) by [casey](https://github.com/casey)) +- Split runes more evenly ([#2897](https://github.com/ordinals/ord/pull/2897) by [casey](https://github.com/casey)) +- Dispaly rune ID above height and index ([#2874](https://github.com/ordinals/ord/pull/2874) by [casey](https://github.com/casey)) +- Use transaction version 2 ([#2873](https://github.com/ordinals/ord/pull/2873) by [casey](https://github.com/casey)) [0.13.1](https://github.com/ordinals/ord/releases/tag/0.13.1) - 2023-12-16 -------------------------------------------------------------------------- ### Fixed -- Use pre-segwit transaction serialization for fundrawtransaction (#2865) +- Use pre-segwit transaction serialization for fundrawtransaction ([#2865](https://github.com/ordinals/ord/pull/2865) by [casey](https://github.com/casey)) [0.13.0](https://github.com/ordinals/ord/releases/tag/0.13.0) - 2023-12-15 -------------------------------------------------------------------------- ### Added -- Send runes with `ord wallet send` (#2858) -- Add rune spacers (#2862) -- Reserve runes for sequential allocation (#2831) -- Unlock runes over course of halving epoch (#2852) -- Add flag to decompress brotli server-side (#2854) -- Add /status page (#2819) -- Add coin charm (#2821) +- Send runes with `ord wallet send` ([#2858](https://github.com/ordinals/ord/pull/2858) by [casey](https://github.com/casey)) +- Add rune spacers ([#2862](https://github.com/ordinals/ord/pull/2862) by [casey](https://github.com/casey)) +- Reserve runes for sequential allocation ([#2831](https://github.com/ordinals/ord/pull/2831) by [casey](https://github.com/casey)) +- Unlock runes over course of halving epoch ([#2852](https://github.com/ordinals/ord/pull/2852) by [casey](https://github.com/casey)) +- Add flag to decompress brotli server-side ([#2854](https://github.com/ordinals/ord/pull/2854) by [raphjaph](https://github.com/raphjaph)) +- Add /status page ([#2819](https://github.com/ordinals/ord/pull/2819) by [casey](https://github.com/casey)) +- Add coin charm ([#2821](https://github.com/ordinals/ord/pull/2821) by [casey](https://github.com/casey)) ### Fixed -- Fix endpoint `/inscriptions/block//` (#2798) +- Fix endpoint `/inscriptions/block//` ([#2798](https://github.com/ordinals/ord/pull/2798) by [gmart7t2](https://github.com/gmart7t2)) ### Misc -- Tweak rune tags and flags (#2860) -- Unlock runes in first block of interval (#2861) -- Index runes on testnet and signet deployments (#2857) -- Fix fuzzers (#2859) -- Make varint decoding infallible (#2853) -- Add runes to parse command (#2830) -- Update dependencies (#2828) -- Add coverage recipe (#2846) -- Put `Accept-Encoding` value in backticks (#2840) -- Don't print status when deploying (#2838) -- Fix justfile (#2836) -- Allow deploying remotes other than ordinals/ord (#2829) -- Include `Accept-Encoding` header value in error message (#2835) -- Clarify docs (#2827) -- Fix batch docs (#2823) -- Add accept encoding test without qvalues (#2822) -- Italian version of the handbook (#2801) -- Preview can mine blocks (#2809) -- Burn input runes if there are no non-op-return outputs (#2812) -- Update audit-cache binary (#2804) +- Tweak rune tags and flags ([#2860](https://github.com/ordinals/ord/pull/2860) by [casey](https://github.com/casey)) +- Unlock runes in first block of interval ([#2861](https://github.com/ordinals/ord/pull/2861) by [casey](https://github.com/casey)) +- Index runes on testnet and signet deployments ([#2857](https://github.com/ordinals/ord/pull/2857) by [casey](https://github.com/casey)) +- Fix fuzzers ([#2859](https://github.com/ordinals/ord/pull/2859) by [casey](https://github.com/casey)) +- Make varint decoding infallible ([#2853](https://github.com/ordinals/ord/pull/2853) by [casey](https://github.com/casey)) +- Add runes to parse command ([#2830](https://github.com/ordinals/ord/pull/2830) by [casey](https://github.com/casey)) +- Update dependencies ([#2828](https://github.com/ordinals/ord/pull/2828) by [casey](https://github.com/casey)) +- Add coverage recipe ([#2846](https://github.com/ordinals/ord/pull/2846) by [casey](https://github.com/casey)) +- Put `Accept-Encoding` value in backticks ([#2840](https://github.com/ordinals/ord/pull/2840) by [casey](https://github.com/casey)) +- Don't print status when deploying ([#2838](https://github.com/ordinals/ord/pull/2838) by [casey](https://github.com/casey)) +- Fix justfile ([#2836](https://github.com/ordinals/ord/pull/2836) by [raphjaph](https://github.com/raphjaph)) +- Allow deploying remotes other than ordinals/ord ([#2829](https://github.com/ordinals/ord/pull/2829) by [casey](https://github.com/casey)) +- Include `Accept-Encoding` header value in error message ([#2835](https://github.com/ordinals/ord/pull/2835) by [casey](https://github.com/casey)) +- Clarify docs ([#2827](https://github.com/ordinals/ord/pull/2827) by [raphjaph](https://github.com/raphjaph)) +- Fix batch docs ([#2823](https://github.com/ordinals/ord/pull/2823) by [raphjaph](https://github.com/raphjaph)) +- Add accept encoding test without qvalues ([#2822](https://github.com/ordinals/ord/pull/2822) by [casey](https://github.com/casey)) +- Italian version of the handbook ([#2801](https://github.com/ordinals/ord/pull/2801) by [DrJingLee](https://github.com/DrJingLee)) +- Preview can mine blocks ([#2809](https://github.com/ordinals/ord/pull/2809) by [raphjaph](https://github.com/raphjaph)) +- Burn input runes if there are no non-op-return outputs ([#2812](https://github.com/ordinals/ord/pull/2812) by [casey](https://github.com/casey)) +- Update audit-cache binary ([#2804](https://github.com/ordinals/ord/pull/2804) by [casey](https://github.com/casey)) [0.12.3](https://github.com/ordinals/ord/releases/tag/0.12.3) - 2023-12-01 -------------------------------------------------------------------------- ### Added -- Add `ord balances` to show rune balances (#2782) +- Add `ord balances` to show rune balances ([#2782](https://github.com/ordinals/ord/pull/2782) by [casey](https://github.com/casey)) ### Fixed -- Fix preview test (#2795) -- Fix reinscriptions charm (#2793) -- Fix fee calculation for batch inscribe on same sat (#2785) +- Fix preview test ([#2795](https://github.com/ordinals/ord/pull/2795) by [casey](https://github.com/casey)) +- Fix reinscriptions charm ([#2793](https://github.com/ordinals/ord/pull/2793) by [raphjaph](https://github.com/raphjaph)) +- Fix fee calculation for batch inscribe on same sat ([#2785](https://github.com/ordinals/ord/pull/2785) by [raphjaph](https://github.com/raphjaph)) ### Misc -- Add `audit-cache` binary to audit Cloudflare caching (#2787) -- Fix typos (#2791) -- Add total bytes and proportion to database info (#2783) +- Add `audit-cache` binary to audit Cloudflare caching ([#2787](https://github.com/ordinals/ord/pull/2787) by [casey](https://github.com/casey)) +- Fix typos ([#2791](https://github.com/ordinals/ord/pull/2791) by [vuittont60](https://github.com/vuittont60)) +- Add total bytes and proportion to database info ([#2783](https://github.com/ordinals/ord/pull/2783) by [casey](https://github.com/casey)) [0.12.2](https://github.com/ordinals/ord/releases/tag/0.12.2) - 2023-11-29 -------------------------------------------------------------------------- ### Added -- Bless cursed inscriptions after Jubilee height (#2656) +- Bless cursed inscriptions after Jubilee height ([#2656](https://github.com/ordinals/ord/pull/2656) by [casey](https://github.com/casey)) ### Misc -- Hide /content/ HTML inscriptions (#2778) +- Hide /content/ HTML inscriptions ([#2778](https://github.com/ordinals/ord/pull/2778) by [casey](https://github.com/casey)) [0.12.1](https://github.com/ordinals/ord/releases/tag/0.12.1) - 2023-11-29 -------------------------------------------------------------------------- ### Added -- Add commands to etch and list runes (#2544) -- Add ability to specify sat to batch inscribe (#2770) -- Allow setting the sat to inscribe (#2765) -- Batch inscribe on same sat (#2749) -- Add stuttering curse (#2745) -- Add batch to preview command (#2752) +- Add commands to etch and list runes ([#2544](https://github.com/ordinals/ord/pull/2544) by [casey](https://github.com/casey)) +- Add ability to specify sat to batch inscribe ([#2770](https://github.com/ordinals/ord/pull/2770) by [raphjaph](https://github.com/raphjaph)) +- Allow setting the sat to inscribe ([#2765](https://github.com/ordinals/ord/pull/2765) by [raphjaph](https://github.com/raphjaph)) +- Batch inscribe on same sat ([#2749](https://github.com/ordinals/ord/pull/2749) by [raphjaph](https://github.com/raphjaph)) +- Add stuttering curse ([#2745](https://github.com/ordinals/ord/pull/2745) by [casey](https://github.com/casey)) +- Add batch to preview command ([#2752](https://github.com/ordinals/ord/pull/2752) by [raphjaph](https://github.com/raphjaph)) ### Misc -- Add `public` to /content Cache-Control headers (#2773) -- Set CSP origin in deploy script (#2764) -- Fix typos (#2768) -- Select further away coins which meet target (#2724) -- Hide all text (#2753) +- Add `public` to /content Cache-Control headers ([#2773](https://github.com/ordinals/ord/pull/2773) by [casey](https://github.com/casey)) +- Set CSP origin in deploy script ([#2764](https://github.com/ordinals/ord/pull/2764) by [rot13maxi](https://github.com/rot13maxi)) +- Fix typos ([#2768](https://github.com/ordinals/ord/pull/2768) by [xiaolou86](https://github.com/xiaolou86)) +- Select further away coins which meet target ([#2724](https://github.com/ordinals/ord/pull/2724) by [gmart7t2](https://github.com/gmart7t2)) +- Hide all text ([#2753](https://github.com/ordinals/ord/pull/2753) by [raphjaph](https://github.com/raphjaph)) [0.12.0](https://github.com/ordinals/ord/releases/tag/0.12.0) - 2023-11-24 -------------------------------------------------------------------------- ### Added -- Add /r/children recursive endpoint (#2431) -- Add sat recursive endpoints with index and pagination (#2680) -- Allow setting CSP origin (#2708) -- Add destination field to batch (#2701) -- Preview font inscriptions (#2692) -- Add /collections Page (#2561) -- Add inscription compression (#1713) -- Add inscription charms (#2681) -- Hide protocol inscriptions (#2674) -- Hide JSON and .btc (#2744) -- Add Hindi version of handbook (#2648) +- Add /r/children recursive endpoint ([#2431](https://github.com/ordinals/ord/pull/2431) by [elocremarc](https://github.com/elocremarc)) +- Add sat recursive endpoints with index and pagination ([#2680](https://github.com/ordinals/ord/pull/2680) by [raphjaph](https://github.com/raphjaph)) +- Allow setting CSP origin ([#2708](https://github.com/ordinals/ord/pull/2708) by [rot13maxi](https://github.com/rot13maxi)) +- Add destination field to batch ([#2701](https://github.com/ordinals/ord/pull/2701) by [raphjaph](https://github.com/raphjaph)) +- Preview font inscriptions ([#2692](https://github.com/ordinals/ord/pull/2692) by [elocremarc](https://github.com/elocremarc)) +- Add /collections Page ([#2561](https://github.com/ordinals/ord/pull/2561) by [veryordinally](https://github.com/veryordinally)) +- Add inscription compression ([#1713](https://github.com/ordinals/ord/pull/1713) by [terror](https://github.com/terror)) +- Add inscription charms ([#2681](https://github.com/ordinals/ord/pull/2681) by [casey](https://github.com/casey)) +- Hide protocol inscriptions ([#2674](https://github.com/ordinals/ord/pull/2674) by [casey](https://github.com/casey)) +- Hide JSON and .btc ([#2744](https://github.com/ordinals/ord/pull/2744) by [raphjaph](https://github.com/raphjaph)) +- Add Hindi version of handbook ([#2648](https://github.com/ordinals/ord/pull/2648) by [duttydeedz](https://github.com/duttydeedz)) ### Changed -- Use icons in nav bar (#2722) -- Remove default file path from `ord index export --tsv` (#2717) -- Display table stats in `ord index info` (#2711) -- Move postage into batch file (#2705) +- Use icons in nav bar ([#2722](https://github.com/ordinals/ord/pull/2722) by [casey](https://github.com/casey)) +- Remove default file path from `ord index export --tsv` ([#2717](https://github.com/ordinals/ord/pull/2717) by [casey](https://github.com/casey)) +- Display table stats in `ord index info` ([#2711](https://github.com/ordinals/ord/pull/2711) by [casey](https://github.com/casey)) +- Move postage into batch file ([#2705](https://github.com/ordinals/ord/pull/2705) by [raphjaph](https://github.com/raphjaph)) ### Performance -- Use sequence numbers database keys (#2664) +- Use sequence numbers database keys ([#2664](https://github.com/ordinals/ord/pull/2664) by [casey](https://github.com/casey)) ### Misc -- Add docs for child recursive endpoint (#2743) -- Update docs to include all fields, including content-encoding (#2740) -- Ignore flaky test (#2742) -- Add docs and examples for sat recursive endpoint (#2735) -- Remove `RUNE` from `

` on /rune (#2728) -- Add docs for metadata recursive endpoint (#2734) -- Fix typo in docs/src/inscriptions/metadata.md (#2731) -- Only accept sat number in recursive endpoint (#2732) -- Add Homebrew install instructions to readme (#2726) -- Add Debian packaging instructions (#2725) -- Use redb's recovery callback API (#2584) -- Refactor inscriptions paginations (#2715) -- Update redb to 1.4.0 (#2714) -- Only try to create the database if it wasn't found (#2703) -- Only load used language highlight module in code preview (#2696) -- Clean up install.sh (#2669) -- Add binary media type (#2671) -- Fix unbound outpoint server error (#2479) -- Update schema version for charms (#2687) -- Fix media table formatting (#2686) -- Group rune server tests (#2685) -- Don't color links in headers (#2678) -- Remove Index::index_block_inscription_numbers (#2667) -- Fix lost sats bug (#2666) +- Add docs for child recursive endpoint ([#2743](https://github.com/ordinals/ord/pull/2743) by [raphjaph](https://github.com/raphjaph)) +- Update docs to include all fields, including content-encoding ([#2740](https://github.com/ordinals/ord/pull/2740) by [raphjaph](https://github.com/raphjaph)) +- Ignore flaky test ([#2742](https://github.com/ordinals/ord/pull/2742) by [casey](https://github.com/casey)) +- Add docs and examples for sat recursive endpoint ([#2735](https://github.com/ordinals/ord/pull/2735) by [raphjaph](https://github.com/raphjaph)) +- Remove `RUNE` from `

` on /rune ([#2728](https://github.com/ordinals/ord/pull/2728) by [casey](https://github.com/casey)) +- Add docs for metadata recursive endpoint ([#2734](https://github.com/ordinals/ord/pull/2734) by [raphjaph](https://github.com/raphjaph)) +- Fix typo in docs/src/inscriptions/metadata.md ([#2731](https://github.com/ordinals/ord/pull/2731) by [vuittont60](https://github.com/vuittont60)) +- Only accept sat number in recursive endpoint ([#2732](https://github.com/ordinals/ord/pull/2732) by [raphjaph](https://github.com/raphjaph)) +- Add Homebrew install instructions to readme ([#2726](https://github.com/ordinals/ord/pull/2726) by [casey](https://github.com/casey)) +- Add Debian packaging instructions ([#2725](https://github.com/ordinals/ord/pull/2725) by [casey](https://github.com/casey)) +- Use redb's recovery callback API ([#2584](https://github.com/ordinals/ord/pull/2584) by [cberner](https://github.com/cberner)) +- Refactor inscriptions paginations ([#2715](https://github.com/ordinals/ord/pull/2715) by [raphjaph](https://github.com/raphjaph)) +- Update redb to 1.4.0 ([#2714](https://github.com/ordinals/ord/pull/2714) by [casey](https://github.com/casey)) +- Only try to create the database if it wasn't found ([#2703](https://github.com/ordinals/ord/pull/2703) by [casey](https://github.com/casey)) +- Only load used language highlight module in code preview ([#2696](https://github.com/ordinals/ord/pull/2696) by [casey](https://github.com/casey)) +- Clean up install.sh ([#2669](https://github.com/ordinals/ord/pull/2669) by [eagr](https://github.com/eagr)) +- Add binary media type ([#2671](https://github.com/ordinals/ord/pull/2671) by [elocremarc](https://github.com/elocremarc)) +- Fix unbound outpoint server error ([#2479](https://github.com/ordinals/ord/pull/2479) by [raphjaph](https://github.com/raphjaph)) +- Update schema version for charms ([#2687](https://github.com/ordinals/ord/pull/2687) by [casey](https://github.com/casey)) +- Fix media table formatting ([#2686](https://github.com/ordinals/ord/pull/2686) by [casey](https://github.com/casey)) +- Group rune server tests ([#2685](https://github.com/ordinals/ord/pull/2685) by [casey](https://github.com/casey)) +- Don't color links in headers ([#2678](https://github.com/ordinals/ord/pull/2678) by [casey](https://github.com/casey)) +- Remove Index::index_block_inscription_numbers ([#2667](https://github.com/ordinals/ord/pull/2667) by [casey](https://github.com/casey)) +- Fix lost sats bug ([#2666](https://github.com/ordinals/ord/pull/2666) by [raphjaph](https://github.com/raphjaph)) [0.11.1](https://github.com/ordinals/ord/releases/tag/0.11.1) - 2023-11-09 -------------------------------------------------------------------------- ### Fixed -- Use new RPC client in Reorg::get_block_with_retries (#2650) +- Use new RPC client in Reorg::get_block_with_retries ([#2650](https://github.com/ordinals/ord/pull/2650) by [casey](https://github.com/casey)) ### Misc -- Refactor varint encoding (#2645) +- Refactor varint encoding ([#2645](https://github.com/ordinals/ord/pull/2645) by [eagr](https://github.com/eagr)) [0.11.0](https://github.com/ordinals/ord/releases/tag/0.11.0) - 2023-11-07 -------------------------------------------------------------------------- ### Added -- Add a link to the Ordicord (#2629) -- Add `/children` with pagination (#2617) -- Add metadata recursive endpoint (#2604) -- Add recursive directory and make all endpoints JSON (#2493) -- Add Portuguese version of handbook (#2572) -- Add decode just recipe (#2592) -- Add `/block/:query` JSON API endpoint (#2423) -- Add syntax highlighting for Python inscriptions (#2538) -- Add publish-and-tag-crate just recipe (#2576) -- Document teleburning handbook (#2577) +- Add a link to the Ordicord ([#2629](https://github.com/ordinals/ord/pull/2629) by [devords](https://github.com/devords)) +- Add `/children` with pagination ([#2617](https://github.com/ordinals/ord/pull/2617) by [devords](https://github.com/devords)) +- Add metadata recursive endpoint ([#2604](https://github.com/ordinals/ord/pull/2604) by [rot13maxi](https://github.com/rot13maxi)) +- Add recursive directory and make all endpoints JSON ([#2493](https://github.com/ordinals/ord/pull/2493) by [raphjaph](https://github.com/raphjaph)) +- Add Portuguese version of handbook ([#2572](https://github.com/ordinals/ord/pull/2572) by [namcios](https://github.com/namcios)) +- Add decode just recipe ([#2592](https://github.com/ordinals/ord/pull/2592) by [casey](https://github.com/casey)) +- Add `/block/:query` JSON API endpoint ([#2423](https://github.com/ordinals/ord/pull/2423) by [terror](https://github.com/terror)) +- Add syntax highlighting for Python inscriptions ([#2538](https://github.com/ordinals/ord/pull/2538) by [elocremarc](https://github.com/elocremarc)) +- Add publish-and-tag-crate just recipe ([#2576](https://github.com/ordinals/ord/pull/2576) by [casey](https://github.com/casey)) +- Document teleburning handbook ([#2577](https://github.com/ordinals/ord/pull/2577) by [casey](https://github.com/casey)) ### Changed -- Clarify sat hunting guide (#2640) -- Update docs (#2627) -- Remove blank line in CI workflow (#2620) -- Update README.md and zh.po (#2605) -- Require --batch or --file for `ord wallet inscribe` (#2581) +- Clarify sat hunting guide ([#2640](https://github.com/ordinals/ord/pull/2640) by [raphjaph](https://github.com/raphjaph)) +- Update docs ([#2627](https://github.com/ordinals/ord/pull/2627) by [rot13maxi](https://github.com/rot13maxi)) +- Remove blank line in CI workflow ([#2620](https://github.com/ordinals/ord/pull/2620) by [casey](https://github.com/casey)) +- Update README.md and zh.po ([#2605](https://github.com/ordinals/ord/pull/2605) by [DrJingLee](https://github.com/DrJingLee)) +- Require --batch or --file for `ord wallet inscribe` ([#2581](https://github.com/ordinals/ord/pull/2581) by [casey](https://github.com/casey)) ### Fixed -- Respect locked coins (#2618) -- Set `Cache-Control: no-store` header on 404 responses (#2637) -- Fix statistics table and increment schema version (#2624) -- Fix broken link in README (#2621) -- Speed up indexing of re-inscriptions (#2608) -- Fix docs rendering (#2612) -- Update docs with new position of --enable-json-api (#2601) -- Move `--enable-json-api` flag to server options (#2599) -- Make server_runs_with_rpc_user_and_pass_as_env_vars test less flaky (#2580) +- Respect locked coins ([#2618](https://github.com/ordinals/ord/pull/2618) by [rot13maxi](https://github.com/rot13maxi)) +- Set `Cache-Control: no-store` header on 404 responses ([#2637](https://github.com/ordinals/ord/pull/2637) by [devords](https://github.com/devords)) +- Fix statistics table and increment schema version ([#2624](https://github.com/ordinals/ord/pull/2624) by [raphjaph](https://github.com/raphjaph)) +- Fix broken link in README ([#2621](https://github.com/ordinals/ord/pull/2621) by [yixinrock](https://github.com/yixinrock)) +- Speed up indexing of re-inscriptions ([#2608](https://github.com/ordinals/ord/pull/2608) by [SmarakNayak](https://github.com/SmarakNayak)) +- Fix docs rendering ([#2612](https://github.com/ordinals/ord/pull/2612) by [casey](https://github.com/casey)) +- Update docs with new position of --enable-json-api ([#2601](https://github.com/ordinals/ord/pull/2601) by [elocremarc](https://github.com/elocremarc)) +- Move `--enable-json-api` flag to server options ([#2599](https://github.com/ordinals/ord/pull/2599) by [raphjaph](https://github.com/raphjaph)) +- Make server_runs_with_rpc_user_and_pass_as_env_vars test less flaky ([#2580](https://github.com/ordinals/ord/pull/2580) by [casey](https://github.com/casey)) ### Runes -- Implement open etchings (#2548) -- Add more info to /rune page and link to rune from /tx (#2528) -- Display inscription on /rune (#2542) -- Add rune numbers (#2557) -- Ignore non push opcodes in runestones (#2553) -- Improve rune minimum at height (#2546) +- Implement open etchings ([#2548](https://github.com/ordinals/ord/pull/2548) by [casey](https://github.com/casey)) +- Add more info to /rune page and link to rune from /tx ([#2528](https://github.com/ordinals/ord/pull/2528) by [casey](https://github.com/casey)) +- Display inscription on /rune ([#2542](https://github.com/ordinals/ord/pull/2542) by [casey](https://github.com/casey)) +- Add rune numbers ([#2557](https://github.com/ordinals/ord/pull/2557) by [casey](https://github.com/casey)) +- Ignore non push opcodes in runestones ([#2553](https://github.com/ordinals/ord/pull/2553) by [casey](https://github.com/casey)) +- Improve rune minimum at height ([#2546](https://github.com/ordinals/ord/pull/2546) by [casey](https://github.com/casey)) [0.10.0](https://github.com/ordinals/ord/releases/tag/0.10.0) - 2023-10-23 -------------------------------------------------------------------------- ### Added -- Batch inscriptions (#2504) -- Add teleburn command to generate Ethereum teleburn addresses (#1680) -- Add Korean version of handbook (#2560) -- Add German version of handbook (#2441) -- Add Arabic version of handbook (#2442) -- Add French version of handbook (#2508) -- Implement pointer spec (#2499) -- Add pointer spec (#2383) -- Add Russian version of handbook (#2468) -- Add inscription number endpoint (#2485) -- Allow inscriptions to include CBOR metadata (#2421) -- Add Filipino version of handbook (#2483) -- Add code syntax highlighting to preview (#2471) -- Add font media types (#2464) -- Render markdown previews (#2325) -- Add metaprotocol field (#2449) -- Add Spanish version of handbook (#2448) -- Add `application/cbor` media type with extension `.cbor` (#2446) +- Batch inscriptions ([#2504](https://github.com/ordinals/ord/pull/2504) by [raphjaph](https://github.com/raphjaph)) +- Add teleburn command to generate Ethereum teleburn addresses ([#1680](https://github.com/ordinals/ord/pull/1680) by [casey](https://github.com/casey)) +- Add Korean version of handbook ([#2560](https://github.com/ordinals/ord/pull/2560) by [Neofishtwo](https://github.com/Neofishtwo)) +- Add German version of handbook ([#2441](https://github.com/ordinals/ord/pull/2441) by [ordinaHO](https://github.com/ordinaHO)) +- Add Arabic version of handbook ([#2442](https://github.com/ordinals/ord/pull/2442) by [ordinaHO](https://github.com/ordinaHO)) +- Add French version of handbook ([#2508](https://github.com/ordinals/ord/pull/2508) by [rupturenft](https://github.com/rupturenft)) +- Implement pointer spec ([#2499](https://github.com/ordinals/ord/pull/2499) by [raphjaph](https://github.com/raphjaph)) +- Add pointer spec ([#2383](https://github.com/ordinals/ord/pull/2383) by [casey](https://github.com/casey)) +- Add Russian version of handbook ([#2468](https://github.com/ordinals/ord/pull/2468) by [DrJingLee](https://github.com/DrJingLee)) +- Add inscription number endpoint ([#2485](https://github.com/ordinals/ord/pull/2485) by [raphjaph](https://github.com/raphjaph)) +- Allow inscriptions to include CBOR metadata ([#2421](https://github.com/ordinals/ord/pull/2421) by [casey](https://github.com/casey)) +- Add Filipino version of handbook ([#2483](https://github.com/ordinals/ord/pull/2483) by [jcatama](https://github.com/jcatama)) +- Add code syntax highlighting to preview ([#2471](https://github.com/ordinals/ord/pull/2471) by [elocremarc](https://github.com/elocremarc)) +- Add font media types ([#2464](https://github.com/ordinals/ord/pull/2464) by [devords](https://github.com/devords)) +- Render markdown previews ([#2325](https://github.com/ordinals/ord/pull/2325) by [elocremarc](https://github.com/elocremarc)) +- Add metaprotocol field ([#2449](https://github.com/ordinals/ord/pull/2449) by [casey](https://github.com/casey)) +- Add Spanish version of handbook ([#2448](https://github.com/ordinals/ord/pull/2448) by [Psifour](https://github.com/Psifour)) +- Add `application/cbor` media type with extension `.cbor` ([#2446](https://github.com/ordinals/ord/pull/2446) by [casey](https://github.com/casey)) ### Changed -- Create single-directory release archives (#2537) -- Allow fixed length encoding for parent id in child inscription (#2519) -- Recognize inscriptions with pushnum opcodes (#2497) -- Rename `index run` -> `index update` (#2462) -- Refactor inscription parsing (#2461) -- Allow running `find` on a range of sats (#1992) +- Create single-directory release archives ([#2537](https://github.com/ordinals/ord/pull/2537) by [casey](https://github.com/casey)) +- Allow fixed length encoding for parent id in child inscription ([#2519](https://github.com/ordinals/ord/pull/2519) by [veryordinally](https://github.com/veryordinally)) +- Recognize inscriptions with pushnum opcodes ([#2497](https://github.com/ordinals/ord/pull/2497) by [casey](https://github.com/casey)) +- Rename `index run` -> `index update` ([#2462](https://github.com/ordinals/ord/pull/2462) by [casey](https://github.com/casey)) +- Refactor inscription parsing ([#2461](https://github.com/ordinals/ord/pull/2461) by [casey](https://github.com/casey)) +- Allow running `find` on a range of sats ([#1992](https://github.com/ordinals/ord/pull/1992) by [gmart7t2](https://github.com/gmart7t2)) ### Fixed -- Fix overflow in Sat::from_name (#2500) -- Fix issue with `--satpoint` when offset not 0 (#2466) +- Fix overflow in Sat::from_name ([#2500](https://github.com/ordinals/ord/pull/2500) by [casey](https://github.com/casey)) +- Fix issue with `--satpoint` when offset not 0 ([#2466](https://github.com/ordinals/ord/pull/2466) by [felipelincoln](https://github.com/felipelincoln)) ### Misc -- Remove paranthetical annotations (#2540) -- Refactor index checks (#2541) -- Don't add path component in bin/package (#2536) -- Metadata Filipino translation (#2517) -- Add pointer spec to docs (#2533) -- Make inscriptions with pointer cursed (#2523) -- Small refactor for inscribe code (#2515) -- Pre-allocate vector size (#1960) -- Add troubleshooting guide for syncing bitcoind (#1737) -- Same input envelopes become reinscriptions (#2478) -- Document JSON-API (#2484) -- Update parent-child guide (#2487) -- Add regtest flag to bitcoin-cli docs (#2488) -- Update overview.md (#2456) -- Correct donation address (#2475) -- Fixes release tarbomb (#2473) -- Update dependencies (#2470) -- Add internal sequence number (#2460) -- Update guide with parent-child and json API (#2429) -- Update Japanese handbook version with provenance section (#2450) +- Remove paranthetical annotations ([#2540](https://github.com/ordinals/ord/pull/2540) by [casey](https://github.com/casey)) +- Refactor index checks ([#2541](https://github.com/ordinals/ord/pull/2541) by [casey](https://github.com/casey)) +- Don't add path component in bin/package ([#2536](https://github.com/ordinals/ord/pull/2536) by [casey](https://github.com/casey)) +- Metadata Filipino translation ([#2517](https://github.com/ordinals/ord/pull/2517) by [jcatama](https://github.com/jcatama)) +- Add pointer spec to docs ([#2533](https://github.com/ordinals/ord/pull/2533) by [raphjaph](https://github.com/raphjaph)) +- Make inscriptions with pointer cursed ([#2523](https://github.com/ordinals/ord/pull/2523) by [raphjaph](https://github.com/raphjaph)) +- Small refactor for inscribe code ([#2515](https://github.com/ordinals/ord/pull/2515) by [raphjaph](https://github.com/raphjaph)) +- Pre-allocate vector size ([#1960](https://github.com/ordinals/ord/pull/1960) by [bonedaddy](https://github.com/bonedaddy)) +- Add troubleshooting guide for syncing bitcoind ([#1737](https://github.com/ordinals/ord/pull/1737) by [andrewtoth](https://github.com/andrewtoth)) +- Same input envelopes become reinscriptions ([#2478](https://github.com/ordinals/ord/pull/2478) by [raphjaph](https://github.com/raphjaph)) +- Document JSON-API ([#2484](https://github.com/ordinals/ord/pull/2484) by [raphjaph](https://github.com/raphjaph)) +- Update parent-child guide ([#2487](https://github.com/ordinals/ord/pull/2487) by [raphjaph](https://github.com/raphjaph)) +- Add regtest flag to bitcoin-cli docs ([#2488](https://github.com/ordinals/ord/pull/2488) by [elocremarc](https://github.com/elocremarc)) +- Update overview.md ([#2456](https://github.com/ordinals/ord/pull/2456) by [ordinaHO](https://github.com/ordinaHO)) +- Correct donation address ([#2475](https://github.com/ordinals/ord/pull/2475) by [raphjaph](https://github.com/raphjaph)) +- Fixes release tarbomb ([#2473](https://github.com/ordinals/ord/pull/2473) by [uzyn](https://github.com/uzyn)) +- Update dependencies ([#2470](https://github.com/ordinals/ord/pull/2470) by [casey](https://github.com/casey)) +- Add internal sequence number ([#2460](https://github.com/ordinals/ord/pull/2460) by [raphjaph](https://github.com/raphjaph)) +- Update guide with parent-child and json API ([#2429](https://github.com/ordinals/ord/pull/2429) by [elocremarc](https://github.com/elocremarc)) +- Update Japanese handbook version with provenance section ([#2450](https://github.com/ordinals/ord/pull/2450) by [DrJingLee](https://github.com/DrJingLee)) ### Runes -- Implement splits (#2530) -- Add rune fuzz targets (#2526) -- Allow searching by rune or rune ID (#2522) -- Encode runestones with tags (#2547) -- Edict with zero amount allocates all remaining runes (#2531) -- Always create rune, even if none were allocated (#2543) -- Show rune balances on /output page (#2527) -- Delta encode Rune IDs in edicts (#2532) -- Add test to keep track of runestone size (#2529) -- Show etching and inscription on /rune page (#2512) -- Track burned runes (#2511) -- Don't encode divisibility if zero (#2510) -- Format rune supply using divisibility (#2509) -- Add pre-alpha unstable incomplete half-baked rune index (#2491) +- Implement splits ([#2530](https://github.com/ordinals/ord/pull/2530) by [casey](https://github.com/casey)) +- Add rune fuzz targets ([#2526](https://github.com/ordinals/ord/pull/2526) by [casey](https://github.com/casey)) +- Allow searching by rune or rune ID ([#2522](https://github.com/ordinals/ord/pull/2522) by [casey](https://github.com/casey)) +- Encode runestones with tags ([#2547](https://github.com/ordinals/ord/pull/2547) by [casey](https://github.com/casey)) +- Edict with zero amount allocates all remaining runes ([#2531](https://github.com/ordinals/ord/pull/2531) by [casey](https://github.com/casey)) +- Always create rune, even if none were allocated ([#2543](https://github.com/ordinals/ord/pull/2543) by [casey](https://github.com/casey)) +- Show rune balances on /output page ([#2527](https://github.com/ordinals/ord/pull/2527) by [casey](https://github.com/casey)) +- Delta encode Rune IDs in edicts ([#2532](https://github.com/ordinals/ord/pull/2532) by [casey](https://github.com/casey)) +- Add test to keep track of runestone size ([#2529](https://github.com/ordinals/ord/pull/2529) by [casey](https://github.com/casey)) +- Show etching and inscription on /rune page ([#2512](https://github.com/ordinals/ord/pull/2512) by [casey](https://github.com/casey)) +- Track burned runes ([#2511](https://github.com/ordinals/ord/pull/2511) by [casey](https://github.com/casey)) +- Don't encode divisibility if zero ([#2510](https://github.com/ordinals/ord/pull/2510) by [casey](https://github.com/casey)) +- Format rune supply using divisibility ([#2509](https://github.com/ordinals/ord/pull/2509) by [casey](https://github.com/casey)) +- Add pre-alpha unstable incomplete half-baked rune index ([#2491](https://github.com/ordinals/ord/pull/2491) by [casey](https://github.com/casey)) [0.9.0](https://github.com/ordinals/ord/releases/tag/0.9.0) - 2023-09-11 ------------------------------------------------------------------------ ### Added -- Allow reinscribing with wallet (#2432) -- Provide more detailed translation instructions (#2443) -- Add Japanese version of handbook (#2426) -- Add provenance to docs summary (#2427) -- Inscribe with parent (#2388) -- Add provenance spec (#2278) -- Implement provenance in index (#2353) -- Add application/protobuf media type (#2389) -- Install mdbook-i18n-helpers in Github Workflows (#2408) -- Add `decode` command (#2401) -- Add Chinese version of the handbook (#2406) -- Add language picker for docs (#2403) -- Add reindexing docs (#2393) -- Vaccuum log with every new deploy (#2390) +- Allow reinscribing with wallet ([#2432](https://github.com/ordinals/ord/pull/2432) by [raphjaph](https://github.com/raphjaph)) +- Provide more detailed translation instructions ([#2443](https://github.com/ordinals/ord/pull/2443) by [DrJingLee](https://github.com/DrJingLee)) +- Add Japanese version of handbook ([#2426](https://github.com/ordinals/ord/pull/2426) by [DrJingLee](https://github.com/DrJingLee)) +- Add provenance to docs summary ([#2427](https://github.com/ordinals/ord/pull/2427) by [casey](https://github.com/casey)) +- Inscribe with parent ([#2388](https://github.com/ordinals/ord/pull/2388) by [raphjaph](https://github.com/raphjaph)) +- Add provenance spec ([#2278](https://github.com/ordinals/ord/pull/2278) by [casey](https://github.com/casey)) +- Implement provenance in index ([#2353](https://github.com/ordinals/ord/pull/2353) by [casey](https://github.com/casey)) +- Add application/protobuf media type ([#2389](https://github.com/ordinals/ord/pull/2389) by [casey](https://github.com/casey)) +- Install mdbook-i18n-helpers in Github Workflows ([#2408](https://github.com/ordinals/ord/pull/2408) by [raphjaph](https://github.com/raphjaph)) +- Add `decode` command ([#2401](https://github.com/ordinals/ord/pull/2401) by [casey](https://github.com/casey)) +- Add Chinese version of the handbook ([#2406](https://github.com/ordinals/ord/pull/2406) by [DrJingLee](https://github.com/DrJingLee)) +- Add language picker for docs ([#2403](https://github.com/ordinals/ord/pull/2403) by [raphjaph](https://github.com/raphjaph)) +- Add reindexing docs ([#2393](https://github.com/ordinals/ord/pull/2393) by [raphjaph](https://github.com/raphjaph)) +- Vaccuum log with every new deploy ([#2390](https://github.com/ordinals/ord/pull/2390) by [raphjaph](https://github.com/raphjaph)) ### Changed -- Fold BlockIndex into database (#2436) -- Prevent search when query field is empty (#2425) -- Make any zero-valued input inscription unbound (#2397) -- Tweak translations intructions (#2413) -- Remove unused itertools dependency (#2416) -- Update dependencies (#2414) -- Update clap (#2415) -- Use tapscript extraction from rust-bitcoin (#2404) -- Allocate blocks vector ahead of time (#2409) -- Deduplicate sat range summation logic (#2402) -- Inscriptions with unrecognized even fields are unbound and cursed (#2359) -- Remove unused content_response match statement (#2384) +- Fold BlockIndex into database ([#2436](https://github.com/ordinals/ord/pull/2436) by [raphjaph](https://github.com/raphjaph)) +- Prevent search when query field is empty ([#2425](https://github.com/ordinals/ord/pull/2425) by [elocremarc](https://github.com/elocremarc)) +- Make any zero-valued input inscription unbound ([#2397](https://github.com/ordinals/ord/pull/2397) by [raphjaph](https://github.com/raphjaph)) +- Tweak translations intructions ([#2413](https://github.com/ordinals/ord/pull/2413) by [raphjaph](https://github.com/raphjaph)) +- Remove unused itertools dependency ([#2416](https://github.com/ordinals/ord/pull/2416) by [casey](https://github.com/casey)) +- Update dependencies ([#2414](https://github.com/ordinals/ord/pull/2414) by [casey](https://github.com/casey)) +- Update clap ([#2415](https://github.com/ordinals/ord/pull/2415) by [casey](https://github.com/casey)) +- Use tapscript extraction from rust-bitcoin ([#2404](https://github.com/ordinals/ord/pull/2404) by [casey](https://github.com/casey)) +- Allocate blocks vector ahead of time ([#2409](https://github.com/ordinals/ord/pull/2409) by [casey](https://github.com/casey)) +- Deduplicate sat range summation logic ([#2402](https://github.com/ordinals/ord/pull/2402) by [casey](https://github.com/casey)) +- Inscriptions with unrecognized even fields are unbound and cursed ([#2359](https://github.com/ordinals/ord/pull/2359) by [raphjaph](https://github.com/raphjaph)) +- Remove unused content_response match statement ([#2384](https://github.com/ordinals/ord/pull/2384) by [casey](https://github.com/casey)) ### Fixed -- Fix type (#2444) -- Fix Chinese translation typos and format errors (#2419) -- Fix UTXO selection in mock Bitcoin Core instance(#2417) +- Fix type ([#2444](https://github.com/ordinals/ord/pull/2444) by [DrJingLee](https://github.com/DrJingLee)) +- Fix Chinese translation typos and format errors ([#2419](https://github.com/ordinals/ord/pull/2419) by [DrJingLee](https://github.com/DrJingLee)) +- Fix UTXO selection in mock Bitcoin Core instance([#2417](https://github.com/ordinals/ord/pull/2417) by [raphjaph](https://github.com/raphjaph)) [0.8.3](https://github.com/ordinals/ord/releases/tag/0.8.3) - 2023-08-28 ------------------------------------------------------------------------ ### Added -- Tweaks to front-end (#2381) -- Add some links to docs (#2364) -- Add testing guide for recursion (#2357) -- Make homepage more interesting (#2374) -- Add proper block inscriptions HTML (#2337) -- Render GLB/GLTF models in preview (#2369) -- Add tags and inscription id documentation (#2351) -- Add hint about maximum number of open files for testing (#2348) -- Reduce index durability when testing (#2347) -- Homogenize design (#2346) +- Tweaks to front-end ([#2381](https://github.com/ordinals/ord/pull/2381) by [raphjaph](https://github.com/raphjaph)) +- Add some links to docs ([#2364](https://github.com/ordinals/ord/pull/2364) by [ordinaHO](https://github.com/ordinaHO)) +- Add testing guide for recursion ([#2357](https://github.com/ordinals/ord/pull/2357) by [elocremarc](https://github.com/elocremarc)) +- Make homepage more interesting ([#2374](https://github.com/ordinals/ord/pull/2374) by [raphjaph](https://github.com/raphjaph)) +- Add proper block inscriptions HTML ([#2337](https://github.com/ordinals/ord/pull/2337) by [veryordinally](https://github.com/veryordinally)) +- Render GLB/GLTF models in preview ([#2369](https://github.com/ordinals/ord/pull/2369) by [raphjaph](https://github.com/raphjaph)) +- Add tags and inscription id documentation ([#2351](https://github.com/ordinals/ord/pull/2351) by [raphjaph](https://github.com/raphjaph)) +- Add hint about maximum number of open files for testing ([#2348](https://github.com/ordinals/ord/pull/2348) by [casey](https://github.com/casey)) +- Reduce index durability when testing ([#2347](https://github.com/ordinals/ord/pull/2347) by [casey](https://github.com/casey)) +- Homogenize design ([#2346](https://github.com/ordinals/ord/pull/2346) by [casey](https://github.com/casey)) ### Fixed -- Fix slice error for inscriptions block view (#2378) -- Use correct height and depth in reorg log (#2352) +- Fix slice error for inscriptions block view ([#2378](https://github.com/ordinals/ord/pull/2378) by [veryordinally](https://github.com/veryordinally)) +- Use correct height and depth in reorg log ([#2352](https://github.com/ordinals/ord/pull/2352) by [gmart7t2](https://github.com/gmart7t2)) ### Changed -- Remove transaction ID to inscription ID conversion (#2370) -- Return JSON from all commands (#2355) -- Allow splitting merged inscriptions (#1927) -- Update explorer.md (#2215) -- Recognize media types without explicit charset (#2349) +- Remove transaction ID to inscription ID conversion ([#2370](https://github.com/ordinals/ord/pull/2370) by [casey](https://github.com/casey)) +- Return JSON from all commands ([#2355](https://github.com/ordinals/ord/pull/2355) by [casey](https://github.com/casey)) +- Allow splitting merged inscriptions ([#1927](https://github.com/ordinals/ord/pull/1927) by [gmart7t2](https://github.com/gmart7t2)) +- Update explorer.md ([#2215](https://github.com/ordinals/ord/pull/2215) by [elocremarc](https://github.com/elocremarc)) +- Recognize media types without explicit charset ([#2349](https://github.com/ordinals/ord/pull/2349) by [casey](https://github.com/casey)) [0.8.2](https://github.com/ordinals/ord/releases/tag/0.8.2) - 2023-08-17 ------------------------------------------------------------------------ ### Added -- Allow setting custom postage (#2331) -- Make retrieving inscriptions in block fast (#2333) -- JSON API for `/inscription`, `/inscriptions` and `/output` (#2323) -- Ignore invalid content type header values (#2326) -- Add reorg resistance (#2320) -- Add JSON API endpoint `/sat/` (#2250) -- Add `amount` field to `wallet inscriptions` output. (#1928) +- Allow setting custom postage ([#2331](https://github.com/ordinals/ord/pull/2331) by [raphjaph](https://github.com/raphjaph)) +- Make retrieving inscriptions in block fast ([#2333](https://github.com/ordinals/ord/pull/2333) by [veryordinally](https://github.com/veryordinally)) +- JSON API for `/inscription`, `/inscriptions` and `/output` ([#2323](https://github.com/ordinals/ord/pull/2323) by [veryordinally](https://github.com/veryordinally)) +- Ignore invalid content type header values ([#2326](https://github.com/ordinals/ord/pull/2326) by [casey](https://github.com/casey)) +- Add reorg resistance ([#2320](https://github.com/ordinals/ord/pull/2320) by [raphjaph](https://github.com/raphjaph)) +- Add JSON API endpoint `/sat/` ([#2250](https://github.com/ordinals/ord/pull/2250) by [Mathieu-Be](https://github.com/Mathieu-Be)) +- Add `amount` field to `wallet inscriptions` output. ([#1928](https://github.com/ordinals/ord/pull/1928) by [gmart7t2](https://github.com/gmart7t2)) ### Changed -- Only fetch inscriptions that are owned by the ord wallet (#2310) -- Inform user when redb starts in recovery mode (#2304) -- Select multiple utxos (#2303) +- Only fetch inscriptions that are owned by the ord wallet ([#2310](https://github.com/ordinals/ord/pull/2310) by [gmart7t2](https://github.com/gmart7t2)) +- Inform user when redb starts in recovery mode ([#2304](https://github.com/ordinals/ord/pull/2304) by [gmart7t2](https://github.com/gmart7t2)) +- Select multiple utxos ([#2303](https://github.com/ordinals/ord/pull/2303) by [raphjaph](https://github.com/raphjaph)) ### Fixed -- Use `--fee-rate` when sending an amount (#1922) -- Fix typos in documentation (#2328) -- Fix dust limit for padding in `TransactionBuilder` (#1929) -- Fix remote RPC wallet commands (#1766) +- Use `--fee-rate` when sending an amount ([#1922](https://github.com/ordinals/ord/pull/1922) by [gmart7t2](https://github.com/gmart7t2)) +- Fix typos in documentation ([#2328](https://github.com/ordinals/ord/pull/2328) by [omahs](https://github.com/omahs)) +- Fix dust limit for padding in `TransactionBuilder` ([#1929](https://github.com/ordinals/ord/pull/1929) by [gmart7t2](https://github.com/gmart7t2)) +- Fix remote RPC wallet commands ([#1766](https://github.com/ordinals/ord/pull/1766) by [carlosalaniz](https://github.com/carlosalaniz)) [0.8.1](https://github.com/ordinals/ord/releases/tag/0.8.1) - 2023-07-23 --------------------------------------------------------------------- ### Added -- Add retry to fetcher (#2297) -- Add satpoint and address to index export (#2284) -- Don't create default data directory if --index overrides it (#1991) -- Implement clean index shutdown to prevent index corruption (with clippy updates for Rust 1.71) (#2275) -- Set lower max age for not found (#2240) +- Add retry to fetcher ([#2297](https://github.com/ordinals/ord/pull/2297) by [victorkirov](https://github.com/victorkirov)) +- Add satpoint and address to index export ([#2284](https://github.com/ordinals/ord/pull/2284) by [veryordinally](https://github.com/veryordinally)) +- Don't create default data directory if --index overrides it ([#1991](https://github.com/ordinals/ord/pull/1991) by [gmart7t2](https://github.com/gmart7t2)) +- Implement clean index shutdown to prevent index corruption (with clippy updates for Rust 1.71) ([#2275](https://github.com/ordinals/ord/pull/2275) by [raphjaph](https://github.com/raphjaph)) +- Set lower max age for not found ([#2240](https://github.com/ordinals/ord/pull/2240) by [revofusion](https://github.com/revofusion)) ### Changed -- Fix justfile recipe (#2299) -- Clean up deploy scripts (#2298) -- Update redb (#2294) -- Update bitcoin dependencies (#2281) -- Fix ordering for reinscriptions and show all reinscriptions for sat (#2279) -- Modify `ord list` output to include the end of each range (#1998) +- Fix justfile recipe ([#2299](https://github.com/ordinals/ord/pull/2299) by [raphjaph](https://github.com/raphjaph)) +- Clean up deploy scripts ([#2298](https://github.com/ordinals/ord/pull/2298) by [raphjaph](https://github.com/raphjaph)) +- Update redb ([#2294](https://github.com/ordinals/ord/pull/2294) by [raphjaph](https://github.com/raphjaph)) +- Update bitcoin dependencies ([#2281](https://github.com/ordinals/ord/pull/2281) by [raphjaph](https://github.com/raphjaph)) +- Fix ordering for reinscriptions and show all reinscriptions for sat ([#2279](https://github.com/ordinals/ord/pull/2279) by [veryordinally](https://github.com/veryordinally)) +- Modify `ord list` output to include the end of each range ([#1998](https://github.com/ordinals/ord/pull/1998) by [gmart7t2](https://github.com/gmart7t2)) ### Documentation -- Fix docs inconsistency (#2276) -- Add contributing section (#2261) +- Fix docs inconsistency ([#2276](https://github.com/ordinals/ord/pull/2276) by [raphjaph](https://github.com/raphjaph)) +- Add contributing section ([#2261](https://github.com/ordinals/ord/pull/2261) by [raphjaph](https://github.com/raphjaph)) [0.8.0](https://github.com/ordinals/ord/releases/tag/0.8.0) - 2023-07-01 --------------------------------------------------------------------- ### Added -- Dev server deploy script (#2228) -- Set DB cache size (#2224) -- Update redb from 0.13.0 to 1.0.2 (#2141) -- Fix typo in BIP (#2220) +- Dev server deploy script ([#2228](https://github.com/ordinals/ord/pull/2228) by [raphjaph](https://github.com/raphjaph)) +- Set DB cache size ([#2224](https://github.com/ordinals/ord/pull/2224) by [raphjaph](https://github.com/raphjaph)) +- Update redb from 0.13.0 to 1.0.2 ([#2141](https://github.com/ordinals/ord/pull/2141) by [raphjaph](https://github.com/raphjaph)) +- Fix typo in BIP ([#2220](https://github.com/ordinals/ord/pull/2220) by [ilanolkies](https://github.com/ilanolkies)) [0.7.0](https://github.com/ordinals/ord/releases/tag/0.7.0) - 2023-06-23 --------------------------------------------------------------------- ### Added -- Tweak publish recipe (#2212) -- Handle cursed inscriptions edge cases (#2209) -- Add export command for table (#2208) -- Add Markdown media type (#2206) -- Add blob urls to Content Security Policy headers (#2203) -- Check inscribe destination address network (#2189) +- Tweak publish recipe ([#2212](https://github.com/ordinals/ord/pull/2212) by [raphjaph](https://github.com/raphjaph)) +- Handle cursed inscriptions edge cases ([#2209](https://github.com/ordinals/ord/pull/2209) by [veryordinally](https://github.com/veryordinally)) +- Add export command for table ([#2208](https://github.com/ordinals/ord/pull/2208) by [raphjaph](https://github.com/raphjaph)) +- Add Markdown media type ([#2206](https://github.com/ordinals/ord/pull/2206) by [elocremarc](https://github.com/elocremarc)) +- Add blob urls to Content Security Policy headers ([#2203](https://github.com/ordinals/ord/pull/2203) by [Vanniix](https://github.com/Vanniix)) +- Check inscribe destination address network ([#2189](https://github.com/ordinals/ord/pull/2189) by [casey](https://github.com/casey)) [0.6.2](https://github.com/ordinals/ord/releases/tag/0.6.2) - 2023-06-15 --------------------------------------------------------------------- ### Added -- Recursive endpoints: `/blockhash, /blockheight, /blocktime` (#2175) -- Document recursion (#2174) -- Add CSS and JavaScript media types (#2173) -- Recursive Inscriptions (#2167) +- Recursive endpoints: `/blockhash, /blockheight, /blocktime` ([#2175](https://github.com/ordinals/ord/pull/2175) by [raphjaph](https://github.com/raphjaph)) +- Document recursion ([#2174](https://github.com/ordinals/ord/pull/2174) by [casey](https://github.com/casey)) +- Add CSS and JavaScript media types ([#2173](https://github.com/ordinals/ord/pull/2173) by [casey](https://github.com/casey)) +- Recursive Inscriptions ([#2167](https://github.com/ordinals/ord/pull/2167) by [casey](https://github.com/casey)) ### Misc -- Update ord dependency in lockfile (#2168) +- Update ord dependency in lockfile ([#2168](https://github.com/ordinals/ord/pull/2168) by [casey](https://github.com/casey)) [0.6.1](https://github.com/ordinals/ord/releases/tag/0.6.1) - 2023-06-06 --------------------------------------------------------------------- ### Changed -- Fix sat index test and unbound assignment (#2154) -- Updated install.sh for new repo name (#2155) +- Fix sat index test and unbound assignment ([#2154](https://github.com/ordinals/ord/pull/2154) by [raphjaph](https://github.com/raphjaph)) +- Updated install.sh for new repo name ([#2155](https://github.com/ordinals/ord/pull/2155) by [LightRider5](https://github.com/LightRider5)) [0.6.0](https://github.com/ordinals/ord/releases/tag/0.6.0) - 2023-06-04 --------------------------------------------------------------------- ### Added -- Cursed Inscriptions [1/n] (#2145) -- Authenticate to bitcoin using a username and password (#1527) -- Add example config file (#2044) +- Cursed Inscriptions [1/n] ([#2145](https://github.com/ordinals/ord/pull/2145) by [raphjaph](https://github.com/raphjaph)) +- Authenticate to bitcoin using a username and password ([#1527](https://github.com/ordinals/ord/pull/1527) by [raphjaph](https://github.com/raphjaph)) +- Add example config file ([#2044](https://github.com/ordinals/ord/pull/2044) by [soenkehahn](https://github.com/soenkehahn)) ### Changed -- Unbind inscriptions from zero-sat transactions (#2107) +- Unbind inscriptions from zero-sat transactions ([#2107](https://github.com/ordinals/ord/pull/2107) by [casey](https://github.com/casey)) ### Documentation -- Tweak doc: Inscriptions made on first sat of input (#2148) -- `OP_PUSH` instead of `OP_1` in inscription docs (#2135) -- Document bitcoind RPC authentication options (#2056) -- Fix typo in Sparrow Wallet docs (#2077) -- Update donate.md for inscriptions donations. (#2125) -- Promote raphjaph to lead maintainer 🫡 (#2119) -- Improve donation page (#2034) +- Tweak doc: Inscriptions made on first sat of input ([#2148](https://github.com/ordinals/ord/pull/2148) by [raphjaph](https://github.com/raphjaph)) +- `OP_PUSH` instead of `OP_1` in inscription docs ([#2135](https://github.com/ordinals/ord/pull/2135) by [raphjaph](https://github.com/raphjaph)) +- Document bitcoind RPC authentication options ([#2056](https://github.com/ordinals/ord/pull/2056) by [casey](https://github.com/casey)) +- Fix typo in Sparrow Wallet docs ([#2077](https://github.com/ordinals/ord/pull/2077) by [eltociear](https://github.com/eltociear)) +- Update donate.md for inscriptions donations. ([#2125](https://github.com/ordinals/ord/pull/2125) by [veryordinally](https://github.com/veryordinally)) +- Promote raphjaph to lead maintainer 🫡 ([#2119](https://github.com/ordinals/ord/pull/2119) by [casey](https://github.com/casey)) +- Improve donation page ([#2034](https://github.com/ordinals/ord/pull/2034) by [casey](https://github.com/casey)) ### Misc -- Switch CI back to stable clippy (#2108) -- Update dependencies (#2068) -- Use struct variants in Origin enum (#2067) -- Fix test name typos(#2043) -- Switch to nightly clippy (#2037) +- Switch CI back to stable clippy ([#2108](https://github.com/ordinals/ord/pull/2108) by [casey](https://github.com/casey)) +- Update dependencies ([#2068](https://github.com/ordinals/ord/pull/2068) by [casey](https://github.com/casey)) +- Use struct variants in Origin enum ([#2067](https://github.com/ordinals/ord/pull/2067) by [casey](https://github.com/casey)) +- Fix test name typos([#2043](https://github.com/ordinals/ord/pull/2043) by [soenkehahn](https://github.com/soenkehahn)) +- Switch to nightly clippy ([#2037](https://github.com/ordinals/ord/pull/2037) by [soenkehahn](https://github.com/soenkehahn)) [0.5.2](https://github.com/ordinals/ord/releases/tag/0.5.2) - 2023-04-17 --------------------------------------------------------------------- ### Added -- Add `ord wallet cardinals` command to list the cardinal outputs (#1904) +- Add `ord wallet cardinals` command to list the cardinal outputs ([#1904](https://github.com/ordinals/ord/pull/1904) by [gmart7t2](https://github.com/gmart7t2)) ### Changed -- Shut down immediately after two interrupts (#2008) -- Mandatory fee rate for inscribe (#1897) -- Add error when a satpoint's offset exceeds the size of its output (#1857) +- Shut down immediately after two interrupts ([#2008](https://github.com/ordinals/ord/pull/2008) by [terror](https://github.com/terror)) +- Mandatory fee rate for inscribe ([#1897](https://github.com/ordinals/ord/pull/1897) by [gmart7t2](https://github.com/gmart7t2)) +- Add error when a satpoint's offset exceeds the size of its output ([#1857](https://github.com/ordinals/ord/pull/1857) by [gmart7t2](https://github.com/gmart7t2)) ### Fixed -- Fix fee-spent inscription tracking (#1971) -- Label change and receive addresses correctly (#1847) -- Correct reveal tx fee calculation (#1853) +- Fix fee-spent inscription tracking ([#1971](https://github.com/ordinals/ord/pull/1971) by [gmart7t2](https://github.com/gmart7t2)) +- Label change and receive addresses correctly ([#1847](https://github.com/ordinals/ord/pull/1847) by [gmart7t2](https://github.com/gmart7t2)) +- Correct reveal tx fee calculation ([#1853](https://github.com/ordinals/ord/pull/1853) by [gmart7t2](https://github.com/gmart7t2)) ### Misc -- Misc changes (#2025) -- Misc doc fixes (#2021) -- Typo in sparrow wallet guide (#1947) -- Miscellaneous design improvements (#1968) -- Update miniscript dependency to 9.0.1 (#1966) -- Skip indexing inscriptions when below first inscription also for `--index-sats`(#1828) -- Better interrupt message (#1874) -- Fix colored coins link in BIP (#1856) -- Added cozy pair programming twitch link to README.md (#1827) -- Create rpc client after updating index (#1731) -- Add additional err msg to build from source for users who's arch falls outside of the list (#1792) -- Add note on default build location (#1625) -- Minor copy fixes (#1730) -- Typo (#1815) +- Misc changes ([#2025](https://github.com/ordinals/ord/pull/2025) by [casey](https://github.com/casey)) +- Misc doc fixes ([#2021](https://github.com/ordinals/ord/pull/2021) by [raphjaph](https://github.com/raphjaph)) +- Typo in sparrow wallet guide ([#1947](https://github.com/ordinals/ord/pull/1947) by [gmart7t2](https://github.com/gmart7t2)) +- Miscellaneous design improvements ([#1968](https://github.com/ordinals/ord/pull/1968) by [raphjaph](https://github.com/raphjaph)) +- Update miniscript dependency to 9.0.1 ([#1966](https://github.com/ordinals/ord/pull/1966) by [soenkehahn](https://github.com/soenkehahn)) +- Skip indexing inscriptions when below first inscription also for `--index-sats`([#1828](https://github.com/ordinals/ord/pull/1828) by [andrewtoth](https://github.com/andrewtoth)) +- Better interrupt message ([#1874](https://github.com/ordinals/ord/pull/1874) by [neunenak](https://github.com/neunenak)) +- Fix colored coins link in BIP ([#1856](https://github.com/ordinals/ord/pull/1856) by [gmart7t2](https://github.com/gmart7t2)) +- Added cozy pair programming twitch link to README.md ([#1827](https://github.com/ordinals/ord/pull/1827) by [cbspears](https://github.com/cbspears)) +- Create rpc client after updating index ([#1731](https://github.com/ordinals/ord/pull/1731) by [andrewtoth](https://github.com/andrewtoth)) +- Add additional err msg to build from source for users who's arch falls outside of the list ([#1792](https://github.com/ordinals/ord/pull/1792) by [bnonni](https://github.com/bnonni)) +- Add note on default build location ([#1625](https://github.com/ordinals/ord/pull/1625) by [whoabuddy](https://github.com/whoabuddy)) +- Minor copy fixes ([#1730](https://github.com/ordinals/ord/pull/1730) by [kn0wmad](https://github.com/kn0wmad)) +- Typo ([#1815](https://github.com/ordinals/ord/pull/1815) by [toddynho](https://github.com/toddynho)) [0.5.1](https://github.com/ordinals/ord/releases/tag/0.5.1) - 2023-02-21 --------------------------------------------------------------------- ### Performance -- Batch tx requests and re-enable skipping transactions (#1759) +- Batch tx requests and re-enable skipping transactions ([#1759](https://github.com/ordinals/ord/pull/1759) by [andrewtoth](https://github.com/andrewtoth)) ### Added -- Add option to set inscription destination address (#1536) -- Allow supplying passphrase for `ord wallet create` and `ord wallet restore` (#1669) -- Add `--config-dir` option (#1697) +- Add option to set inscription destination address ([#1536](https://github.com/ordinals/ord/pull/1536) by [rot13maxi](https://github.com/rot13maxi)) +- Allow supplying passphrase for `ord wallet create` and `ord wallet restore` ([#1669](https://github.com/ordinals/ord/pull/1669) by [Psifour](https://github.com/Psifour)) +- Add `--config-dir` option ([#1697](https://github.com/ordinals/ord/pull/1697) by [terror](https://github.com/terror)) ### Changed -- Require users manually specify a `--fee-rate` for `wallet send` (#1755) +- Require users manually specify a `--fee-rate` for `wallet send` ([#1755](https://github.com/ordinals/ord/pull/1755) by [veryordinally](https://github.com/veryordinally)) ### Documentation -- Add Sparrow Wallet Guide to Handbook (#1742) +- Add Sparrow Wallet Guide to Handbook ([#1742](https://github.com/ordinals/ord/pull/1742) by [windsok](https://github.com/windsok)) ### Misc -- Handle block count RPC error gracefully (#1637) -- Fix typos in overview.md (#1715) -- Typo fix (#1682) -- README typo fix (#1716) -- Fix changelog dates: 2022 → 2023 (#1700) -- Bump version number (#1695) +- Handle block count RPC error gracefully ([#1637](https://github.com/ordinals/ord/pull/1637) by [andrewtoth](https://github.com/andrewtoth)) +- Fix typos in overview.md ([#1715](https://github.com/ordinals/ord/pull/1715) by [mjethani](https://github.com/mjethani)) +- Typo fix ([#1682](https://github.com/ordinals/ord/pull/1682) by [sbddesign](https://github.com/sbddesign)) +- README typo fix ([#1716](https://github.com/ordinals/ord/pull/1716) by [terror](https://github.com/terror)) +- Fix changelog dates: 2022 → 2023 ([#1700](https://github.com/ordinals/ord/pull/1700) by [casey](https://github.com/casey)) +- Bump version number ([#1695](https://github.com/ordinals/ord/pull/1695) by [casey](https://github.com/casey)) [0.5.0](https://github.com/ordinals/ord/releases/tag/0.5.0) - 2023-02-11 --------------------------------------------------------------------- ### Breaking Changes -- Upgrade to redb 0.13.0 (#1513) -- Update redb to 0.12.1 (#1329) -- Display inscription genesis fee (#1381) +- Upgrade to redb 0.13.0 ([#1513](https://github.com/ordinals/ord/pull/1513) by [hantuzun](https://github.com/hantuzun)) +- Update redb to 0.12.1 ([#1329](https://github.com/ordinals/ord/pull/1329) by [casey](https://github.com/casey)) +- Display inscription genesis fee ([#1381](https://github.com/ordinals/ord/pull/1381) by [raphjaph](https://github.com/raphjaph)) ### Added -- Add support for `.glb` inscriptions (#1689) -- Add --no-limit flag to bypass MAX_STANDARD_TX_WEIGHT check to allow four meggers (#1571) -- Add `--commit-fee-rate` for setting inscribe commit transaction fee rate (#1490) -- Allow viewing but not creating AVIF inscriptions (#1428) -- Support STL inscriptions (#1492) -- Support MP4 inscriptions (#1419) -- Preview JSON and YAML inscriptions as text (#1449) -- Display inputs on /tx (#1433) -- Support PGP signature inscriptions (#1413) -- Add config (#1392) -- Add paging to /inscriptions (#1279) +- Add support for `.glb` inscriptions ([#1689](https://github.com/ordinals/ord/pull/1689) by [casey](https://github.com/casey)) +- Add --no-limit flag to bypass MAX_STANDARD_TX_WEIGHT check to allow four meggers ([#1571](https://github.com/ordinals/ord/pull/1571) by [raphjaph](https://github.com/raphjaph)) +- Add `--commit-fee-rate` for setting inscribe commit transaction fee rate ([#1490](https://github.com/ordinals/ord/pull/1490) by [andrewtoth](https://github.com/andrewtoth)) +- Allow viewing but not creating AVIF inscriptions ([#1428](https://github.com/ordinals/ord/pull/1428) by [hashbender](https://github.com/hashbender)) +- Support STL inscriptions ([#1492](https://github.com/ordinals/ord/pull/1492) by [casey](https://github.com/casey)) +- Support MP4 inscriptions ([#1419](https://github.com/ordinals/ord/pull/1419) by [cryptoquick](https://github.com/cryptoquick)) +- Preview JSON and YAML inscriptions as text ([#1449](https://github.com/ordinals/ord/pull/1449) by [casey](https://github.com/casey)) +- Display inputs on /tx ([#1433](https://github.com/ordinals/ord/pull/1433) by [casey](https://github.com/casey)) +- Support PGP signature inscriptions ([#1413](https://github.com/ordinals/ord/pull/1413) by [casey](https://github.com/casey)) +- Add config ([#1392](https://github.com/ordinals/ord/pull/1392) by [casey](https://github.com/casey)) +- Add paging to /inscriptions ([#1279](https://github.com/ordinals/ord/pull/1279) by [casey](https://github.com/casey)) ### Changed -- Increase deployment mempool size to 1024 megabytes (#1587) -- Increase number of inscriptions in RSS feed (#1567) -- Link to block from /inscription (#1395) -- Use favicon as icon for Twitter preview (#1425) -- Allow data URIs in content security policy (#1422) -- Raise server open file limit (#1408) -- Remove HTTP to HTTPS redirect (#1414) -- Use JSON for more command output (#1367) -- Use JSON for `wallet` command output (#1359) +- Increase deployment mempool size to 1024 megabytes ([#1587](https://github.com/ordinals/ord/pull/1587) by [casey](https://github.com/casey)) +- Increase number of inscriptions in RSS feed ([#1567](https://github.com/ordinals/ord/pull/1567) by [raphjaph](https://github.com/raphjaph)) +- Link to block from /inscription ([#1395](https://github.com/ordinals/ord/pull/1395) by [casey](https://github.com/casey)) +- Use favicon as icon for Twitter preview ([#1425](https://github.com/ordinals/ord/pull/1425) by [raphjaph](https://github.com/raphjaph)) +- Allow data URIs in content security policy ([#1422](https://github.com/ordinals/ord/pull/1422) by [casey](https://github.com/casey)) +- Raise server open file limit ([#1408](https://github.com/ordinals/ord/pull/1408) by [casey](https://github.com/casey)) +- Remove HTTP to HTTPS redirect ([#1414](https://github.com/ordinals/ord/pull/1414) by [casey](https://github.com/casey)) +- Use JSON for more command output ([#1367](https://github.com/ordinals/ord/pull/1367) by [raphjaph](https://github.com/raphjaph)) +- Use JSON for `wallet` command output ([#1359](https://github.com/ordinals/ord/pull/1359) by [raphjaph](https://github.com/raphjaph)) ### Misc -- Set rustc version in Cargo.toml & README (#1615) -- Disable Prettier format-on-save (#1593) -- Add build instructions to README (#1573) -- Ensure wallet commands load wallet (#1524) -- Improve error messages related to cookie file (#1537) -- Include inscription ID in text inscription decode error (#1540) -- Lazily load iframes (#1456) +- Set rustc version in Cargo.toml & README ([#1615](https://github.com/ordinals/ord/pull/1615) by [apbendi](https://github.com/apbendi)) +- Disable Prettier format-on-save ([#1593](https://github.com/ordinals/ord/pull/1593) by [whoabuddy](https://github.com/whoabuddy)) +- Add build instructions to README ([#1573](https://github.com/ordinals/ord/pull/1573) by [dplusplus1024](https://github.com/dplusplus1024)) +- Ensure wallet commands load wallet ([#1524](https://github.com/ordinals/ord/pull/1524) by [raphjaph](https://github.com/raphjaph)) +- Improve error messages related to cookie file ([#1537](https://github.com/ordinals/ord/pull/1537) by [casey](https://github.com/casey)) +- Include inscription ID in text inscription decode error ([#1540](https://github.com/ordinals/ord/pull/1540) by [casey](https://github.com/casey)) +- Lazily load iframes ([#1456](https://github.com/ordinals/ord/pull/1456) by [casey](https://github.com/casey)) - Log recoverable errors as warnings -- Add alert pop-up example (#1498) -- Use custom Discord invite link in handbox (#1506) -- Note that bounty 3 requires sat index (#1509) -- Link donation addresses to mempool.space (#1510) -- Add linebreak to donate page (#1500) -- Add donate page to handbook (#1499) -- Moderation guide typo: wiht → with (#1483) -- Add moderation guide (#1473) -- Add collecting guide to docs (#1474) -- Add missing dependencies to shell.nix (#1463) -- Mute and autoplay video inscriptions (#1420) -- Throw an error Bitcoin Core wallet and ord index are out of sync (#1459) -- Typo: managment -> management (#1441) -- Fix README.md grammar (#1406) -- Typo: Aritacts -> Artifacts (#1434) -- Update justfile to use unproxied domains (#1391) -- Typo: sat -> sats (#1411) -- Docs: `ord wallet utxos` -> `ord wallet outputs` (#1405) -- Round expected sat timestamps (#1386) -- Remove ellipsis (#1376) -- Hide overflowing ordered lists (#1384) -- Compress responses (#1366) -- Avoid listening on 0.0.0.0 in tests (#1365) -- Rename `GitHub` nav link to `Wallet` (#1360) +- Add alert pop-up example ([#1498](https://github.com/ordinals/ord/pull/1498) by [casey](https://github.com/casey)) +- Use custom Discord invite link in handbox ([#1506](https://github.com/ordinals/ord/pull/1506) by [casey](https://github.com/casey)) +- Note that bounty 3 requires sat index ([#1509](https://github.com/ordinals/ord/pull/1509) by [casey](https://github.com/casey)) +- Link donation addresses to mempool.space ([#1510](https://github.com/ordinals/ord/pull/1510) by [casey](https://github.com/casey)) +- Add linebreak to donate page ([#1500](https://github.com/ordinals/ord/pull/1500) by [casey](https://github.com/casey)) +- Add donate page to handbook ([#1499](https://github.com/ordinals/ord/pull/1499) by [casey](https://github.com/casey)) +- Moderation guide typo: wiht → with ([#1483](https://github.com/ordinals/ord/pull/1483) by [casey](https://github.com/casey)) +- Add moderation guide ([#1473](https://github.com/ordinals/ord/pull/1473) by [casey](https://github.com/casey)) +- Add collecting guide to docs ([#1474](https://github.com/ordinals/ord/pull/1474) by [casey](https://github.com/casey)) +- Add missing dependencies to shell.nix ([#1463](https://github.com/ordinals/ord/pull/1463) by [niftynei](https://github.com/niftynei)) +- Mute and autoplay video inscriptions ([#1420](https://github.com/ordinals/ord/pull/1420) by [cryptoquick](https://github.com/cryptoquick)) +- Throw an error Bitcoin Core wallet and ord index are out of sync ([#1459](https://github.com/ordinals/ord/pull/1459) by [raphjaph](https://github.com/raphjaph)) +- Typo: managment -> management ([#1441](https://github.com/ordinals/ord/pull/1441) by [jsahagun91](https://github.com/jsahagun91)) +- Fix README.md grammar ([#1406](https://github.com/ordinals/ord/pull/1406) by [rayonx](https://github.com/rayonx)) +- Typo: Aritacts -> Artifacts ([#1434](https://github.com/ordinals/ord/pull/1434) by [worm-emoji](https://github.com/worm-emoji)) +- Update justfile to use unproxied domains ([#1391](https://github.com/ordinals/ord/pull/1391) by [casey](https://github.com/casey)) +- Typo: sat -> sats ([#1411](https://github.com/ordinals/ord/pull/1411) by [goodwinmark](https://github.com/goodwinmark)) +- Docs: `ord wallet utxos` -> `ord wallet outputs` ([#1405](https://github.com/ordinals/ord/pull/1405) by [cryptoquick](https://github.com/cryptoquick)) +- Round expected sat timestamps ([#1386](https://github.com/ordinals/ord/pull/1386) by [casey](https://github.com/casey)) +- Remove ellipsis ([#1376](https://github.com/ordinals/ord/pull/1376) by [casey](https://github.com/casey)) +- Hide overflowing ordered lists ([#1384](https://github.com/ordinals/ord/pull/1384) by [casey](https://github.com/casey)) +- Compress responses ([#1366](https://github.com/ordinals/ord/pull/1366) by [casey](https://github.com/casey)) +- Avoid listening on 0.0.0.0 in tests ([#1365](https://github.com/ordinals/ord/pull/1365) by [raphjaph](https://github.com/raphjaph)) +- Rename `GitHub` nav link to `Wallet` ([#1360](https://github.com/ordinals/ord/pull/1360) by [casey](https://github.com/casey)) [0.4.2](https://github.com/ordinals/ord/releases/tag/0.4.2) - 2023-01-24 --------------------------------------------------------------------- @@ -660,652 +763,652 @@ Changelog - Fetch transactions below first inscription height ### Fixed -- Fix install script directory (#1356) +- Fix install script directory ([#1356](https://github.com/ordinals/ord/pull/1356) by [casey](https://github.com/casey)) ### Misc -- Fix guide typo: getblockchount -> getblockcount (#1354) +- Fix guide typo: getblockchount -> getblockcount ([#1354](https://github.com/ordinals/ord/pull/1354) by [casey](https://github.com/casey)) [0.4.1](https://github.com/ordinals/ord/releases/tag/0.4.1) - 2023-01-24 --------------------------------------------------------------------- ### Added -- Support video inscriptions (#1349) -- Support PDF Inscriptions (#1352) -- Display lost sats on /output (#1336) -- Show explorer URLs in `ord wallet inscriptions` (#1308) +- Support video inscriptions ([#1349](https://github.com/ordinals/ord/pull/1349) by [casey](https://github.com/casey)) +- Support PDF Inscriptions ([#1352](https://github.com/ordinals/ord/pull/1352) by [casey](https://github.com/casey)) +- Display lost sats on /output ([#1336](https://github.com/ordinals/ord/pull/1336) by [casey](https://github.com/casey)) +- Show explorer URLs in `ord wallet inscriptions` ([#1308](https://github.com/ordinals/ord/pull/1308) by [raphjaph](https://github.com/raphjaph)) ### Changed -- Display timestamps as UTC (#1348) -- Enable pointer events on inscription page iframes (#1344) -- Exclude inscribed utxos when calculating wallet balance (#1341) +- Display timestamps as UTC ([#1348](https://github.com/ordinals/ord/pull/1348) by [casey](https://github.com/casey)) +- Enable pointer events on inscription page iframes ([#1344](https://github.com/ordinals/ord/pull/1344) by [casey](https://github.com/casey)) +- Exclude inscribed utxos when calculating wallet balance ([#1341](https://github.com/ordinals/ord/pull/1341) by [raphjaph](https://github.com/raphjaph)) ### Misc -- Activate nav arrows on single tap on iOS Safari (#1347) -- Ignore keyboard events search box has focus (#1346) -- Cache content responses (#1318) -- Show unordered list decorations (#1353) -- Update inscriptions guide for mainnet (#1342) -- Hide list overflow and break dl overflow between words (#1343) -- Add white on black fish eye logo (#1325) -- Un-reverse thumbnail row order (#1321) -- Deploy branches other than master to mainnet (#1319) -- Add Just recipe to deploy to all chains (#1313) -- Display newest inscriptions on right (#1311) -- Allow publishing arbitrary revisions with publish recipe (#1307) -- Make genesis clock mark orange and add tooltip to height (#1312) -- Serve favicon as PNG to Safari and SVG others (#1302) -- Use sans-serif font for height on clock (#1300) +- Activate nav arrows on single tap on iOS Safari ([#1347](https://github.com/ordinals/ord/pull/1347) by [casey](https://github.com/casey)) +- Ignore keyboard events search box has focus ([#1346](https://github.com/ordinals/ord/pull/1346) by [casey](https://github.com/casey)) +- Cache content responses ([#1318](https://github.com/ordinals/ord/pull/1318) by [casey](https://github.com/casey)) +- Show unordered list decorations ([#1353](https://github.com/ordinals/ord/pull/1353) by [casey](https://github.com/casey)) +- Update inscriptions guide for mainnet ([#1342](https://github.com/ordinals/ord/pull/1342) by [casey](https://github.com/casey)) +- Hide list overflow and break dl overflow between words ([#1343](https://github.com/ordinals/ord/pull/1343) by [raphjaph](https://github.com/raphjaph)) +- Add white on black fish eye logo ([#1325](https://github.com/ordinals/ord/pull/1325) by [casey](https://github.com/casey)) +- Un-reverse thumbnail row order ([#1321](https://github.com/ordinals/ord/pull/1321) by [casey](https://github.com/casey)) +- Deploy branches other than master to mainnet ([#1319](https://github.com/ordinals/ord/pull/1319) by [casey](https://github.com/casey)) +- Add Just recipe to deploy to all chains ([#1313](https://github.com/ordinals/ord/pull/1313) by [casey](https://github.com/casey)) +- Display newest inscriptions on right ([#1311](https://github.com/ordinals/ord/pull/1311) by [casey](https://github.com/casey)) +- Allow publishing arbitrary revisions with publish recipe ([#1307](https://github.com/ordinals/ord/pull/1307) by [casey](https://github.com/casey)) +- Make genesis clock mark orange and add tooltip to height ([#1312](https://github.com/ordinals/ord/pull/1312) by [casey](https://github.com/casey)) +- Serve favicon as PNG to Safari and SVG others ([#1302](https://github.com/ordinals/ord/pull/1302) by [casey](https://github.com/casey)) +- Use sans-serif font for height on clock ([#1300](https://github.com/ordinals/ord/pull/1300) by [casey](https://github.com/casey)) [0.4.0](https://github.com/ordinals/ord/releases/tag/0.4.0) - 2023-01-19 --------------------------------------------------------------------- ### Added -- Support searching for inscription IDs (#1294) -- Add RSS feed (#1229) -- Add --dry-run-flag (#1265) -- Support recovering wallet from mnemonic (#1215) -- Audio inscriptions (#1241) -- Allow using custom fee rate (#1150) -- Show timestamp on /inscription (#1200) -- Add prev and next links to /inscription (#1193) -- Show address on /inscription (#1187) -- Add --limit to `ord wallet transaction` (#1049) -- Add `ord preview` (#1089) -- Add `ord wallet balance` (#1047) -- Support HTML and SVG inscriptions (#1035) -- Display genesis height on inscription page (#1026) -- Support more image types (#1020) -- Support GIFs (#1013) +- Support searching for inscription IDs ([#1294](https://github.com/ordinals/ord/pull/1294) by [raphjaph](https://github.com/raphjaph)) +- Add RSS feed ([#1229](https://github.com/ordinals/ord/pull/1229) by [casey](https://github.com/casey)) +- Add --dry-run-flag ([#1265](https://github.com/ordinals/ord/pull/1265) by [raphjaph](https://github.com/raphjaph)) +- Support recovering wallet from mnemonic ([#1215](https://github.com/ordinals/ord/pull/1215) by [casey](https://github.com/casey)) +- Audio inscriptions ([#1241](https://github.com/ordinals/ord/pull/1241) by [casey](https://github.com/casey)) +- Allow using custom fee rate ([#1150](https://github.com/ordinals/ord/pull/1150) by [raphjaph](https://github.com/raphjaph)) +- Show timestamp on /inscription ([#1200](https://github.com/ordinals/ord/pull/1200) by [casey](https://github.com/casey)) +- Add prev and next links to /inscription ([#1193](https://github.com/ordinals/ord/pull/1193) by [casey](https://github.com/casey)) +- Show address on /inscription ([#1187](https://github.com/ordinals/ord/pull/1187) by [casey](https://github.com/casey)) +- Add --limit to `ord wallet transaction` ([#1049](https://github.com/ordinals/ord/pull/1049) by [rot13maxi](https://github.com/rot13maxi)) +- Add `ord preview` ([#1089](https://github.com/ordinals/ord/pull/1089) by [casey](https://github.com/casey)) +- Add `ord wallet balance` ([#1047](https://github.com/ordinals/ord/pull/1047) by [rot13maxi](https://github.com/rot13maxi)) +- Support HTML and SVG inscriptions ([#1035](https://github.com/ordinals/ord/pull/1035) by [casey](https://github.com/casey)) +- Display genesis height on inscription page ([#1026](https://github.com/ordinals/ord/pull/1026) by [raphjaph](https://github.com/raphjaph)) +- Support more image types ([#1020](https://github.com/ordinals/ord/pull/1020) by [casey](https://github.com/casey)) +- Support GIFs ([#1013](https://github.com/ordinals/ord/pull/1013) by [raphjaph](https://github.com/raphjaph)) ### Changed -- Poll Bitcoin Core less frequently (#1268) -- Automatically load wallet (#1210) -- Ignore inscriptions on sat after first (#1214) -- Allow right-click to save image inscriptions (#1218) -- Scale text inscriptions to fit preview (#1222) -- Convert `ord wallet inscriptions` to JSON (#1224) -- Improve error when preview fails to launch bitcoind (#1243) -- Output inscription ID from `ord wallet inscribe` (#1208) -- Allow arbitrary wallet names (#1207) -- Use distinct inscription IDs (#1201) -- Remove ordinal addresses (#1197) -- Create taproot-only wallets (#1158) -- Check schema when opening index (#1127) -- Teach `ord wallet send` to send cardinal sats (#1137) -- Rename `ord wallet utxos` → `ord wallet outputs` (#1148) -- Swap arguments to ord wallet send (#1142) -- Rename --index-satoshis → --index-sats (#993) +- Poll Bitcoin Core less frequently ([#1268](https://github.com/ordinals/ord/pull/1268) by [raphjaph](https://github.com/raphjaph)) +- Automatically load wallet ([#1210](https://github.com/ordinals/ord/pull/1210) by [raphjaph](https://github.com/raphjaph)) +- Ignore inscriptions on sat after first ([#1214](https://github.com/ordinals/ord/pull/1214) by [casey](https://github.com/casey)) +- Allow right-click to save image inscriptions ([#1218](https://github.com/ordinals/ord/pull/1218) by [casey](https://github.com/casey)) +- Scale text inscriptions to fit preview ([#1222](https://github.com/ordinals/ord/pull/1222) by [casey](https://github.com/casey)) +- Convert `ord wallet inscriptions` to JSON ([#1224](https://github.com/ordinals/ord/pull/1224) by [casey](https://github.com/casey)) +- Improve error when preview fails to launch bitcoind ([#1243](https://github.com/ordinals/ord/pull/1243) by [casey](https://github.com/casey)) +- Output inscription ID from `ord wallet inscribe` ([#1208](https://github.com/ordinals/ord/pull/1208) by [casey](https://github.com/casey)) +- Allow arbitrary wallet names ([#1207](https://github.com/ordinals/ord/pull/1207) by [raphjaph](https://github.com/raphjaph)) +- Use distinct inscription IDs ([#1201](https://github.com/ordinals/ord/pull/1201) by [casey](https://github.com/casey)) +- Remove ordinal addresses ([#1197](https://github.com/ordinals/ord/pull/1197) by [casey](https://github.com/casey)) +- Create taproot-only wallets ([#1158](https://github.com/ordinals/ord/pull/1158) by [raphjaph](https://github.com/raphjaph)) +- Check schema when opening index ([#1127](https://github.com/ordinals/ord/pull/1127) by [casey](https://github.com/casey)) +- Teach `ord wallet send` to send cardinal sats ([#1137](https://github.com/ordinals/ord/pull/1137) by [casey](https://github.com/casey)) +- Rename `ord wallet utxos` → `ord wallet outputs` ([#1148](https://github.com/ordinals/ord/pull/1148) by [casey](https://github.com/casey)) +- Swap arguments to ord wallet send ([#1142](https://github.com/ordinals/ord/pull/1142) by [casey](https://github.com/casey)) +- Rename --index-satoshis → --index-sats ([#993](https://github.com/ordinals/ord/pull/993) by [casey](https://github.com/casey)) ### Fixed -- Fix preview for inscriptions with no body (#1287) -- Bail if reveal transaction is too large (#1272) -- Increase commit transaction output to pay for reveal transaction (#1242) -- Fix inscription thumbnail links (#1199) -- Use outpoint value table correctly and cache values in memory(#1172) -- Fix install script targets (#1120) +- Fix preview for inscriptions with no body ([#1287](https://github.com/ordinals/ord/pull/1287) by [casey](https://github.com/casey)) +- Bail if reveal transaction is too large ([#1272](https://github.com/ordinals/ord/pull/1272) by [casey](https://github.com/casey)) +- Increase commit transaction output to pay for reveal transaction ([#1242](https://github.com/ordinals/ord/pull/1242) by [casey](https://github.com/casey)) +- Fix inscription thumbnail links ([#1199](https://github.com/ordinals/ord/pull/1199) by [casey](https://github.com/casey)) +- Use outpoint value table correctly and cache values in memory([#1172](https://github.com/ordinals/ord/pull/1172) by [casey](https://github.com/casey)) +- Fix install script targets ([#1120](https://github.com/ordinals/ord/pull/1120) by [casey](https://github.com/casey)) ### Misc -- Use examples in core preview test (#1289) -- Use array for transaction builder change addresses (#1281) -- Fuzz test TransactionBuilder (#1283) -- Adopt Fish Eye logo (#1270) -- Split library and binary (#1273) -- Fix preview kill on drop (#1260) -- Add warning to readme (#1213) -- Run ignored tests in `ci` recipe (#1259) -- Add Bitcoin Core test job to CI (#1191) -- Add digital artifacts page to handbook (#1165) -- Use numbers in page titles (#1221) -- Set strict transport security header (#1216) -- Simplify BIP (#1226) -- Document required Bitcoin Core version for inscribing (#1225) -- Index lost sat ranges (#1227) -- Link to /block from /sat (#1228) -- Print index path in `ord info` (#1232) -- Add backlinks from /output and /transaction (#1235) -- Don't check lockfile on CI (#1209) -- Redirect HTTP to HTTPS (#1198) -- Test that inscriptions in additional envelopes and outputs are ignored (#1190) -- Use "sat" throughout codebase (#1184) -- Enable firewall on deployments (#1186) -- Request bech32m addresses in preview command (#1183) -- Use mainnet in tests (#1185) -- Move wallet tests into submodules (#1182) -- Link to /sat from /inscription (#1176) -- Match inscription preview and site background colors (#1175) -- Test that envelopes not starting with OP_FALSE are ignored (#1171) -- Update changelog (#1177) -- Remove mainnet wall restrictions (#1170) -- Ordinal addresses (#1163) -- Link outputs and inscriptions (#1162) -- Remove mainnet ord-dev index (#1164) -- Preview all inscriptions inside iframes (#1132) -- Remove inscriptions subcommand struct (#1151) -- Limit transaction count limit to u16::Max (#1152) -- Tweak homepage (#1124) -- Track fee-spent and lost inscriptions (#1125) -- Use InscriptionId in Reference (#1135) -- Avoid push_scriptint (#1136) -- Check Bitcoin Core version before inscribing (#1048) -- Display alpha in navbar on mainnet (#1122) -- Make PageHtml generic over PageContent type (#1123) -- Track inscriptions at offset and vout other than first (#1108) -- Unrecognized even fields are invalid (#1107) -- Add short flags (#1102) -- Document Debian dependencies (#1110) -- Add first testnet inscription height (#1109) -- Remove CORS headers (#1103) -- Don't wrap text inscriptions (#1100) -- Upgrade to redb 0.11.0 (#1099) -- Add quickstart script for macos (#1096) -- Remove text inscription anchor tag text decoration (#1084) -- Display inscription content on /inscriptions (#1077) -- Make inscription text white on inscription page (#1079) -- Move templates into root module (#1090) -- Show text inscriptions on homepage (#1058) -- Add white background to inscriptions (#1054) -- Show rare sat locations on /sat (#1029) -- Add first signet inscription height (#1016) -- Improve inscription style (#1025) -- Improve ord-dev recipes (#1022) -- Move inscription content above details (#1017) -- Style latest inscriptions (#1018) -- Print server URLs on startup (#1015) -- Add inscription page preview image (#1010) -- Show most recent inscriptions first on homepage and inscriptions page (#1011) -- Display graphical inscriptions on homepage (#1008) -- Add inscriptions page (#1009) -- Minimize transaction fetching (#1002) -- Rename `ord wallet satoshis` to `ord wallet sats` (#1004) -- Update introduction.md (#1000) -- Improve latest inscriptions style (#999) -- Show latest inscriptions on home page (#996) -- Add link to docs in readme (#995) -- Add inscription docs (#994) -- Fix softprops/actions-gh-release version (#992) -- Fuzz test transaction builder with multiple UTXOs (#1291) +- Use examples in core preview test ([#1289](https://github.com/ordinals/ord/pull/1289) by [raphjaph](https://github.com/raphjaph)) +- Use array for transaction builder change addresses ([#1281](https://github.com/ordinals/ord/pull/1281) by [casey](https://github.com/casey)) +- Fuzz test TransactionBuilder ([#1283](https://github.com/ordinals/ord/pull/1283) by [raphjaph](https://github.com/raphjaph)) +- Adopt Fish Eye logo ([#1270](https://github.com/ordinals/ord/pull/1270) by [casey](https://github.com/casey)) +- Split library and binary ([#1273](https://github.com/ordinals/ord/pull/1273) by [casey](https://github.com/casey)) +- Fix preview kill on drop ([#1260](https://github.com/ordinals/ord/pull/1260) by [raphjaph](https://github.com/raphjaph)) +- Add warning to readme ([#1213](https://github.com/ordinals/ord/pull/1213) by [casey](https://github.com/casey)) +- Run ignored tests in `ci` recipe ([#1259](https://github.com/ordinals/ord/pull/1259) by [casey](https://github.com/casey)) +- Add Bitcoin Core test job to CI ([#1191](https://github.com/ordinals/ord/pull/1191) by [casey](https://github.com/casey)) +- Add digital artifacts page to handbook ([#1165](https://github.com/ordinals/ord/pull/1165) by [casey](https://github.com/casey)) +- Use numbers in page titles ([#1221](https://github.com/ordinals/ord/pull/1221) by [casey](https://github.com/casey)) +- Set strict transport security header ([#1216](https://github.com/ordinals/ord/pull/1216) by [casey](https://github.com/casey)) +- Simplify BIP ([#1226](https://github.com/ordinals/ord/pull/1226) by [casey](https://github.com/casey)) +- Document required Bitcoin Core version for inscribing ([#1225](https://github.com/ordinals/ord/pull/1225) by [casey](https://github.com/casey)) +- Index lost sat ranges ([#1227](https://github.com/ordinals/ord/pull/1227) by [casey](https://github.com/casey)) +- Link to /block from /sat ([#1228](https://github.com/ordinals/ord/pull/1228) by [casey](https://github.com/casey)) +- Print index path in `ord info` ([#1232](https://github.com/ordinals/ord/pull/1232) by [casey](https://github.com/casey)) +- Add backlinks from /output and /transaction ([#1235](https://github.com/ordinals/ord/pull/1235) by [casey](https://github.com/casey)) +- Don't check lockfile on CI ([#1209](https://github.com/ordinals/ord/pull/1209) by [casey](https://github.com/casey)) +- Redirect HTTP to HTTPS ([#1198](https://github.com/ordinals/ord/pull/1198) by [casey](https://github.com/casey)) +- Test that inscriptions in additional envelopes and outputs are ignored ([#1190](https://github.com/ordinals/ord/pull/1190) by [casey](https://github.com/casey)) +- Use "sat" throughout codebase ([#1184](https://github.com/ordinals/ord/pull/1184) by [casey](https://github.com/casey)) +- Enable firewall on deployments ([#1186](https://github.com/ordinals/ord/pull/1186) by [casey](https://github.com/casey)) +- Request bech32m addresses in preview command ([#1183](https://github.com/ordinals/ord/pull/1183) by [casey](https://github.com/casey)) +- Use mainnet in tests ([#1185](https://github.com/ordinals/ord/pull/1185) by [casey](https://github.com/casey)) +- Move wallet tests into submodules ([#1182](https://github.com/ordinals/ord/pull/1182) by [casey](https://github.com/casey)) +- Link to /sat from /inscription ([#1176](https://github.com/ordinals/ord/pull/1176) by [casey](https://github.com/casey)) +- Match inscription preview and site background colors ([#1175](https://github.com/ordinals/ord/pull/1175) by [casey](https://github.com/casey)) +- Test that envelopes not starting with OP_FALSE are ignored ([#1171](https://github.com/ordinals/ord/pull/1171) by [casey](https://github.com/casey)) +- Update changelog ([#1177](https://github.com/ordinals/ord/pull/1177) by [casey](https://github.com/casey)) +- Remove mainnet wall restrictions ([#1170](https://github.com/ordinals/ord/pull/1170) by [casey](https://github.com/casey)) +- Ordinal addresses ([#1163](https://github.com/ordinals/ord/pull/1163) by [casey](https://github.com/casey)) +- Link outputs and inscriptions ([#1162](https://github.com/ordinals/ord/pull/1162) by [casey](https://github.com/casey)) +- Remove mainnet ord-dev index ([#1164](https://github.com/ordinals/ord/pull/1164) by [casey](https://github.com/casey)) +- Preview all inscriptions inside iframes ([#1132](https://github.com/ordinals/ord/pull/1132) by [casey](https://github.com/casey)) +- Remove inscriptions subcommand struct ([#1151](https://github.com/ordinals/ord/pull/1151) by [casey](https://github.com/casey)) +- Limit transaction count limit to u16::Max ([#1152](https://github.com/ordinals/ord/pull/1152) by [casey](https://github.com/casey)) +- Tweak homepage ([#1124](https://github.com/ordinals/ord/pull/1124) by [casey](https://github.com/casey)) +- Track fee-spent and lost inscriptions ([#1125](https://github.com/ordinals/ord/pull/1125) by [casey](https://github.com/casey)) +- Use InscriptionId in Reference ([#1135](https://github.com/ordinals/ord/pull/1135) by [casey](https://github.com/casey)) +- Avoid push_scriptint ([#1136](https://github.com/ordinals/ord/pull/1136) by [casey](https://github.com/casey)) +- Check Bitcoin Core version before inscribing ([#1048](https://github.com/ordinals/ord/pull/1048) by [rot13maxi](https://github.com/rot13maxi)) +- Display alpha in navbar on mainnet ([#1122](https://github.com/ordinals/ord/pull/1122) by [casey](https://github.com/casey)) +- Make PageHtml generic over PageContent type ([#1123](https://github.com/ordinals/ord/pull/1123) by [casey](https://github.com/casey)) +- Track inscriptions at offset and vout other than first ([#1108](https://github.com/ordinals/ord/pull/1108) by [casey](https://github.com/casey)) +- Unrecognized even fields are invalid ([#1107](https://github.com/ordinals/ord/pull/1107) by [casey](https://github.com/casey)) +- Add short flags ([#1102](https://github.com/ordinals/ord/pull/1102) by [casey](https://github.com/casey)) +- Document Debian dependencies ([#1110](https://github.com/ordinals/ord/pull/1110) by [casey](https://github.com/casey)) +- Add first testnet inscription height ([#1109](https://github.com/ordinals/ord/pull/1109) by [casey](https://github.com/casey)) +- Remove CORS headers ([#1103](https://github.com/ordinals/ord/pull/1103) by [casey](https://github.com/casey)) +- Don't wrap text inscriptions ([#1100](https://github.com/ordinals/ord/pull/1100) by [casey](https://github.com/casey)) +- Upgrade to redb 0.11.0 ([#1099](https://github.com/ordinals/ord/pull/1099) by [cberner](https://github.com/cberner)) +- Add quickstart script for macos ([#1096](https://github.com/ordinals/ord/pull/1096) by [casey](https://github.com/casey)) +- Remove text inscription anchor tag text decoration ([#1084](https://github.com/ordinals/ord/pull/1084) by [casey](https://github.com/casey)) +- Display inscription content on /inscriptions ([#1077](https://github.com/ordinals/ord/pull/1077) by [casey](https://github.com/casey)) +- Make inscription text white on inscription page ([#1079](https://github.com/ordinals/ord/pull/1079) by [casey](https://github.com/casey)) +- Move templates into root module ([#1090](https://github.com/ordinals/ord/pull/1090) by [casey](https://github.com/casey)) +- Show text inscriptions on homepage ([#1058](https://github.com/ordinals/ord/pull/1058) by [casey](https://github.com/casey)) +- Add white background to inscriptions ([#1054](https://github.com/ordinals/ord/pull/1054) by [casey](https://github.com/casey)) +- Show rare sat locations on /sat ([#1029](https://github.com/ordinals/ord/pull/1029) by [casey](https://github.com/casey)) +- Add first signet inscription height ([#1016](https://github.com/ordinals/ord/pull/1016) by [casey](https://github.com/casey)) +- Improve inscription style ([#1025](https://github.com/ordinals/ord/pull/1025) by [casey](https://github.com/casey)) +- Improve ord-dev recipes ([#1022](https://github.com/ordinals/ord/pull/1022) by [casey](https://github.com/casey)) +- Move inscription content above details ([#1017](https://github.com/ordinals/ord/pull/1017) by [casey](https://github.com/casey)) +- Style latest inscriptions ([#1018](https://github.com/ordinals/ord/pull/1018) by [casey](https://github.com/casey)) +- Print server URLs on startup ([#1015](https://github.com/ordinals/ord/pull/1015) by [casey](https://github.com/casey)) +- Add inscription page preview image ([#1010](https://github.com/ordinals/ord/pull/1010) by [raphjaph](https://github.com/raphjaph)) +- Show most recent inscriptions first on homepage and inscriptions page ([#1011](https://github.com/ordinals/ord/pull/1011) by [casey](https://github.com/casey)) +- Display graphical inscriptions on homepage ([#1008](https://github.com/ordinals/ord/pull/1008) by [casey](https://github.com/casey)) +- Add inscriptions page ([#1009](https://github.com/ordinals/ord/pull/1009) by [raphjaph](https://github.com/raphjaph)) +- Minimize transaction fetching ([#1002](https://github.com/ordinals/ord/pull/1002) by [casey](https://github.com/casey)) +- Rename `ord wallet satoshis` to `ord wallet sats` ([#1004](https://github.com/ordinals/ord/pull/1004) by [casey](https://github.com/casey)) +- Update introduction.md ([#1000](https://github.com/ordinals/ord/pull/1000) by [batcavekid](https://github.com/batcavekid)) +- Improve latest inscriptions style ([#999](https://github.com/ordinals/ord/pull/999) by [raphjaph](https://github.com/raphjaph)) +- Show latest inscriptions on home page ([#996](https://github.com/ordinals/ord/pull/996) by [raphjaph](https://github.com/raphjaph)) +- Add link to docs in readme ([#995](https://github.com/ordinals/ord/pull/995) by [casey](https://github.com/casey)) +- Add inscription docs ([#994](https://github.com/ordinals/ord/pull/994) by [casey](https://github.com/casey)) +- Fix softprops/actions-gh-release version ([#992](https://github.com/ordinals/ord/pull/992) by [casey](https://github.com/casey)) +- Fuzz test transaction builder with multiple UTXOs ([#1291](https://github.com/ordinals/ord/pull/1291) by [casey](https://github.com/casey)) [0.3.0](https://github.com/ordinals/ord/releases/tag/0.3.0) - 2022-12-16 --------------------------------------------------------------------- -- Update CI dependencies (#986) -- Add /content endpoint (#976) -- Display content type and size /inscription (#975) -- Use "sat" in place of "ordinal" (#979) +- Update CI dependencies ([#986](https://github.com/ordinals/ord/pull/986) by [casey](https://github.com/casey)) +- Add /content endpoint ([#976](https://github.com/ordinals/ord/pull/976) by [casey](https://github.com/casey)) +- Display content type and size /inscription ([#975](https://github.com/ordinals/ord/pull/975) by [casey](https://github.com/casey)) +- Use "sat" in place of "ordinal" ([#979](https://github.com/ordinals/ord/pull/979) by [casey](https://github.com/casey)) [0.2.1](https://github.com/ordinals/ord/releases/tag/0.2.1) - 2022-12-14 --------------------------------------------------------------------- -- Revise inscription guide after mainnet walkthrough (#968) +- Revise inscription guide after mainnet walkthrough ([#968](https://github.com/ordinals/ord/pull/968) by [casey](https://github.com/casey)) [0.2.0](https://github.com/ordinals/ord/releases/tag/0.2.0) - 2022-12-14 --------------------------------------------------------------------- -- Add `ord wallet create` (#958) -- Add chain flags (#961) -- Make inscription parser more lenient (#956) -- Add `ord wallet transactions` (#951) -- Update dependencies (#955) -- Show inscription on reveal transaction page (#954) -- Mention that wallet may not need to be loaded (#953) -- Document install script (#952) -- Revise homepage (#950) -- Add content to guide page (#945) -- Mention incentive to run full node in FAQ (#948) -- Expand FAQ (#944) -- Change --index-ordinals to --index-satoshis (#942) -- Improve wording (#938) -- Add help text to subcommands (#934) -- Merge CI jobs into one workflow (#935) -- Add install script (#940) -- Build MacOS ARM Binaries (#941) -- Add inscription guide (#912) -- Allow inscribing without specifying a satpoint (#919) -- Add `ord wallet inscriptions` (#917) -- Add `ord wallet utxos` (#911) -- Add `ord wallet recieve` (#909) -- Fix signet block explorer link (#908) -- Opt wallet transactions into RBF (#901) -- Avoid `as` conversions (#903) -- Save commit transaction recovery key (#885) -- Refuse to send inscriptions by satpoint (#898) -- Limit inscription content to 1024 bytes on signet and testnet (#896) -- Extend bounty 3 (#897) -- Make inscription type more flexible (#892) -- Update dependencies (#894) -- Refuse to inscribe UTXOs with additional inscriptions (#880) -- Make inscriptions support backwards-compatible extension (#888) -- Refuse to send additional inscriptions (#881) -- Enable Windows tests on CI (#846) -- Refuse to inscribe sats that have already been inscribe (#878) -- Send by inscription ID (#877) -- Test commands which return errors when not tracking rare ordinals (#875) -- Don't store serialized inscriptions (#872) -- Do not select inscribed sats as cardinal utxos (#835) -- Make ord info work without ordinal index (#874) -- Improve subcommand names (#867) -- Calculate TXIDs in background thread (#866) -- Track inscription satpoints (#860) -- Add type aliases index for array types (#859) -- Index inscriptions when not indexing ordinals (#857) -- Use satpoints instead of ordinals in wallet commands (#849) -- Only request transactions if indexing ordinals (#851) -- Make analyzing index easier (#850) -- Add `ord list-ranges ` (#848) -- Conditionally disable ordinal index dependent server features (#845) -- Update redb (#832) -- Compress downloaded logs (#836) -- Only index ordinal ranges if `--index-ordinals` is passed (#837) -- Record commit block count and timestamp in index (#826) -- Add build-snapshots recipe (#831) -- Add minimum system requirements to readme (#829) -- Abort update if another has run concurrently (#830) -- Add benchmark-revision recipe (#827) -- Retry get_block_hash as well as get_block (#820) -- Update dependencies (#823) -- Add inscription page (#817) -- Add PNG inscriptions (#800) -- Disable inscriptions on mainnet (#814) -- Add benchmark recipe (#810) -- Display chain in header if not on mainnet (#809) -- Show difficulty target on block page (#750) -- Deduct fee before calculating reveal transaction signature (#780) -- Remove redundant wallet balance check (#764) -- Add `ord wallet inscribe` command (#658) -- Remove outdated runes and inscriptions (#760) -- Prevent progress bar from flickering when synced (#759) -- Fix graph command to work with new format (#755) -- Track ordinal ranges (#756) -- Use HTTP connection reusing `rust-jsonrpc` (#754) -- Extend bounty 3 by one difficulty adjustment period (#753) -- Replace binary search in epoch construction (#723) -- Search for ordinals in TSV using `ord wallet identify` (#729) -- Don't create acme cache dir (#727) -- Split up ci into test and lint workflows (#728) -- Enable CI for Windows (#603) -- Add bounty 3 (#725) -- Fetch blocks in background (#495) -- Don't call `apt-get update` in CI workflow (#719) -- Remove old recipes from justfile (#718) -- Update roadmap (#722) +- Add `ord wallet create` ([#958](https://github.com/ordinals/ord/pull/958) by [casey](https://github.com/casey)) +- Add chain flags ([#961](https://github.com/ordinals/ord/pull/961) by [casey](https://github.com/casey)) +- Make inscription parser more lenient ([#956](https://github.com/ordinals/ord/pull/956) by [casey](https://github.com/casey)) +- Add `ord wallet transactions` ([#951](https://github.com/ordinals/ord/pull/951) by [raphjaph](https://github.com/raphjaph)) +- Update dependencies ([#955](https://github.com/ordinals/ord/pull/955) by [casey](https://github.com/casey)) +- Show inscription on reveal transaction page ([#954](https://github.com/ordinals/ord/pull/954) by [casey](https://github.com/casey)) +- Mention that wallet may not need to be loaded ([#953](https://github.com/ordinals/ord/pull/953) by [casey](https://github.com/casey)) +- Document install script ([#952](https://github.com/ordinals/ord/pull/952) by [casey](https://github.com/casey)) +- Revise homepage ([#950](https://github.com/ordinals/ord/pull/950) by [casey](https://github.com/casey)) +- Add content to guide page ([#945](https://github.com/ordinals/ord/pull/945) by [casey](https://github.com/casey)) +- Mention incentive to run full node in FAQ ([#948](https://github.com/ordinals/ord/pull/948) by [casey](https://github.com/casey)) +- Expand FAQ ([#944](https://github.com/ordinals/ord/pull/944) by [casey](https://github.com/casey)) +- Change --index-ordinals to --index-satoshis ([#942](https://github.com/ordinals/ord/pull/942) by [casey](https://github.com/casey)) +- Improve wording ([#938](https://github.com/ordinals/ord/pull/938) by [casey](https://github.com/casey)) +- Add help text to subcommands ([#934](https://github.com/ordinals/ord/pull/934) by [casey](https://github.com/casey)) +- Merge CI jobs into one workflow ([#935](https://github.com/ordinals/ord/pull/935) by [casey](https://github.com/casey)) +- Add install script ([#940](https://github.com/ordinals/ord/pull/940) by [casey](https://github.com/casey)) +- Build MacOS ARM Binaries ([#941](https://github.com/ordinals/ord/pull/941) by [casey](https://github.com/casey)) +- Add inscription guide ([#912](https://github.com/ordinals/ord/pull/912) by [casey](https://github.com/casey)) +- Allow inscribing without specifying a satpoint ([#919](https://github.com/ordinals/ord/pull/919) by [raphjaph](https://github.com/raphjaph)) +- Add `ord wallet inscriptions` ([#917](https://github.com/ordinals/ord/pull/917) by [raphjaph](https://github.com/raphjaph)) +- Add `ord wallet utxos` ([#911](https://github.com/ordinals/ord/pull/911) by [raphjaph](https://github.com/raphjaph)) +- Add `ord wallet recieve` ([#909](https://github.com/ordinals/ord/pull/909) by [raphjaph](https://github.com/raphjaph)) +- Fix signet block explorer link ([#908](https://github.com/ordinals/ord/pull/908) by [casey](https://github.com/casey)) +- Opt wallet transactions into RBF ([#901](https://github.com/ordinals/ord/pull/901) by [casey](https://github.com/casey)) +- Avoid `as` conversions ([#903](https://github.com/ordinals/ord/pull/903) by [casey](https://github.com/casey)) +- Save commit transaction recovery key ([#885](https://github.com/ordinals/ord/pull/885) by [raphjaph](https://github.com/raphjaph)) +- Refuse to send inscriptions by satpoint ([#898](https://github.com/ordinals/ord/pull/898) by [casey](https://github.com/casey)) +- Limit inscription content to 1024 bytes on signet and testnet ([#896](https://github.com/ordinals/ord/pull/896) by [casey](https://github.com/casey)) +- Extend bounty 3 ([#897](https://github.com/ordinals/ord/pull/897) by [casey](https://github.com/casey)) +- Make inscription type more flexible ([#892](https://github.com/ordinals/ord/pull/892) by [casey](https://github.com/casey)) +- Update dependencies ([#894](https://github.com/ordinals/ord/pull/894) by [casey](https://github.com/casey)) +- Refuse to inscribe UTXOs with additional inscriptions ([#880](https://github.com/ordinals/ord/pull/880) by [raphjaph](https://github.com/raphjaph)) +- Make inscriptions support backwards-compatible extension ([#888](https://github.com/ordinals/ord/pull/888) by [casey](https://github.com/casey)) +- Refuse to send additional inscriptions ([#881](https://github.com/ordinals/ord/pull/881) by [raphjaph](https://github.com/raphjaph)) +- Enable Windows tests on CI ([#846](https://github.com/ordinals/ord/pull/846) by [casey](https://github.com/casey)) +- Refuse to inscribe sats that have already been inscribe ([#878](https://github.com/ordinals/ord/pull/878) by [raphjaph](https://github.com/raphjaph)) +- Send by inscription ID ([#877](https://github.com/ordinals/ord/pull/877) by [casey](https://github.com/casey)) +- Test commands which return errors when not tracking rare ordinals ([#875](https://github.com/ordinals/ord/pull/875) by [casey](https://github.com/casey)) +- Don't store serialized inscriptions ([#872](https://github.com/ordinals/ord/pull/872) by [casey](https://github.com/casey)) +- Do not select inscribed sats as cardinal utxos ([#835](https://github.com/ordinals/ord/pull/835) by [raphjaph](https://github.com/raphjaph)) +- Make ord info work without ordinal index ([#874](https://github.com/ordinals/ord/pull/874) by [casey](https://github.com/casey)) +- Improve subcommand names ([#867](https://github.com/ordinals/ord/pull/867) by [casey](https://github.com/casey)) +- Calculate TXIDs in background thread ([#866](https://github.com/ordinals/ord/pull/866) by [casey](https://github.com/casey)) +- Track inscription satpoints ([#860](https://github.com/ordinals/ord/pull/860) by [raphjaph](https://github.com/raphjaph)) +- Add type aliases index for array types ([#859](https://github.com/ordinals/ord/pull/859) by [casey](https://github.com/casey)) +- Index inscriptions when not indexing ordinals ([#857](https://github.com/ordinals/ord/pull/857) by [casey](https://github.com/casey)) +- Use satpoints instead of ordinals in wallet commands ([#849](https://github.com/ordinals/ord/pull/849) by [raphjaph](https://github.com/raphjaph)) +- Only request transactions if indexing ordinals ([#851](https://github.com/ordinals/ord/pull/851) by [casey](https://github.com/casey)) +- Make analyzing index easier ([#850](https://github.com/ordinals/ord/pull/850) by [casey](https://github.com/casey)) +- Add `ord list-ranges ` ([#848](https://github.com/ordinals/ord/pull/848) by [raphjaph](https://github.com/raphjaph)) +- Conditionally disable ordinal index dependent server features ([#845](https://github.com/ordinals/ord/pull/845) by [casey](https://github.com/casey)) +- Update redb ([#832](https://github.com/ordinals/ord/pull/832) by [casey](https://github.com/casey)) +- Compress downloaded logs ([#836](https://github.com/ordinals/ord/pull/836) by [casey](https://github.com/casey)) +- Only index ordinal ranges if `--index-ordinals` is passed ([#837](https://github.com/ordinals/ord/pull/837) by [casey](https://github.com/casey)) +- Record commit block count and timestamp in index ([#826](https://github.com/ordinals/ord/pull/826) by [casey](https://github.com/casey)) +- Add build-snapshots recipe ([#831](https://github.com/ordinals/ord/pull/831) by [casey](https://github.com/casey)) +- Add minimum system requirements to readme ([#829](https://github.com/ordinals/ord/pull/829) by [casey](https://github.com/casey)) +- Abort update if another has run concurrently ([#830](https://github.com/ordinals/ord/pull/830) by [casey](https://github.com/casey)) +- Add benchmark-revision recipe ([#827](https://github.com/ordinals/ord/pull/827) by [casey](https://github.com/casey)) +- Retry get_block_hash as well as get_block ([#820](https://github.com/ordinals/ord/pull/820) by [casey](https://github.com/casey)) +- Update dependencies ([#823](https://github.com/ordinals/ord/pull/823) by [casey](https://github.com/casey)) +- Add inscription page ([#817](https://github.com/ordinals/ord/pull/817) by [raphjaph](https://github.com/raphjaph)) +- Add PNG inscriptions ([#800](https://github.com/ordinals/ord/pull/800) by [raphjaph](https://github.com/raphjaph)) +- Disable inscriptions on mainnet ([#814](https://github.com/ordinals/ord/pull/814) by [casey](https://github.com/casey)) +- Add benchmark recipe ([#810](https://github.com/ordinals/ord/pull/810) by [casey](https://github.com/casey)) +- Display chain in header if not on mainnet ([#809](https://github.com/ordinals/ord/pull/809) by [casey](https://github.com/casey)) +- Show difficulty target on block page ([#750](https://github.com/ordinals/ord/pull/750) by [casey](https://github.com/casey)) +- Deduct fee before calculating reveal transaction signature ([#780](https://github.com/ordinals/ord/pull/780) by [casey](https://github.com/casey)) +- Remove redundant wallet balance check ([#764](https://github.com/ordinals/ord/pull/764) by [casey](https://github.com/casey)) +- Add `ord wallet inscribe` command ([#658](https://github.com/ordinals/ord/pull/658) by [casey](https://github.com/casey)) +- Remove outdated runes and inscriptions ([#760](https://github.com/ordinals/ord/pull/760) by [casey](https://github.com/casey)) +- Prevent progress bar from flickering when synced ([#759](https://github.com/ordinals/ord/pull/759) by [casey](https://github.com/casey)) +- Fix graph command to work with new format ([#755](https://github.com/ordinals/ord/pull/755) by [casey](https://github.com/casey)) +- Track ordinal ranges ([#756](https://github.com/ordinals/ord/pull/756) by [casey](https://github.com/casey)) +- Use HTTP connection reusing `rust-jsonrpc` ([#754](https://github.com/ordinals/ord/pull/754) by [casey](https://github.com/casey)) +- Extend bounty 3 by one difficulty adjustment period ([#753](https://github.com/ordinals/ord/pull/753) by [casey](https://github.com/casey)) +- Replace binary search in epoch construction ([#723](https://github.com/ordinals/ord/pull/723) by [veryordinally](https://github.com/veryordinally)) +- Search for ordinals in TSV using `ord wallet identify` ([#729](https://github.com/ordinals/ord/pull/729) by [raphjaph](https://github.com/raphjaph)) +- Don't create acme cache dir ([#727](https://github.com/ordinals/ord/pull/727) by [casey](https://github.com/casey)) +- Split up ci into test and lint workflows ([#728](https://github.com/ordinals/ord/pull/728) by [casey](https://github.com/casey)) +- Enable CI for Windows ([#603](https://github.com/ordinals/ord/pull/603) by [casey](https://github.com/casey)) +- Add bounty 3 ([#725](https://github.com/ordinals/ord/pull/725) by [casey](https://github.com/casey)) +- Fetch blocks in background ([#495](https://github.com/ordinals/ord/pull/495) by [casey](https://github.com/casey)) +- Don't call `apt-get update` in CI workflow ([#719](https://github.com/ordinals/ord/pull/719) by [casey](https://github.com/casey)) +- Remove old recipes from justfile ([#718](https://github.com/ordinals/ord/pull/718) by [casey](https://github.com/casey)) +- Update roadmap ([#722](https://github.com/ordinals/ord/pull/722) by [raphjaph](https://github.com/raphjaph)) [0.1.0](https://github.com/ordinals/ord/releases/tag/0.1.0) - 2022-10-25 --------------------------------------------------------------------- -- Add index updater (#703) -- Speed up rarity check while indexing (#702) +- Add index updater ([#703](https://github.com/ordinals/ord/pull/703) by [veryordinally](https://github.com/veryordinally)) +- Speed up rarity check while indexing ([#702](https://github.com/ordinals/ord/pull/702) by [veryordinally](https://github.com/veryordinally)) [0.0.6](https://github.com/ordinals/ord/releases/tag/0.0.6) - 2022-10-25 --------------------------------------------------------------------- -- Switch to ord-bitcoincore-rpc (#707) -- Start error messages with lowercase character (#693) -- Ensure addresses are valid for network (#698) -- Link videos from docs (#696) -- Restrict `ord wallet send` on mainnet (#687) -- Improve progress bar (#694) -- Note bounty 2 has been claimed (#700) -- Don't opt-in to RBF (#685) -- Don't unintentionally send rare ordinals (#683) -- Enforce transaction construction output address invariants (#682) -- Use worst-case fee estimates (#681) -- Add encoding to clock SVG (#678) -- Add helpers to make transaction builder tests more concise (#679) -- Don't use UTXOs with rare ordinals as cardinal inputs (#680) -- Improve not enough cardinal UTXOs error message (#675) -- Pad initial output to be above dust limit (#674) -- Start indexing progress bar at current height (#673) -- Add additional postage when necessary (#671) -- Check transaction fees in transaction builder (#669) -- Display progress bar when indexing (#668) -- Send ordinal first in recipient output (#666) -- Add doc-comment to transaction builder (#663) -- Change feerate to 1 sat/vbyte (#664) -- Strip excess postage from end of output (#662) -- Download logs to tempdir (#656) -- Improve transaction builder checks (#661) -- Use redb's two-phase write strategy in production (#660) -- Replace `Result<()>` with `Result` (#657) -- Add fee when sending (#655) -- Make table names more explicit (#654) -- Fix race condition in commit test (#651) -- Reform `ord wallet send` (#648) -- Use https://signet.ordinals.com as default signet publish URL (#649) -- Append network to data dir (#650) -- Only commit when necessary (#647) -- Make rarity text white (#644) -- Link to ordinal from rune (#643) -- Show inscriptions on /ordinal (#645) -- Document search (#646) -- Check that RPC server is on correct network (#642) -- Add /input page (#639) -- Expand search box to fill available space (#633) -- Add `ord rune publish` command (#637) -- Add links to docs (#635) -- Use docs for name of workflow and directory (#632) -- Remove multilingual book config key (#631) -- Add `ord wallet send` (#618) -- Streamline roadmap (#628) -- Improve styling (#626) -- Fix book publish directory (#625) -- Convert docs from Zola to mdBook (#623) -- Add nav bar (#614) -- Add status header to homepage (#620) -- Update roadmap (#617) -- Use reduced database durability during tests (#621) -- Add /rare.txt (#619) -- Embellish block page (#605) -- Refactor server error handling (#607) -- Profile tests (#608) -- Display ranges with an en dash (#606) -- Display more information homepage (#610) -- Remove prime trait (#612) -- Sort ordinal properties (#609) -- Add dark mode (#611) -- Add more help text to CLI (#613) -- Expand ordinal hunting guide (#600) -- Embellish transaction page (#602) -- Add `ord wallet list` command (#601) -- Ignore temporary directory (#594) -- Add ordinal hunting how-to docs page (#596) -- Fix bounty example links (#595) +- Switch to ord-bitcoincore-rpc ([#707](https://github.com/ordinals/ord/pull/707) by [casey](https://github.com/casey)) +- Start error messages with lowercase character ([#693](https://github.com/ordinals/ord/pull/693) by [raphjaph](https://github.com/raphjaph)) +- Ensure addresses are valid for network ([#698](https://github.com/ordinals/ord/pull/698) by [casey](https://github.com/casey)) +- Link videos from docs ([#696](https://github.com/ordinals/ord/pull/696) by [casey](https://github.com/casey)) +- Restrict `ord wallet send` on mainnet ([#687](https://github.com/ordinals/ord/pull/687) by [casey](https://github.com/casey)) +- Improve progress bar ([#694](https://github.com/ordinals/ord/pull/694) by [casey](https://github.com/casey)) +- Note bounty 2 has been claimed ([#700](https://github.com/ordinals/ord/pull/700) by [casey](https://github.com/casey)) +- Don't opt-in to RBF ([#685](https://github.com/ordinals/ord/pull/685) by [raphjaph](https://github.com/raphjaph)) +- Don't unintentionally send rare ordinals ([#683](https://github.com/ordinals/ord/pull/683) by [raphjaph](https://github.com/raphjaph)) +- Enforce transaction construction output address invariants ([#682](https://github.com/ordinals/ord/pull/682) by [casey](https://github.com/casey)) +- Use worst-case fee estimates ([#681](https://github.com/ordinals/ord/pull/681) by [casey](https://github.com/casey)) +- Add encoding to clock SVG ([#678](https://github.com/ordinals/ord/pull/678) by [casey](https://github.com/casey)) +- Add helpers to make transaction builder tests more concise ([#679](https://github.com/ordinals/ord/pull/679) by [casey](https://github.com/casey)) +- Don't use UTXOs with rare ordinals as cardinal inputs ([#680](https://github.com/ordinals/ord/pull/680) by [casey](https://github.com/casey)) +- Improve not enough cardinal UTXOs error message ([#675](https://github.com/ordinals/ord/pull/675) by [casey](https://github.com/casey)) +- Pad initial output to be above dust limit ([#674](https://github.com/ordinals/ord/pull/674) by [casey](https://github.com/casey)) +- Start indexing progress bar at current height ([#673](https://github.com/ordinals/ord/pull/673) by [casey](https://github.com/casey)) +- Add additional postage when necessary ([#671](https://github.com/ordinals/ord/pull/671) by [casey](https://github.com/casey)) +- Check transaction fees in transaction builder ([#669](https://github.com/ordinals/ord/pull/669) by [casey](https://github.com/casey)) +- Display progress bar when indexing ([#668](https://github.com/ordinals/ord/pull/668) by [casey](https://github.com/casey)) +- Send ordinal first in recipient output ([#666](https://github.com/ordinals/ord/pull/666) by [raphjaph](https://github.com/raphjaph)) +- Add doc-comment to transaction builder ([#663](https://github.com/ordinals/ord/pull/663) by [casey](https://github.com/casey)) +- Change feerate to 1 sat/vbyte ([#664](https://github.com/ordinals/ord/pull/664) by [raphjaph](https://github.com/raphjaph)) +- Strip excess postage from end of output ([#662](https://github.com/ordinals/ord/pull/662) by [raphjaph](https://github.com/raphjaph)) +- Download logs to tempdir ([#656](https://github.com/ordinals/ord/pull/656) by [casey](https://github.com/casey)) +- Improve transaction builder checks ([#661](https://github.com/ordinals/ord/pull/661) by [casey](https://github.com/casey)) +- Use redb's two-phase write strategy in production ([#660](https://github.com/ordinals/ord/pull/660) by [casey](https://github.com/casey)) +- Replace `Result<()>` with `Result` ([#657](https://github.com/ordinals/ord/pull/657) by [casey](https://github.com/casey)) +- Add fee when sending ([#655](https://github.com/ordinals/ord/pull/655) by [raphjaph](https://github.com/raphjaph)) +- Make table names more explicit ([#654](https://github.com/ordinals/ord/pull/654) by [casey](https://github.com/casey)) +- Fix race condition in commit test ([#651](https://github.com/ordinals/ord/pull/651) by [casey](https://github.com/casey)) +- Reform `ord wallet send` ([#648](https://github.com/ordinals/ord/pull/648) by [raphjaph](https://github.com/raphjaph)) +- Use https://signet.ordinals.com as default signet publish URL ([#649](https://github.com/ordinals/ord/pull/649) by [casey](https://github.com/casey)) +- Append network to data dir ([#650](https://github.com/ordinals/ord/pull/650) by [casey](https://github.com/casey)) +- Only commit when necessary ([#647](https://github.com/ordinals/ord/pull/647) by [casey](https://github.com/casey)) +- Make rarity text white ([#644](https://github.com/ordinals/ord/pull/644) by [casey](https://github.com/casey)) +- Link to ordinal from rune ([#643](https://github.com/ordinals/ord/pull/643) by [casey](https://github.com/casey)) +- Show inscriptions on /ordinal ([#645](https://github.com/ordinals/ord/pull/645) by [casey](https://github.com/casey)) +- Document search ([#646](https://github.com/ordinals/ord/pull/646) by [casey](https://github.com/casey)) +- Check that RPC server is on correct network ([#642](https://github.com/ordinals/ord/pull/642) by [casey](https://github.com/casey)) +- Add /input page ([#639](https://github.com/ordinals/ord/pull/639) by [casey](https://github.com/casey)) +- Expand search box to fill available space ([#633](https://github.com/ordinals/ord/pull/633) by [casey](https://github.com/casey)) +- Add `ord rune publish` command ([#637](https://github.com/ordinals/ord/pull/637) by [casey](https://github.com/casey)) +- Add links to docs ([#635](https://github.com/ordinals/ord/pull/635) by [casey](https://github.com/casey)) +- Use docs for name of workflow and directory ([#632](https://github.com/ordinals/ord/pull/632) by [casey](https://github.com/casey)) +- Remove multilingual book config key ([#631](https://github.com/ordinals/ord/pull/631) by [casey](https://github.com/casey)) +- Add `ord wallet send` ([#618](https://github.com/ordinals/ord/pull/618) by [raphjaph](https://github.com/raphjaph)) +- Streamline roadmap ([#628](https://github.com/ordinals/ord/pull/628) by [casey](https://github.com/casey)) +- Improve styling ([#626](https://github.com/ordinals/ord/pull/626) by [casey](https://github.com/casey)) +- Fix book publish directory ([#625](https://github.com/ordinals/ord/pull/625) by [casey](https://github.com/casey)) +- Convert docs from Zola to mdBook ([#623](https://github.com/ordinals/ord/pull/623) by [casey](https://github.com/casey)) +- Add nav bar ([#614](https://github.com/ordinals/ord/pull/614) by [casey](https://github.com/casey)) +- Add status header to homepage ([#620](https://github.com/ordinals/ord/pull/620) by [casey](https://github.com/casey)) +- Update roadmap ([#617](https://github.com/ordinals/ord/pull/617) by [casey](https://github.com/casey)) +- Use reduced database durability during tests ([#621](https://github.com/ordinals/ord/pull/621) by [casey](https://github.com/casey)) +- Add /rare.txt ([#619](https://github.com/ordinals/ord/pull/619) by [casey](https://github.com/casey)) +- Embellish block page ([#605](https://github.com/ordinals/ord/pull/605) by [casey](https://github.com/casey)) +- Refactor server error handling ([#607](https://github.com/ordinals/ord/pull/607) by [casey](https://github.com/casey)) +- Profile tests ([#608](https://github.com/ordinals/ord/pull/608) by [casey](https://github.com/casey)) +- Display ranges with an en dash ([#606](https://github.com/ordinals/ord/pull/606) by [casey](https://github.com/casey)) +- Display more information homepage ([#610](https://github.com/ordinals/ord/pull/610) by [casey](https://github.com/casey)) +- Remove prime trait ([#612](https://github.com/ordinals/ord/pull/612) by [casey](https://github.com/casey)) +- Sort ordinal properties ([#609](https://github.com/ordinals/ord/pull/609) by [casey](https://github.com/casey)) +- Add dark mode ([#611](https://github.com/ordinals/ord/pull/611) by [casey](https://github.com/casey)) +- Add more help text to CLI ([#613](https://github.com/ordinals/ord/pull/613) by [casey](https://github.com/casey)) +- Expand ordinal hunting guide ([#600](https://github.com/ordinals/ord/pull/600) by [casey](https://github.com/casey)) +- Embellish transaction page ([#602](https://github.com/ordinals/ord/pull/602) by [casey](https://github.com/casey)) +- Add `ord wallet list` command ([#601](https://github.com/ordinals/ord/pull/601) by [raphjaph](https://github.com/raphjaph)) +- Ignore temporary directory ([#594](https://github.com/ordinals/ord/pull/594) by [casey](https://github.com/casey)) +- Add ordinal hunting how-to docs page ([#596](https://github.com/ordinals/ord/pull/596) by [raphjaph](https://github.com/raphjaph)) +- Fix bounty example links ([#595](https://github.com/ordinals/ord/pull/595) by [casey](https://github.com/casey)) [0.0.5](https://github.com/ordinals/ord/releases/tag/0.0.5) - 2022-10-02 --------------------------------------------------------------------- -- Add bitcoin.conf (#592) -- Add uncommon ordinal bounty (#588) -- Show output size on output page (#590) -- Implement `wallet identify` (#586) -- Report integration test times (#587) -- Show message when output couldn't be listed because it was spent (#585) -- Add server integration test (#583) -- Use constants from rust-bitcoin (#564) -- Update dependencies (#582) -- Move bounties into subpages (#576) -- Convert last find integration test to unit test (#580) -- Make index::custom_index_size test faster (#579) -- Make info::basic test faster (#578) -- Convert list unit tests to inegration tests (#572) -- Add prime trait (#563) -- Rename workflow jobs (#575) -- Convert some find integration tests to unit tests (#571) -- Remove /clock.svg route (#573) -- Fix test bitcoin core rpc server compilation (#570) -- Move test Bitcoin Core RPC server into sub-crate (#569) -- Remove spent output test (#568) -- Remove find-by-slot tests (#567) -- Remove BDK wallet (#566) -- Show if a reorg has happened on /status (#518) -- Convert block and transaction integration tests to unit tests (#560) -- Fix release script (#562) +- Add bitcoin.conf ([#592](https://github.com/ordinals/ord/pull/592) by [casey](https://github.com/casey)) +- Add uncommon ordinal bounty ([#588](https://github.com/ordinals/ord/pull/588) by [casey](https://github.com/casey)) +- Show output size on output page ([#590](https://github.com/ordinals/ord/pull/590) by [casey](https://github.com/casey)) +- Implement `wallet identify` ([#586](https://github.com/ordinals/ord/pull/586) by [raphjaph](https://github.com/raphjaph)) +- Report integration test times ([#587](https://github.com/ordinals/ord/pull/587) by [casey](https://github.com/casey)) +- Show message when output couldn't be listed because it was spent ([#585](https://github.com/ordinals/ord/pull/585) by [casey](https://github.com/casey)) +- Add server integration test ([#583](https://github.com/ordinals/ord/pull/583) by [casey](https://github.com/casey)) +- Use constants from rust-bitcoin ([#564](https://github.com/ordinals/ord/pull/564) by [casey](https://github.com/casey)) +- Update dependencies ([#582](https://github.com/ordinals/ord/pull/582) by [casey](https://github.com/casey)) +- Move bounties into subpages ([#576](https://github.com/ordinals/ord/pull/576) by [casey](https://github.com/casey)) +- Convert last find integration test to unit test ([#580](https://github.com/ordinals/ord/pull/580) by [raphjaph](https://github.com/raphjaph)) +- Make index::custom_index_size test faster ([#579](https://github.com/ordinals/ord/pull/579) by [casey](https://github.com/casey)) +- Make info::basic test faster ([#578](https://github.com/ordinals/ord/pull/578) by [casey](https://github.com/casey)) +- Convert list unit tests to inegration tests ([#572](https://github.com/ordinals/ord/pull/572) by [raphjaph](https://github.com/raphjaph)) +- Add prime trait ([#563](https://github.com/ordinals/ord/pull/563) by [casey](https://github.com/casey)) +- Rename workflow jobs ([#575](https://github.com/ordinals/ord/pull/575) by [casey](https://github.com/casey)) +- Convert some find integration tests to unit tests ([#571](https://github.com/ordinals/ord/pull/571) by [casey](https://github.com/casey)) +- Remove /clock.svg route ([#573](https://github.com/ordinals/ord/pull/573) by [casey](https://github.com/casey)) +- Fix test bitcoin core rpc server compilation ([#570](https://github.com/ordinals/ord/pull/570) by [casey](https://github.com/casey)) +- Move test Bitcoin Core RPC server into sub-crate ([#569](https://github.com/ordinals/ord/pull/569) by [casey](https://github.com/casey)) +- Remove spent output test ([#568](https://github.com/ordinals/ord/pull/568) by [casey](https://github.com/casey)) +- Remove find-by-slot tests ([#567](https://github.com/ordinals/ord/pull/567) by [casey](https://github.com/casey)) +- Remove BDK wallet ([#566](https://github.com/ordinals/ord/pull/566) by [casey](https://github.com/casey)) +- Show if a reorg has happened on /status ([#518](https://github.com/ordinals/ord/pull/518) by [raphjaph](https://github.com/raphjaph)) +- Convert block and transaction integration tests to unit tests ([#560](https://github.com/ordinals/ord/pull/560) by [raphjaph](https://github.com/raphjaph)) +- Fix release script ([#562](https://github.com/ordinals/ord/pull/562) by [casey](https://github.com/casey)) [0.0.4](https://github.com/ordinals/ord/releases/tag/0.0.4) - 2022-09-26 --------------------------------------------------------------------- -- Add more links and labels to clocks (#552) -- Add script to deploy dev server on production machines (#550) -- Update redb to 0.7.0 (#559) -- Don't block server on index (#551) -- Allow searching for block hashes, txids, and outputs things (#549) -- Convert more integration tests to unit tests (#548) -- Make range integration tests faster (#547) -- Add roadmap (#546) -- Convert some integration tests to unit tests (#544) -- Sync index on `Index::open` (#545) -- Make some tests faster (#543) -- Add search-by-path endpoint at /search/QUERY (#521) -- Note why unit tests should use regtest network (#539) -- Use --chain regtest to speed up unit tests (#538) -- Add attributes to search box (#520) -- Fix off-by-some --height-limit bug (#526) -- Count total number of outputs traversed when building index (#525) -- Use boilerplate 0.2.0 (#531) -- Add favicon to docs.ordinals.com (#530) -- Move docs to GitHub Pages (#515) -- Bounty 1 claimed! (#529) -- Use fixed-size index keys and values. (#516) -- Update dependencies (#519) -- Log retry interval (#509) -- Retry with exponential backoff on RPC errors during indexing (#508) -- Include outpoint in missing outpoint message (#506) -- Link to clock from home page (#499) -- Pass benchmark dir name in justfile recipe (#498) -- Improve benchmark (#497) -- Commit every 1000 blocks instead of every block (#496) -- Improve benchmark script (#493) -- Add colors and tooltips to clock (#476) -- Block height to clock (#477) -- Add benchmark script (#488) -- Add flamegraph recipe (#486) -- Fix degree parsing (#485) -- Add search box to homepage (#479) -- Add shell.nix (#475) -- Fix indentation in test-deploy recipe (#474) -- Document how to turn on logging (#464) -- Add contribution advice to readme (#460) -- Increase default maximum index size for non-regtest chains (#448) -- Remove old NFT mint and verify commands (#418) -- Update readme (#399) -- Allow serving HTTP and HTTPS simultaneously (#359) -- Prevent ordinals that are being sent from being spent as fees (#369) -- Add error on None case for special_ordinals (#382) -- Guard against invalid percentiles (#380) -- Add percentile representation (#378) -- Make --acme-contact optional (#379) -- Improve names for a couple of properties (#377) -- [bin/graph] Skip previous syncs (#376) -- Add graph recipe (#375) -- Log ord by default (#374) -- Don't write to OUTPOINT_TO_TXID table (#373) -- Change just recipe to log main instance by default (#372) -- Add bounty 1 (#370) -- Don't hardcode cookie file in deploy script (#367) -- Remove comments from service files (#368) -- Add special ordinal protection (#357) -- Add defaults for --acme-cache and --acme-domain (#364) -- Read cookie file from --bitcoin-data-dir (#365) -- Pass network to deploy scripts (#366) -- Put .hushlogin in correct location (#363) -- Pass domain to deploy scripts (#361) -- Suppress login messages (#360) -- Disable password auth on deploy (#358) -- Improve deploy scripts (#342) -- Tick tock next block (#355) -- Add `ord wallet identify` (#304) -- Note bounty #0 has been claimed (#356) -- Remove unused CSS font-family (#354) -- Use rustl-acme acceptor (#289) -- Display hashes, ranges, and outputs in monospace (#353) -- Improve
    style (#352) -- Add temporary favicon (#351) -- Make deploys faster (#350) -- Color blocks on homepage by rarity (#349) -- Rarity-color ranges in outputs and link to first ordinal in ranges (#348) -- Remove slide deck (#346) -- Switch to one-at-a-time bounties (#347) -- Add better message for spent outputs (#345) -- Use
      for homepage (#343) -- Remove GitHub pages directory (#344) -- Rename / page from "root" to "home" (#341) -- Remove sleeps from server tests (#340) -- Add space around nav items (#338) -- Style links (#337) -- Add FAQ and bounty (#339) -- Add links to homepage (#335) -- Styling (#333) -- Remove fluff from BIP (#336) -- Remove old comment from bitcoind.service (#334) -- Add viewport meta tag (#332) -- Add rarity colors (#330) -- Don't let ordinals become telephone numbers (#331) -- Add next and prev links to /ordinal (#329) -- Fix broken link (#328) -- Add header to /range (#325) -- Fix off by one bug in index::blocks (#326) -- Add header to /output (#324) -- Limit blocks (#320) -- Add header to /tx (#322) -- Add header to /block/HASH (#321) -- Convert / to boilerplate template (#317) -- Return BlockHash from Index::all (#319) -- Don't warn about installing bitcoind in deploy/setup (#318) -- Improvements (#298) -- Update rust toolchain when deploying (#311) -- Fix forbidden word check (#313) -- Don't run integration tests on MacOS CI (#316) -- Disable redb checksums (#315) -- Pay a fixed fee when sending transactions (#314) -- Refactor duplicate blockchain code in purse (#312) -- Add `ord wallet send` (#305) -- Add wallet balance subcommand (#271) -- Add wallet utxos subcommand (#259) -- Use bitcoin core node for integration tests (#263) -- List transaction outputs (#292) -- Add `/output/:outpoint` endpoint (#293) -- Add /range/:start/:end endpoint (#291) -- Move /list endpoint to /api/list (#288) -- List block transactions at `/block/:hash` (#286) -- Display ordinals at `/ordinal/:ordinal` (#287) -- Wait for bitcoind and ord to become available (#285) -- List blocks on root page (#276) -- Remove user-facing list page (#275) -- Add network option (#274) -- Serve HTTPS with ACME certs (#256) -- Remove unused functionality (#270) -- Revise homepage (#268) -- Link to blog post (#267) -- Use hour, minute, second, and third terminology (#262) -- Allow passing ordinals in degree and decimal notation (#261) -- Update dependencies (#258) -- Make genesis sat mythic (#260) -- Add wallet (#233) -- Overhaul traits (#255) -- Clarify duplicate transaction rule in BIP (#254) -- Purge LMDB (#231) -- Add justfile with commands for moving ordinals around manually (#238) -- Add links to discord server (#237) -- Make `nft verify` take input as argument (#235) -- Add --version flag (#236) -- Bump version: 0.0.2 → 0.0.3 (#234) -- Change deploy target in recipe (#232) -- Use default port and set ambient capabilities in ord service (#230) -- Test deploy on vagrant (#229) -- Update slide deck (#227) -- Add link to video (#226) -- Separate deck pages (#225) -- Fix docs HTML (#224) -- Add side deck (#223) -- Change slot notation to AxBxCxD (#222) -- Improve NFT encoding (#221) -- Remove use of sha256d in signature algorithm (#219) -- Use standard formats (#218) -- Use CBOR for serialization/deserialization (#217) -- Add nix flake (#214) -- Build binaries for releases (#213) +- Add more links and labels to clocks ([#552](https://github.com/ordinals/ord/pull/552) by [casey](https://github.com/casey)) +- Add script to deploy dev server on production machines ([#550](https://github.com/ordinals/ord/pull/550) by [casey](https://github.com/casey)) +- Update redb to 0.7.0 ([#559](https://github.com/ordinals/ord/pull/559) by [windsok](https://github.com/windsok)) +- Don't block server on index ([#551](https://github.com/ordinals/ord/pull/551) by [casey](https://github.com/casey)) +- Allow searching for block hashes, txids, and outputs things ([#549](https://github.com/ordinals/ord/pull/549) by [casey](https://github.com/casey)) +- Convert more integration tests to unit tests ([#548](https://github.com/ordinals/ord/pull/548) by [raphjaph](https://github.com/raphjaph)) +- Make range integration tests faster ([#547](https://github.com/ordinals/ord/pull/547) by [casey](https://github.com/casey)) +- Add roadmap ([#546](https://github.com/ordinals/ord/pull/546) by [raphjaph](https://github.com/raphjaph)) +- Convert some integration tests to unit tests ([#544](https://github.com/ordinals/ord/pull/544) by [raphjaph](https://github.com/raphjaph)) +- Sync index on `Index::open` ([#545](https://github.com/ordinals/ord/pull/545) by [casey](https://github.com/casey)) +- Make some tests faster ([#543](https://github.com/ordinals/ord/pull/543) by [casey](https://github.com/casey)) +- Add search-by-path endpoint at /search/QUERY ([#521](https://github.com/ordinals/ord/pull/521) by [raphjaph](https://github.com/raphjaph)) +- Note why unit tests should use regtest network ([#539](https://github.com/ordinals/ord/pull/539) by [casey](https://github.com/casey)) +- Use --chain regtest to speed up unit tests ([#538](https://github.com/ordinals/ord/pull/538) by [casey](https://github.com/casey)) +- Add attributes to search box ([#520](https://github.com/ordinals/ord/pull/520) by [casey](https://github.com/casey)) +- Fix off-by-some --height-limit bug ([#526](https://github.com/ordinals/ord/pull/526) by [casey](https://github.com/casey)) +- Count total number of outputs traversed when building index ([#525](https://github.com/ordinals/ord/pull/525) by [raphjaph](https://github.com/raphjaph)) +- Use boilerplate 0.2.0 ([#531](https://github.com/ordinals/ord/pull/531) by [casey](https://github.com/casey)) +- Add favicon to docs.ordinals.com ([#530](https://github.com/ordinals/ord/pull/530) by [casey](https://github.com/casey)) +- Move docs to GitHub Pages ([#515](https://github.com/ordinals/ord/pull/515) by [casey](https://github.com/casey)) +- Bounty 1 claimed! ([#529](https://github.com/ordinals/ord/pull/529) by [casey](https://github.com/casey)) +- Use fixed-size index keys and values. ([#516](https://github.com/ordinals/ord/pull/516) by [casey](https://github.com/casey)) +- Update dependencies ([#519](https://github.com/ordinals/ord/pull/519) by [casey](https://github.com/casey)) +- Log retry interval ([#509](https://github.com/ordinals/ord/pull/509) by [veryordinally](https://github.com/veryordinally)) +- Retry with exponential backoff on RPC errors during indexing ([#508](https://github.com/ordinals/ord/pull/508) by [casey](https://github.com/casey)) +- Include outpoint in missing outpoint message ([#506](https://github.com/ordinals/ord/pull/506) by [casey](https://github.com/casey)) +- Link to clock from home page ([#499](https://github.com/ordinals/ord/pull/499) by [casey](https://github.com/casey)) +- Pass benchmark dir name in justfile recipe ([#498](https://github.com/ordinals/ord/pull/498) by [casey](https://github.com/casey)) +- Improve benchmark ([#497](https://github.com/ordinals/ord/pull/497) by [casey](https://github.com/casey)) +- Commit every 1000 blocks instead of every block ([#496](https://github.com/ordinals/ord/pull/496) by [casey](https://github.com/casey)) +- Improve benchmark script ([#493](https://github.com/ordinals/ord/pull/493) by [casey](https://github.com/casey)) +- Add colors and tooltips to clock ([#476](https://github.com/ordinals/ord/pull/476) by [raphjaph](https://github.com/raphjaph)) +- Block height to clock ([#477](https://github.com/ordinals/ord/pull/477) by [raphjaph](https://github.com/raphjaph)) +- Add benchmark script ([#488](https://github.com/ordinals/ord/pull/488) by [casey](https://github.com/casey)) +- Add flamegraph recipe ([#486](https://github.com/ordinals/ord/pull/486) by [casey](https://github.com/casey)) +- Fix degree parsing ([#485](https://github.com/ordinals/ord/pull/485) by [raphjaph](https://github.com/raphjaph)) +- Add search box to homepage ([#479](https://github.com/ordinals/ord/pull/479) by [casey](https://github.com/casey)) +- Add shell.nix ([#475](https://github.com/ordinals/ord/pull/475) by [jurraca](https://github.com/jurraca)) +- Fix indentation in test-deploy recipe ([#474](https://github.com/ordinals/ord/pull/474) by [casey](https://github.com/casey)) +- Document how to turn on logging ([#464](https://github.com/ordinals/ord/pull/464) by [casey](https://github.com/casey)) +- Add contribution advice to readme ([#460](https://github.com/ordinals/ord/pull/460) by [casey](https://github.com/casey)) +- Increase default maximum index size for non-regtest chains ([#448](https://github.com/ordinals/ord/pull/448) by [casey](https://github.com/casey)) +- Remove old NFT mint and verify commands ([#418](https://github.com/ordinals/ord/pull/418) by [casey](https://github.com/casey)) +- Update readme ([#399](https://github.com/ordinals/ord/pull/399) by [casey](https://github.com/casey)) +- Allow serving HTTP and HTTPS simultaneously ([#359](https://github.com/ordinals/ord/pull/359) by [casey](https://github.com/casey)) +- Prevent ordinals that are being sent from being spent as fees ([#369](https://github.com/ordinals/ord/pull/369) by [terror](https://github.com/terror)) +- Add error on None case for special_ordinals ([#382](https://github.com/ordinals/ord/pull/382) by [terror](https://github.com/terror)) +- Guard against invalid percentiles ([#380](https://github.com/ordinals/ord/pull/380) by [casey](https://github.com/casey)) +- Add percentile representation ([#378](https://github.com/ordinals/ord/pull/378) by [casey](https://github.com/casey)) +- Make --acme-contact optional ([#379](https://github.com/ordinals/ord/pull/379) by [casey](https://github.com/casey)) +- Improve names for a couple of properties ([#377](https://github.com/ordinals/ord/pull/377) by [casey](https://github.com/casey)) +- [bin/graph] Skip previous syncs ([#376](https://github.com/ordinals/ord/pull/376) by [casey](https://github.com/casey)) +- Add graph recipe ([#375](https://github.com/ordinals/ord/pull/375) by [terror](https://github.com/terror)) +- Log ord by default ([#374](https://github.com/ordinals/ord/pull/374) by [casey](https://github.com/casey)) +- Don't write to OUTPOINT_TO_TXID table ([#373](https://github.com/ordinals/ord/pull/373) by [casey](https://github.com/casey)) +- Change just recipe to log main instance by default ([#372](https://github.com/ordinals/ord/pull/372) by [casey](https://github.com/casey)) +- Add bounty 1 ([#370](https://github.com/ordinals/ord/pull/370) by [casey](https://github.com/casey)) +- Don't hardcode cookie file in deploy script ([#367](https://github.com/ordinals/ord/pull/367) by [casey](https://github.com/casey)) +- Remove comments from service files ([#368](https://github.com/ordinals/ord/pull/368) by [casey](https://github.com/casey)) +- Add special ordinal protection ([#357](https://github.com/ordinals/ord/pull/357) by [terror](https://github.com/terror)) +- Add defaults for --acme-cache and --acme-domain ([#364](https://github.com/ordinals/ord/pull/364) by [casey](https://github.com/casey)) +- Read cookie file from --bitcoin-data-dir ([#365](https://github.com/ordinals/ord/pull/365) by [casey](https://github.com/casey)) +- Pass network to deploy scripts ([#366](https://github.com/ordinals/ord/pull/366) by [casey](https://github.com/casey)) +- Put .hushlogin in correct location ([#363](https://github.com/ordinals/ord/pull/363) by [casey](https://github.com/casey)) +- Pass domain to deploy scripts ([#361](https://github.com/ordinals/ord/pull/361) by [casey](https://github.com/casey)) +- Suppress login messages ([#360](https://github.com/ordinals/ord/pull/360) by [casey](https://github.com/casey)) +- Disable password auth on deploy ([#358](https://github.com/ordinals/ord/pull/358) by [casey](https://github.com/casey)) +- Improve deploy scripts ([#342](https://github.com/ordinals/ord/pull/342) by [casey](https://github.com/casey)) +- Tick tock next block ([#355](https://github.com/ordinals/ord/pull/355) by [casey](https://github.com/casey)) +- Add `ord wallet identify` ([#304](https://github.com/ordinals/ord/pull/304) by [terror](https://github.com/terror)) +- Note bounty #0 has been claimed ([#356](https://github.com/ordinals/ord/pull/356) by [casey](https://github.com/casey)) +- Remove unused CSS font-family ([#354](https://github.com/ordinals/ord/pull/354) by [casey](https://github.com/casey)) +- Use rustl-acme acceptor ([#289](https://github.com/ordinals/ord/pull/289) by [casey](https://github.com/casey)) +- Display hashes, ranges, and outputs in monospace ([#353](https://github.com/ordinals/ord/pull/353) by [casey](https://github.com/casey)) +- Improve
        style ([#352](https://github.com/ordinals/ord/pull/352) by [casey](https://github.com/casey)) +- Add temporary favicon ([#351](https://github.com/ordinals/ord/pull/351) by [casey](https://github.com/casey)) +- Make deploys faster ([#350](https://github.com/ordinals/ord/pull/350) by [casey](https://github.com/casey)) +- Color blocks on homepage by rarity ([#349](https://github.com/ordinals/ord/pull/349) by [terror](https://github.com/terror)) +- Rarity-color ranges in outputs and link to first ordinal in ranges ([#348](https://github.com/ordinals/ord/pull/348) by [casey](https://github.com/casey)) +- Remove slide deck ([#346](https://github.com/ordinals/ord/pull/346) by [casey](https://github.com/casey)) +- Switch to one-at-a-time bounties ([#347](https://github.com/ordinals/ord/pull/347) by [casey](https://github.com/casey)) +- Add better message for spent outputs ([#345](https://github.com/ordinals/ord/pull/345) by [terror](https://github.com/terror)) +- Use
          for homepage ([#343](https://github.com/ordinals/ord/pull/343) by [casey](https://github.com/casey)) +- Remove GitHub pages directory ([#344](https://github.com/ordinals/ord/pull/344) by [casey](https://github.com/casey)) +- Rename / page from "root" to "home" ([#341](https://github.com/ordinals/ord/pull/341) by [casey](https://github.com/casey)) +- Remove sleeps from server tests ([#340](https://github.com/ordinals/ord/pull/340) by [casey](https://github.com/casey)) +- Add space around nav items ([#338](https://github.com/ordinals/ord/pull/338) by [casey](https://github.com/casey)) +- Style links ([#337](https://github.com/ordinals/ord/pull/337) by [casey](https://github.com/casey)) +- Add FAQ and bounty ([#339](https://github.com/ordinals/ord/pull/339) by [casey](https://github.com/casey)) +- Add links to homepage ([#335](https://github.com/ordinals/ord/pull/335) by [casey](https://github.com/casey)) +- Styling ([#333](https://github.com/ordinals/ord/pull/333) by [casey](https://github.com/casey)) +- Remove fluff from BIP ([#336](https://github.com/ordinals/ord/pull/336) by [casey](https://github.com/casey)) +- Remove old comment from bitcoind.service ([#334](https://github.com/ordinals/ord/pull/334) by [casey](https://github.com/casey)) +- Add viewport meta tag ([#332](https://github.com/ordinals/ord/pull/332) by [terror](https://github.com/terror)) +- Add rarity colors ([#330](https://github.com/ordinals/ord/pull/330) by [casey](https://github.com/casey)) +- Don't let ordinals become telephone numbers ([#331](https://github.com/ordinals/ord/pull/331) by [terror](https://github.com/terror)) +- Add next and prev links to /ordinal ([#329](https://github.com/ordinals/ord/pull/329) by [terror](https://github.com/terror)) +- Fix broken link ([#328](https://github.com/ordinals/ord/pull/328) by [casey](https://github.com/casey)) +- Add header to /range ([#325](https://github.com/ordinals/ord/pull/325) by [casey](https://github.com/casey)) +- Fix off by one bug in index::blocks ([#326](https://github.com/ordinals/ord/pull/326) by [terror](https://github.com/terror)) +- Add header to /output ([#324](https://github.com/ordinals/ord/pull/324) by [casey](https://github.com/casey)) +- Limit blocks ([#320](https://github.com/ordinals/ord/pull/320) by [casey](https://github.com/casey)) +- Add header to /tx ([#322](https://github.com/ordinals/ord/pull/322) by [casey](https://github.com/casey)) +- Add header to /block/HASH ([#321](https://github.com/ordinals/ord/pull/321) by [casey](https://github.com/casey)) +- Convert / to boilerplate template ([#317](https://github.com/ordinals/ord/pull/317) by [casey](https://github.com/casey)) +- Return BlockHash from Index::all ([#319](https://github.com/ordinals/ord/pull/319) by [casey](https://github.com/casey)) +- Don't warn about installing bitcoind in deploy/setup ([#318](https://github.com/ordinals/ord/pull/318) by [casey](https://github.com/casey)) +- Improvements ([#298](https://github.com/ordinals/ord/pull/298) by [casey](https://github.com/casey)) +- Update rust toolchain when deploying ([#311](https://github.com/ordinals/ord/pull/311) by [casey](https://github.com/casey)) +- Fix forbidden word check ([#313](https://github.com/ordinals/ord/pull/313) by [casey](https://github.com/casey)) +- Don't run integration tests on MacOS CI ([#316](https://github.com/ordinals/ord/pull/316) by [casey](https://github.com/casey)) +- Disable redb checksums ([#315](https://github.com/ordinals/ord/pull/315) by [casey](https://github.com/casey)) +- Pay a fixed fee when sending transactions ([#314](https://github.com/ordinals/ord/pull/314) by [terror](https://github.com/terror)) +- Refactor duplicate blockchain code in purse ([#312](https://github.com/ordinals/ord/pull/312) by [terror](https://github.com/terror)) +- Add `ord wallet send` ([#305](https://github.com/ordinals/ord/pull/305) by [terror](https://github.com/terror)) +- Add wallet balance subcommand ([#271](https://github.com/ordinals/ord/pull/271) by [terror](https://github.com/terror)) +- Add wallet utxos subcommand ([#259](https://github.com/ordinals/ord/pull/259) by [terror](https://github.com/terror)) +- Use bitcoin core node for integration tests ([#263](https://github.com/ordinals/ord/pull/263) by [terror](https://github.com/terror)) +- List transaction outputs ([#292](https://github.com/ordinals/ord/pull/292) by [terror](https://github.com/terror)) +- Add `/output/:outpoint` endpoint ([#293](https://github.com/ordinals/ord/pull/293) by [casey](https://github.com/casey)) +- Add /range/:start/:end endpoint ([#291](https://github.com/ordinals/ord/pull/291) by [casey](https://github.com/casey)) +- Move /list endpoint to /api/list ([#288](https://github.com/ordinals/ord/pull/288) by [casey](https://github.com/casey)) +- List block transactions at `/block/:hash` ([#286](https://github.com/ordinals/ord/pull/286) by [terror](https://github.com/terror)) +- Display ordinals at `/ordinal/:ordinal` ([#287](https://github.com/ordinals/ord/pull/287) by [casey](https://github.com/casey)) +- Wait for bitcoind and ord to become available ([#285](https://github.com/ordinals/ord/pull/285) by [casey](https://github.com/casey)) +- List blocks on root page ([#276](https://github.com/ordinals/ord/pull/276) by [terror](https://github.com/terror)) +- Remove user-facing list page ([#275](https://github.com/ordinals/ord/pull/275) by [casey](https://github.com/casey)) +- Add network option ([#274](https://github.com/ordinals/ord/pull/274) by [terror](https://github.com/terror)) +- Serve HTTPS with ACME certs ([#256](https://github.com/ordinals/ord/pull/256) by [casey](https://github.com/casey)) +- Remove unused functionality ([#270](https://github.com/ordinals/ord/pull/270) by [casey](https://github.com/casey)) +- Revise homepage ([#268](https://github.com/ordinals/ord/pull/268) by [casey](https://github.com/casey)) +- Link to blog post ([#267](https://github.com/ordinals/ord/pull/267) by [casey](https://github.com/casey)) +- Use hour, minute, second, and third terminology ([#262](https://github.com/ordinals/ord/pull/262) by [casey](https://github.com/casey)) +- Allow passing ordinals in degree and decimal notation ([#261](https://github.com/ordinals/ord/pull/261) by [casey](https://github.com/casey)) +- Update dependencies ([#258](https://github.com/ordinals/ord/pull/258) by [casey](https://github.com/casey)) +- Make genesis sat mythic ([#260](https://github.com/ordinals/ord/pull/260) by [casey](https://github.com/casey)) +- Add wallet ([#233](https://github.com/ordinals/ord/pull/233) by [terror](https://github.com/terror)) +- Overhaul traits ([#255](https://github.com/ordinals/ord/pull/255) by [casey](https://github.com/casey)) +- Clarify duplicate transaction rule in BIP ([#254](https://github.com/ordinals/ord/pull/254) by [casey](https://github.com/casey)) +- Purge LMDB ([#231](https://github.com/ordinals/ord/pull/231) by [casey](https://github.com/casey)) +- Add justfile with commands for moving ordinals around manually ([#238](https://github.com/ordinals/ord/pull/238) by [casey](https://github.com/casey)) +- Add links to discord server ([#237](https://github.com/ordinals/ord/pull/237) by [casey](https://github.com/casey)) +- Make `nft verify` take input as argument ([#235](https://github.com/ordinals/ord/pull/235) by [casey](https://github.com/casey)) +- Add --version flag ([#236](https://github.com/ordinals/ord/pull/236) by [casey](https://github.com/casey)) +- Bump version: 0.0.2 → 0.0.3 ([#234](https://github.com/ordinals/ord/pull/234) by [casey](https://github.com/casey)) +- Change deploy target in recipe ([#232](https://github.com/ordinals/ord/pull/232) by [terror](https://github.com/terror)) +- Use default port and set ambient capabilities in ord service ([#230](https://github.com/ordinals/ord/pull/230) by [terror](https://github.com/terror)) +- Test deploy on vagrant ([#229](https://github.com/ordinals/ord/pull/229) by [terror](https://github.com/terror)) +- Update slide deck ([#227](https://github.com/ordinals/ord/pull/227) by [casey](https://github.com/casey)) +- Add link to video ([#226](https://github.com/ordinals/ord/pull/226) by [casey](https://github.com/casey)) +- Separate deck pages ([#225](https://github.com/ordinals/ord/pull/225) by [casey](https://github.com/casey)) +- Fix docs HTML ([#224](https://github.com/ordinals/ord/pull/224) by [casey](https://github.com/casey)) +- Add side deck ([#223](https://github.com/ordinals/ord/pull/223) by [casey](https://github.com/casey)) +- Change slot notation to AxBxCxD ([#222](https://github.com/ordinals/ord/pull/222) by [casey](https://github.com/casey)) +- Improve NFT encoding ([#221](https://github.com/ordinals/ord/pull/221) by [alok](https://github.com/alok)) +- Remove use of sha256d in signature algorithm ([#219](https://github.com/ordinals/ord/pull/219) by [casey](https://github.com/casey)) +- Use standard formats ([#218](https://github.com/ordinals/ord/pull/218) by [terror](https://github.com/terror)) +- Use CBOR for serialization/deserialization ([#217](https://github.com/ordinals/ord/pull/217) by [terror](https://github.com/terror)) +- Add nix flake ([#214](https://github.com/ordinals/ord/pull/214) by [jurraca](https://github.com/jurraca)) +- Build binaries for releases ([#213](https://github.com/ordinals/ord/pull/213) by [casey](https://github.com/casey)) [0.0.1](https://github.com/ordinals/ord/releases/tag/0.0.1) - 2022-06-05 --------------------------------------------------------------------- -- Add commands to mint and verify NFTs (#211) -- Add legendary sat location hints (#208) -- Re-implement find (#206) -- Add explanation to bounty page (#205) -- Change bounty dir to bounties (#204) -- Add ordinal bounty page (#203) -- Add drawbacks section to BIP (#202) -- Remove log spam (#200) -- Don't reopen LMDB databases (#201) -- Add serve recipe (#199) -- Continuously index ranges (#198) -- Add about page to website (#197) -- Put script tag in (#195) -- Add list form (#194) -- Run server command (#193) -- Remove find command and KEY_TO_SATPOINT table (#192) -- Make checkout script check out correct branch (#191) -- Add server subcommand (#185) -- Use anyhow to add context to error messages (#184) -- Automate deployment (#187) -- Add ordinals.com website source (#186) -- Add LMDB database backend (#177) -- Link to project board in readme (#176) -- Test null outputs and inputs (#169) -- Log transaction indexing (#168) -- Remove the acknowledgements section since it's still a draft (#164) -- Add index size to info subcommand (#162) -- Document duplicate txid behavior (#161) -- Update redb 0.0.5 (#160) -- Document terminology and notation (#158) -- Describe dust output avoidance workaround (#156) -- Improve readme (#154) -- Improve find height check (#150) -- Use index for find queries (#149) -- Note that LN cannot be used to transfer individual ordinals (#147) -- Print block transaction count (#146) -- Use clap instead of structopt (#145) -- Incremental indexing (#141) -- Use human readable byte values for info (#144) -- Add info subcommand (#138) -- Accept human readable --index-size values (#142) -- Use redb::TableDefinition (#143) -- Work with live Bitcoin Core RPC API (#140) -- Use JSON RPC API instead of blocksdir(#139) -- Test mining and spending transactions in the same block (#136) -- Don't recreate db every run (#131) -- Fix off by one error in log message (#135) -- Improve index performance (#134) -- Reference independent invention (#133) -- Decode block header only in Index::index_blockfiles (#132) -- Add index benchmark (#111) -- Mention physical transfer of ordinals (#130) -- Reorder BIP sections (#129) -- Add applications section to BIP (#127) -- Add initial draft of BIP (#117) -- Test that index handles out-of-order blockfiles (#124) -- Test fee assignment (#122) -- Test underpaying subsidy (#121) -- Allow setting index size (#120) -- Use redb 0.0.4 (#114) -- Add duplicate transaction range test (#113) -- Split up Index::index_blockfiles (#96) -- Allow invalid ordinals (#95) -- Don't hardcode genesis block (#91) -- Rename index_blockfile to index_blockfiles (#90) -- Pin redb to GitHub revision to avoid panic (#89) -- Log progress while indexing (#88) -- Index all files in blocksdir (#87) -- Fix crash when indexing a block with no transactions (#86) -- Refactor test API (#82) -- More integration test cleanup (#70) -- Refactor test block creation (#68) -- Improve index (#60) -- Add `index.redb` to gitignore (#58) -- Make find command print satpoints instead of outpoints (#57) -- Improve transfer algorithm pseudocode (#53) -- Add epoch trait (#51) -- Use strong types (#48) -- Add Index struct (#47) -- Use ordinal number terminology (#46) -- Number satoshis in ascending order (#45) -- Use default location if `--blocksdir` is not provided (#42) -- Update dependencies (#40) -- Create illusive and cursed traits (#36) -- Add character trait (#35) -- Add open questions to readme (#34) -- Use descending numbering scheme (#33) -- Handle out-of-bound values (#30) -- Add yet more traits (#29) -- Add shiny trait (#28) -- Add command to find satoshi with a given name (#27) -- Add more traits (#25) -- Add traits (#24) -- Add readme and refactor code (#22) -- Rename to sat-tracker (#21) -- Start new sat-based implementation (#20) -- Add justfile and catalog recipe (#12) -- Organize code (#10) -- Add supply command (#9) -- Track atom locations (#2) -- Add Rust binary and CI workflow (#1) +- Add commands to mint and verify NFTs ([#211](https://github.com/ordinals/ord/pull/211) by [casey](https://github.com/casey)) +- Add legendary sat location hints ([#208](https://github.com/ordinals/ord/pull/208) by [casey](https://github.com/casey)) +- Re-implement find ([#206](https://github.com/ordinals/ord/pull/206) by [terror](https://github.com/terror)) +- Add explanation to bounty page ([#205](https://github.com/ordinals/ord/pull/205) by [casey](https://github.com/casey)) +- Change bounty dir to bounties ([#204](https://github.com/ordinals/ord/pull/204) by [casey](https://github.com/casey)) +- Add ordinal bounty page ([#203](https://github.com/ordinals/ord/pull/203) by [terror](https://github.com/terror)) +- Add drawbacks section to BIP ([#202](https://github.com/ordinals/ord/pull/202) by [casey](https://github.com/casey)) +- Remove log spam ([#200](https://github.com/ordinals/ord/pull/200) by [casey](https://github.com/casey)) +- Don't reopen LMDB databases ([#201](https://github.com/ordinals/ord/pull/201) by [casey](https://github.com/casey)) +- Add serve recipe ([#199](https://github.com/ordinals/ord/pull/199) by [casey](https://github.com/casey)) +- Continuously index ranges ([#198](https://github.com/ordinals/ord/pull/198) by [terror](https://github.com/terror)) +- Add about page to website ([#197](https://github.com/ordinals/ord/pull/197) by [casey](https://github.com/casey)) +- Put script tag in ([#195](https://github.com/ordinals/ord/pull/195) by [casey](https://github.com/casey)) +- Add list form ([#194](https://github.com/ordinals/ord/pull/194) by [terror](https://github.com/terror)) +- Run server command ([#193](https://github.com/ordinals/ord/pull/193) by [casey](https://github.com/casey)) +- Remove find command and KEY_TO_SATPOINT table ([#192](https://github.com/ordinals/ord/pull/192) by [casey](https://github.com/casey)) +- Make checkout script check out correct branch ([#191](https://github.com/ordinals/ord/pull/191) by [casey](https://github.com/casey)) +- Add server subcommand ([#185](https://github.com/ordinals/ord/pull/185) by [terror](https://github.com/terror)) +- Use anyhow to add context to error messages ([#184](https://github.com/ordinals/ord/pull/184) by [casey](https://github.com/casey)) +- Automate deployment ([#187](https://github.com/ordinals/ord/pull/187) by [casey](https://github.com/casey)) +- Add ordinals.com website source ([#186](https://github.com/ordinals/ord/pull/186) by [casey](https://github.com/casey)) +- Add LMDB database backend ([#177](https://github.com/ordinals/ord/pull/177) by [casey](https://github.com/casey)) +- Link to project board in readme ([#176](https://github.com/ordinals/ord/pull/176) by [casey](https://github.com/casey)) +- Test null outputs and inputs ([#169](https://github.com/ordinals/ord/pull/169) by [casey](https://github.com/casey)) +- Log transaction indexing ([#168](https://github.com/ordinals/ord/pull/168) by [casey](https://github.com/casey)) +- Remove the acknowledgements section since it's still a draft ([#164](https://github.com/ordinals/ord/pull/164) by [casey](https://github.com/casey)) +- Add index size to info subcommand ([#162](https://github.com/ordinals/ord/pull/162) by [terror](https://github.com/terror)) +- Document duplicate txid behavior ([#161](https://github.com/ordinals/ord/pull/161) by [casey](https://github.com/casey)) +- Update redb 0.0.5 ([#160](https://github.com/ordinals/ord/pull/160) by [cberner](https://github.com/cberner)) +- Document terminology and notation ([#158](https://github.com/ordinals/ord/pull/158) by [casey](https://github.com/casey)) +- Describe dust output avoidance workaround ([#156](https://github.com/ordinals/ord/pull/156) by [casey](https://github.com/casey)) +- Improve readme ([#154](https://github.com/ordinals/ord/pull/154) by [casey](https://github.com/casey)) +- Improve find height check ([#150](https://github.com/ordinals/ord/pull/150) by [casey](https://github.com/casey)) +- Use index for find queries ([#149](https://github.com/ordinals/ord/pull/149) by [casey](https://github.com/casey)) +- Note that LN cannot be used to transfer individual ordinals ([#147](https://github.com/ordinals/ord/pull/147) by [casey](https://github.com/casey)) +- Print block transaction count ([#146](https://github.com/ordinals/ord/pull/146) by [casey](https://github.com/casey)) +- Use clap instead of structopt ([#145](https://github.com/ordinals/ord/pull/145) by [casey](https://github.com/casey)) +- Incremental indexing ([#141](https://github.com/ordinals/ord/pull/141) by [casey](https://github.com/casey)) +- Use human readable byte values for info ([#144](https://github.com/ordinals/ord/pull/144) by [casey](https://github.com/casey)) +- Add info subcommand ([#138](https://github.com/ordinals/ord/pull/138) by [casey](https://github.com/casey)) +- Accept human readable --index-size values ([#142](https://github.com/ordinals/ord/pull/142) by [casey](https://github.com/casey)) +- Use redb::TableDefinition ([#143](https://github.com/ordinals/ord/pull/143) by [casey](https://github.com/casey)) +- Work with live Bitcoin Core RPC API ([#140](https://github.com/ordinals/ord/pull/140) by [casey](https://github.com/casey)) +- Use JSON RPC API instead of blocksdir([#139](https://github.com/ordinals/ord/pull/139) by [casey](https://github.com/casey)) +- Test mining and spending transactions in the same block ([#136](https://github.com/ordinals/ord/pull/136) by [terror](https://github.com/terror)) +- Don't recreate db every run ([#131](https://github.com/ordinals/ord/pull/131) by [terror](https://github.com/terror)) +- Fix off by one error in log message ([#135](https://github.com/ordinals/ord/pull/135) by [casey](https://github.com/casey)) +- Improve index performance ([#134](https://github.com/ordinals/ord/pull/134) by [casey](https://github.com/casey)) +- Reference independent invention ([#133](https://github.com/ordinals/ord/pull/133) by [casey](https://github.com/casey)) +- Decode block header only in Index::index_blockfiles ([#132](https://github.com/ordinals/ord/pull/132) by [casey](https://github.com/casey)) +- Add index benchmark ([#111](https://github.com/ordinals/ord/pull/111) by [casey](https://github.com/casey)) +- Mention physical transfer of ordinals ([#130](https://github.com/ordinals/ord/pull/130) by [casey](https://github.com/casey)) +- Reorder BIP sections ([#129](https://github.com/ordinals/ord/pull/129) by [casey](https://github.com/casey)) +- Add applications section to BIP ([#127](https://github.com/ordinals/ord/pull/127) by [casey](https://github.com/casey)) +- Add initial draft of BIP ([#117](https://github.com/ordinals/ord/pull/117) by [casey](https://github.com/casey)) +- Test that index handles out-of-order blockfiles ([#124](https://github.com/ordinals/ord/pull/124) by [casey](https://github.com/casey)) +- Test fee assignment ([#122](https://github.com/ordinals/ord/pull/122) by [terror](https://github.com/terror)) +- Test underpaying subsidy ([#121](https://github.com/ordinals/ord/pull/121) by [terror](https://github.com/terror)) +- Allow setting index size ([#120](https://github.com/ordinals/ord/pull/120) by [terror](https://github.com/terror)) +- Use redb 0.0.4 ([#114](https://github.com/ordinals/ord/pull/114) by [casey](https://github.com/casey)) +- Add duplicate transaction range test ([#113](https://github.com/ordinals/ord/pull/113) by [terror](https://github.com/terror)) +- Split up Index::index_blockfiles ([#96](https://github.com/ordinals/ord/pull/96) by [casey](https://github.com/casey)) +- Allow invalid ordinals ([#95](https://github.com/ordinals/ord/pull/95) by [casey](https://github.com/casey)) +- Don't hardcode genesis block ([#91](https://github.com/ordinals/ord/pull/91) by [casey](https://github.com/casey)) +- Rename index_blockfile to index_blockfiles ([#90](https://github.com/ordinals/ord/pull/90) by [casey](https://github.com/casey)) +- Pin redb to GitHub revision to avoid panic ([#89](https://github.com/ordinals/ord/pull/89) by [casey](https://github.com/casey)) +- Log progress while indexing ([#88](https://github.com/ordinals/ord/pull/88) by [casey](https://github.com/casey)) +- Index all files in blocksdir ([#87](https://github.com/ordinals/ord/pull/87) by [casey](https://github.com/casey)) +- Fix crash when indexing a block with no transactions ([#86](https://github.com/ordinals/ord/pull/86) by [casey](https://github.com/casey)) +- Refactor test API ([#82](https://github.com/ordinals/ord/pull/82) by [terror](https://github.com/terror)) +- More integration test cleanup ([#70](https://github.com/ordinals/ord/pull/70) by [casey](https://github.com/casey)) +- Refactor test block creation ([#68](https://github.com/ordinals/ord/pull/68) by [terror](https://github.com/terror)) +- Improve index ([#60](https://github.com/ordinals/ord/pull/60) by [casey](https://github.com/casey)) +- Add `index.redb` to gitignore ([#58](https://github.com/ordinals/ord/pull/58) by [casey](https://github.com/casey)) +- Make find command print satpoints instead of outpoints ([#57](https://github.com/ordinals/ord/pull/57) by [casey](https://github.com/casey)) +- Improve transfer algorithm pseudocode ([#53](https://github.com/ordinals/ord/pull/53) by [casey](https://github.com/casey)) +- Add epoch trait ([#51](https://github.com/ordinals/ord/pull/51) by [casey](https://github.com/casey)) +- Use strong types ([#48](https://github.com/ordinals/ord/pull/48) by [casey](https://github.com/casey)) +- Add Index struct ([#47](https://github.com/ordinals/ord/pull/47) by [casey](https://github.com/casey)) +- Use ordinal number terminology ([#46](https://github.com/ordinals/ord/pull/46) by [casey](https://github.com/casey)) +- Number satoshis in ascending order ([#45](https://github.com/ordinals/ord/pull/45) by [casey](https://github.com/casey)) +- Use default location if `--blocksdir` is not provided ([#42](https://github.com/ordinals/ord/pull/42) by [casey](https://github.com/casey)) +- Update dependencies ([#40](https://github.com/ordinals/ord/pull/40) by [casey](https://github.com/casey)) +- Create illusive and cursed traits ([#36](https://github.com/ordinals/ord/pull/36) by [casey](https://github.com/casey)) +- Add character trait ([#35](https://github.com/ordinals/ord/pull/35) by [casey](https://github.com/casey)) +- Add open questions to readme ([#34](https://github.com/ordinals/ord/pull/34) by [casey](https://github.com/casey)) +- Use descending numbering scheme ([#33](https://github.com/ordinals/ord/pull/33) by [casey](https://github.com/casey)) +- Handle out-of-bound values ([#30](https://github.com/ordinals/ord/pull/30) by [casey](https://github.com/casey)) +- Add yet more traits ([#29](https://github.com/ordinals/ord/pull/29) by [casey](https://github.com/casey)) +- Add shiny trait ([#28](https://github.com/ordinals/ord/pull/28) by [casey](https://github.com/casey)) +- Add command to find satoshi with a given name ([#27](https://github.com/ordinals/ord/pull/27) by [casey](https://github.com/casey)) +- Add more traits ([#25](https://github.com/ordinals/ord/pull/25) by [casey](https://github.com/casey)) +- Add traits ([#24](https://github.com/ordinals/ord/pull/24) by [casey](https://github.com/casey)) +- Add readme and refactor code ([#22](https://github.com/ordinals/ord/pull/22) by [casey](https://github.com/casey)) +- Rename to sat-tracker ([#21](https://github.com/ordinals/ord/pull/21) by [casey](https://github.com/casey)) +- Start new sat-based implementation ([#20](https://github.com/ordinals/ord/pull/20) by [casey](https://github.com/casey)) +- Add justfile and catalog recipe ([#12](https://github.com/ordinals/ord/pull/12) by [casey](https://github.com/casey)) +- Organize code ([#10](https://github.com/ordinals/ord/pull/10) by [casey](https://github.com/casey)) +- Add supply command ([#9](https://github.com/ordinals/ord/pull/9) by [casey](https://github.com/casey)) +- Track atom locations ([#2](https://github.com/ordinals/ord/pull/2) by [casey](https://github.com/casey)) +- Add Rust binary and CI workflow ([#1](https://github.com/ordinals/ord/pull/1) by [casey](https://github.com/casey)) - Add readme diff --git a/Cargo.lock b/Cargo.lock index 4882ab9a53..891bdfcf54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -112,18 +112,27 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" dependencies = [ "backtrace", ] [[package]] name = "arc-swap" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" + +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +dependencies = [ + "nodrop", +] [[package]] name = "asn1-rs" @@ -166,22 +175,22 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 4.0.3", - "event-listener-strategy", + "event-listener 5.2.0", + "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", ] [[package]] name = "async-compression" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" dependencies = [ "brotli", "flate2", @@ -199,7 +208,7 @@ checksum = "096146020b08dbc4587685b0730a7ba905625af13c65f8028035cdfd69573c91" dependencies = [ "anyhow", "futures", - "http 1.0.0", + "http 1.1.0", "httparse", "log", ] @@ -235,12 +244,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener 4.0.3", - "event-listener-strategy", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -269,7 +278,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -284,7 +293,7 @@ dependencies = [ "futures", "futures-rustls", "gloo-net", - "http 1.0.0", + "http 1.1.0", "js-sys", "lazy_static", "log", @@ -308,7 +317,7 @@ dependencies = [ "futures", "futures-lite 1.13.0", "generic_static", - "http 1.0.0", + "http 1.1.0", "log", "rand", "ring 0.16.20", @@ -344,6 +353,14 @@ dependencies = [ "reqwest", ] +[[package]] +name = "audit-content-security-policy" +version = "0.0.0" +dependencies = [ + "colored", + "reqwest", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -361,8 +378,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "headers", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "itoa", @@ -392,7 +408,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body", "mime", "rustversion", @@ -409,7 +425,7 @@ dependencies = [ "arc-swap", "bytes", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "pin-project-lite", @@ -443,9 +459,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.6" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "bech32" @@ -453,6 +475,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bip39" version = "2.0.0" @@ -470,7 +498,7 @@ version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin-private", "bitcoin_hashes 0.12.0", "hex_lit", @@ -508,9 +536,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" @@ -528,7 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel", - "async-lock 3.2.0", + "async-lock 3.3.0", "async-task", "fastrand 2.0.1", "futures-io", @@ -543,12 +571,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1906889b1f805a715eac02b2dea416e25c5cfa00f099530fa9d137a3cff93113" dependencies = [ - "darling 0.20.3", + "darling 0.20.8", "mime", "new_mime_guess", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -574,9 +602,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -584,9 +612,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "byteorder" @@ -608,12 +636,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -627,11 +652,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -639,14 +670,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -655,15 +686,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -671,9 +702,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.14" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -681,33 +712,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.14" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -736,15 +767,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -780,9 +811,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if 1.0.0", ] @@ -848,6 +879,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -860,9 +897,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ "nix", "windows-sys 0.52.0", @@ -880,12 +917,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "darling_core 0.20.3", - "darling_macro 0.20.3", + "darling_core 0.20.8", + "darling_macro 0.20.8", ] [[package]] @@ -898,22 +935,22 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.48", + "strsim 0.10.0", + "syn 2.0.52", ] [[package]] @@ -929,13 +966,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ - "darling_core 0.20.3", + "darling_core 0.20.8", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1065,14 +1102,14 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encode_unicode" @@ -1089,17 +1126,27 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1135,6 +1182,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.4.0" @@ -1145,6 +1203,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.2.0", + "pin-project-lite", +] + [[package]] name = "executable-path" version = "1.0.0" @@ -1287,14 +1355,14 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "futures-rustls" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afda89bce8f65072d24f8b99a2127e229462d8008182ca93f1d5d2e5df8f22f" +checksum = "c8d8a2499f0fecc0492eb3e47eab4e92da7875e1028ad2528f214ac3346ca04e" dependencies = [ "futures-io", "rustls 0.22.2", @@ -1361,9 +1429,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1416,16 +1484,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.11", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1435,9 +1503,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", +] [[package]] name = "hashbrown" @@ -1445,30 +1517,6 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.6", - "bytes", - "headers-core", - "http 0.2.11", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http 0.2.11", -] - [[package]] name = "heck" version = "0.4.1" @@ -1477,9 +1525,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1501,9 +1549,9 @@ checksum = "459a0ca33ee92551e0a3bb1774f2d3bdd1c09fb6341845736662dd25e1fcb52a" [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1512,9 +1560,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1528,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] @@ -1567,13 +1615,13 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -1595,9 +1643,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1634,9 +1682,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", @@ -1644,9 +1692,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -1683,12 +1731,12 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix 0.38.28", + "libc", "windows-sys 0.52.0", ] @@ -1709,9 +1757,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1790,9 +1838,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -1805,9 +1853,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" @@ -1815,7 +1863,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall 0.4.1", ] @@ -1828,9 +1876,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1844,9 +1892,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchit" @@ -1854,6 +1902,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.7.1" @@ -1894,18 +1948,18 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1973,15 +2027,22 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if 1.0.0", + "cfg_aliases", "libc", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -2012,13 +2073,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] @@ -2037,9 +2103,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2092,11 +2158,11 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if 1.0.0", "foreign-types", "libc", @@ -2113,7 +2179,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2124,9 +2190,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2142,14 +2208,14 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ord" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "async-trait", "axum", "axum-server", - "base64 0.21.6", - "bech32", + "base64 0.22.0", + "bech32 0.11.0", "bip39", "bitcoin", "boilerplate", @@ -2157,16 +2223,16 @@ dependencies = [ "chrono", "ciborium", "clap", + "colored", "criterion", "ctrlc", - "derive_more", "dirs", "env_logger", "executable-path", "futures", "hex", "html-escaper", - "http 0.2.11", + "http 0.2.12", "humantime", "hyper", "indicatif", @@ -2177,6 +2243,7 @@ dependencies = [ "miniscript", "mp4", "ord-bitcoincore-rpc", + "ordinals", "pretty_assertions", "pulldown-cmark", "redb", @@ -2187,6 +2254,7 @@ dependencies = [ "rustls 0.22.2", "rustls-acme", "serde", + "serde-hex", "serde_json", "serde_yaml", "sha3", @@ -2202,9 +2270,9 @@ dependencies = [ [[package]] name = "ord-bitcoincore-rpc" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d57a4297d466506cde088e020b33819f9a496d50272b14d7890e6fde5595a3e" +checksum = "16b622f69d68d7201d5186615978aca36ceb7e7c57d7771491d3e261c64ff4d8" dependencies = [ "bitcoin-private", "jsonrpc", @@ -2226,6 +2294,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ordinals" +version = "0.0.4" +dependencies = [ + "bitcoin", + "derive_more", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "parking" version = "2.2.0" @@ -2253,7 +2332,7 @@ dependencies = [ "instant", "libc", "redox_syscall 0.2.16", - "smallvec", + "smallvec 1.13.1", "winapi", ] @@ -2274,22 +2353,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2317,9 +2396,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" @@ -2404,25 +2483,32 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "pulldown-cmark" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "getopts", "memchr", + "pulldown-cmark-escape", "unicase", ] +[[package]] +name = "pulldown-cmark-escape" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d8f9aa0e3cbcfaf8bf00300004ee3b72f74770f9cbac93f6928771f613276b" + [[package]] name = "quick-xml" version = "0.30.0" @@ -2474,9 +2560,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -2484,9 +2570,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2506,9 +2592,9 @@ dependencies = [ [[package]] name = "redb" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08837f9a129bde83c51953b8c96cbb3422b940166b730caa954836106eb1dfd2" +checksum = "72623e6275cd430215b741f41ebda34db93a13ebde253f908b70871c46afc5ba" dependencies = [ "libc", ] @@ -2544,9 +2630,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -2556,9 +2642,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2573,18 +2659,18 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946" dependencies = [ "async-compression", - "base64 0.21.6", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "hyper-tls", @@ -2596,9 +2682,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -2628,23 +2716,24 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if 1.0.0", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rss" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6c0ea0e621c2a3aa34850ebd711526f0ac7385921f57d2430a47cecc7b9cbc" +checksum = "f7b2c77eb4450d7d5f98df52c381cd6c4e19b75dad9209a9530b85a44510219a" dependencies = [ "atom_syndication", "derive_builder", @@ -2654,9 +2743,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -2665,22 +2754,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.48", + "syn 2.0.52", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" dependencies = [ "sha2", "walkdir", @@ -2726,14 +2815,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.12", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -2744,7 +2833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki 0.101.7", "sct", ] @@ -2756,9 +2845,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.1", + "rustls-webpki 0.102.2", "subtle", "zeroize", ] @@ -2778,7 +2867,7 @@ dependencies = [ "chrono", "futures", "futures-rustls", - "http 1.0.0", + "http 1.1.0", "log", "pem", "rcgen", @@ -2798,14 +2887,14 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.6", + "base64 0.21.7", ] [[package]] name = "rustls-pki-types" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" +checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" [[package]] name = "rustls-webpki" @@ -2813,17 +2902,17 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "rustls-webpki" -version = "0.102.1" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", ] @@ -2836,9 +2925,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2870,7 +2959,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -2920,35 +3009,46 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +dependencies = [ + "array-init", + "serde", + "smallvec 0.6.14", +] + [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "indexmap", "itoa", @@ -2958,9 +3058,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -2980,9 +3080,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" dependencies = [ "indexmap", "itoa", @@ -2991,17 +3091,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" version = "0.10.8" @@ -3034,9 +3123,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -3050,12 +3148,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3076,6 +3174,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.24.1" @@ -3117,9 +3221,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3146,9 +3250,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.30.5" +version = "0.30.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +checksum = "0c385888ef380a852a16209afc8cfad22795dd8873d69c9a14d2e2088f118d18" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -3161,20 +3265,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -3182,30 +3286,21 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.1", - "redox_syscall 0.4.1", - "rustix 0.38.28", + "rustix 0.38.31", "windows-sys 0.52.0", ] -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - [[package]] name = "test-bitcoincore-rpc" version = "0.0.1" dependencies = [ + "base64 0.21.7", "bitcoin", "hex", "jsonrpc-core", @@ -3220,32 +3315,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -3260,10 +3356,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -3294,9 +3391,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -3304,7 +3401,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros", "windows-sys 0.48.0", ] @@ -3317,7 +3414,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3412,13 +3509,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "async-compression", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.4.2", "bytes", "futures-core", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body", "http-range-header", + "mime", "pin-project-lite", "tokio", "tokio-util 0.7.10", @@ -3481,9 +3580,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3536,6 +3635,13 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "update-contributors" +version = "0.0.0" +dependencies = [ + "regex", +] + [[package]] name = "url" version = "2.5.0" @@ -3579,9 +3685,9 @@ checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3604,9 +3710,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3614,24 +3720,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3641,9 +3747,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3651,28 +3757,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3680,9 +3786,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "winapi" @@ -3722,7 +3828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -3731,16 +3837,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -3758,22 +3855,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -3793,25 +3875,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3820,15 +3896,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -3838,15 +3908,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -3856,15 +3920,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -3874,15 +3932,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -3892,15 +3944,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -3910,15 +3956,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -3928,9 +3968,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index ff97614705..a3cb953593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ord" description = "◉ Ordinal wallet and block explorer" -version = "0.15.0" +version = "0.16.0" license = "CC0-1.0" edition = "2021" autotests = false @@ -15,15 +15,15 @@ copyright = "The Ord Maintainers" maintainer = "The Ord Maintainers" [workspace] -members = [".", "test-bitcoincore-rpc", "crates/*"] +members = [".", "crates/*"] [dependencies] anyhow = { version = "1.0.56", features = ["backtrace"] } async-trait = "0.1.72" -axum = { version = "0.6.1", features = ["headers", "http2"] } +axum = { version = "0.6.1", features = ["http2"] } axum-server = "0.5.0" -base64 = "0.21.0" -bech32 = "0.9.1" +base64 = "0.22.0" +bech32 = "0.11.0" bip39 = "2.0.0" bitcoin = { version = "0.30.1", features = ["rand"] } boilerplate = { version = "1.0.0", features = ["axum"] } @@ -31,10 +31,10 @@ brotli = "3.4.0" chrono = { version = "0.4.19", features = ["serde"] } ciborium = "0.2.1" clap = { version = "4.4.2", features = ["derive"] } +colored = "2.0.4" ctrlc = { version = "3.2.1", features = ["termination"] } -derive_more = "0.99.17" dirs = "5.0.0" -env_logger = "0.10.0" +env_logger = "0.11.0" futures = "0.3.21" hex = "0.4.3" html-escaper = "0.2.0" @@ -48,14 +48,17 @@ mime = "0.3.16" mime_guess = "2.0.4" miniscript = "10.0.0" mp4 = "0.14.0" -ord-bitcoincore-rpc = "0.17.1" -redb = "1.4.0" +ord-bitcoincore-rpc = "0.17.2" +ordinals = { version = "0.0.4", path = "crates/ordinals" } +redb = "1.5.0" regex = "1.6.0" +reqwest = { version = "0.11.23", features = ["blocking", "json"] } rss = "2.0.1" rust-embed = "8.0.0" rustls = "0.22.0" rustls-acme = { version = "0.8.1", features = ["axum"] } serde = { version = "1.0.137", features = ["derive"] } +serde-hex = "0.1.0" serde_json = { version = "1.0.81", features = ["preserve_order"] } serde_yaml = "0.9.17" sha3 = "0.10.8" @@ -64,14 +67,14 @@ tempfile = "3.2.0" tokio = { version = "1.17.0", features = ["rt-multi-thread"] } tokio-stream = "0.1.9" tokio-util = {version = "0.7.3", features = ["compat"] } -tower-http = { version = "0.4.0", features = ["compression-br", "compression-gzip", "cors", "set-header"] } +tower-http = { version = "0.4.0", features = ["auth", "compression-br", "compression-gzip", "cors", "set-header"] } [dev-dependencies] criterion = "0.5.1" executable-path = "1.0.0" pretty_assertions = "1.2.1" reqwest = { version = "0.11.10", features = ["blocking", "brotli", "json"] } -test-bitcoincore-rpc = { path = "test-bitcoincore-rpc" } +test-bitcoincore-rpc = { path = "crates/test-bitcoincore-rpc" } unindent = "0.2.1" [[bench]] @@ -91,4 +94,4 @@ name = "integration" path = "tests/lib.rs" [build-dependencies] -pulldown-cmark = "0.9.2" +pulldown-cmark = "0.10.0" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..779e5f794a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:1.75.0-bookworm as builder + +WORKDIR /usr/src/ord + +COPY . . + +RUN cargo build --bin ord --release + +FROM debian:bookworm-slim + +COPY --from=builder /usr/src/ord/target/release/ord /usr/local/bin +RUN apt-get update && apt-get install -y openssl + +ENV RUST_BACKTRACE=1 +ENV RUST_LOG=info diff --git a/README.md b/README.md index e0a1d96a9d..2db3362b05 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ This has a number of implications that you must understand in order to use lead to loss of inscriptions. - `ord wallet` commands automatically load the `ord` wallet given by the - `--wallet` option, which defaults to 'ord'. Keep in mind that after running + `--name` option, which defaults to 'ord'. Keep in mind that after running an `ord wallet` command, an `ord` wallet may be loaded. - Because `ord` has access to your Bitcoin Core wallets, `ord` should not be @@ -90,7 +90,7 @@ Building On Debian and Ubuntu, `ord` requires `libssl-dev` when building from source: ``` -sudo apt-get install libssl-dev +sudo apt-get install pkg-config libssl-dev ``` You'll also need Rust: @@ -111,6 +111,14 @@ Once built, the `ord` binary can be found at `./target/release/ord`. `ord` requires `rustc` version 1.67.0 or later. Run `rustc --version` to ensure you have this version. Run `rustup update` to get the latest stable release. +### Docker + +A Docker image can be built with: + +``` +docker build -t ordinals/ord . +``` + ### Homebrew `ord` is available in [Homebrew](https://brew.sh/): @@ -208,22 +216,22 @@ Alternatively, `ord` can be supplied with a username and password on the command line: ``` -ord --bitcoin-rpc-user foo --bitcoin-rpc-pass bar server +ord --bitcoin-rpc-username foo --bitcoin-rpc-password bar server ``` Using environment variables: ``` -export ORD_BITCOIN_RPC_USER=foo -export ORD_BITCOIN_RPC_PASS=bar +export ORD_BITCOIN_RPC_USERNAME=foo +export ORD_BITCOIN_RPC_PASSWORD=bar ord server ``` Or in the config file: ```yaml -bitcoin_rpc_user: foo -bitcoin_rpc_pass: bar +bitcoin_rpc_username: foo +bitcoin_rpc_password: bar ``` Logging @@ -247,62 +255,64 @@ Release x.y.z - Bump version: x.y.z → x.y.z - Update changelog +- Update changelog contributor credits - Update dependencies ``` Translations ------------ -To translate [the docs](https://docs.ordinals.com) we use this +To translate [the docs](https://docs.ordinals.com) we use [mdBook i18n helper](https://github.com/google/mdbook-i18n-helpers). -So read through their [usage guide](https://github.com/google/mdbook-i18n-helpers/blob/main/i18n-helpers/USAGE.md) -to see the structure that translations should follow. -There are some other things to watch out for but feel free to just start a -translation and open a PR. Have a look at [this commit](https://github.com/ordinals/ord/commit/329f31bf6dac207dad001507dd6f18c87fdef355) -for an idea of what to do. A maintainer will also help you integrate it into our -build system. +See +[mdbook-i18n-helpers usage guide](https://github.com/google/mdbook-i18n-helpers/blob/main/i18n-helpers/USAGE.md) +for help. -To align your translated version of the Handbook with reference to commit -[#2427](https://github.com/ordinals/ord/pull/2426), here are some guiding -commands to assist you. It is assumed that your local environment is already -well-configured with [Python](https://www.python.org/), -[Mdbook](https://github.com/rust-lang/mdBook), -[mdBook i18n helper](https://github.com/google/mdbook-i18n-helpers) and that you've clone -this repo. +Adding a new translations is somewhat involved, so feel free to start +translation and open a pull request, even if your translation is incomplete. +Take a look at +[this commit](https://github.com/ordinals/ord/commit/329f31bf6dac207dad001507dd6f18c87fdef355) +for an example of adding a new translation. A maintainer will help you integrate it +into our build system. -1. Run the following command to generate a new `pot` file, which is named as -`messages.pot`: +To start a new translation: -``` -MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' -mdbook build -d po -``` +1. Install `mdbook`, `mdbook-i18n-helpers`, and `mdbook-linkcheck`: -2. Run `msgmerge` where `xx.po` is your localized language version following -the naming standard of [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). -This process will update the `po` file with the most recent original version: + ``` + cargo install mdbook mdbook-i18n-helpers mdbook-linkcheck + ``` -``` -msgmerge --update po/xx.po po/messages.pot -``` +2. Generate a new `pot` file named `messages.pot`: -3. Look for `#, fuzzy`. The `mdBook-i18n-helper` tool utilizes the `"fuzzy"` tag -to highlight sections that have been recently edited. You can proceed to perform -the translation tasks by editing the `"fuzzy"`part. + ``` + MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' + mdbook build -d po + ``` -4. Execute the `mdbook` command. A demonstration in Chinese (`zh`) is given below: +3. Run `msgmerge` on `XX.po` where `XX` is the two-letter + [ISO-639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code for + the language you are translating into. This will update the `po` file with + the text of the most recent English version: -``` -mdbook build docs -d build -MDBOOK_BOOK__LANGUAGE=zh mdbook build docs -d build/zh -mv docs/build/zh/html docs/build/html/zh -python3 -m http.server --directory docs/build/html --bind 127.0.0.1 8080 -``` + ``` + msgmerge --update po/XX.po po/messages.pot + ``` + +4. Untranslated sections are marked with `#, fuzzy` in `XX.po`. Edit the + `msgstr` string with the translated text. + +5. Execute the `mdbook` command to rebuild the docs. For Chinese, whose + two-letter ISO-639 code is `zh`: + + ``` + mdbook build docs -d build + MDBOOK_BOOK__LANGUAGE=zh mdbook build docs -d build/zh + mv docs/build/zh/html docs/build/html/zh + python3 -m http.server --directory docs/build/html --bind 127.0.0.1 8080 + ``` -5. Upon verifying everything and ensuring all is in order, you can commit the -modifications and progress to open a Pull Request (PR) on Github. -(**Note**: Please ensure **ONLY** the **'xx.po'** file is pushed, other files -such as '.pot' or files ending in '~' are **unnecessary** and should **NOT** be -included in the Pull Request.) +6. If everything looks good, commit `XX.po` and open a pull request on GitHub. + Other changed files should be omitted from the pull request. diff --git a/batch.yaml b/batch.yaml index b0b6cd5133..9c9e94561f 100644 --- a/batch.yaml +++ b/batch.yaml @@ -1,9 +1,10 @@ # example batch file # inscription modes: +# - `same-sat`: inscribe on the same sat +# - `satpoints`: inscribe on the first sat of specified satpoint's output # - `separate-outputs`: inscribe on separate postage-sized outputs # - `shared-output`: inscribe on a single output separated by postage -# - `same-sat`: inscribe on the same sat mode: separate-outputs # parent inscription: @@ -12,33 +13,35 @@ parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0 # postage for each inscription: postage: 12345 +# allow reinscribing +reinscribe: true + # sat to inscribe on, can only be used with `same-sat`: # sat: 5000000000 # inscriptions to inscribe -# -# each inscription has the following fields: -# -# `file`: path to inscription contents -# `metadata`: inscription metadata (optional) -# `metaprotocol`: inscription metaprotocol (optional) -# `destination`: destination for that inscription (optional). Note: If no destination is specified a new wallet change address will be used inscriptions: - - file: mango.avif - metadata: - title: Delicious Mangos - description: > - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam semper, - ligula ornare laoreet tincidunt, odio nisi euismod tortor, vel blandit - metus est et odio. Nullam venenatis, urna et molestie vestibulum, orci - mi efficitur risus, eu malesuada diam lorem sed velit. Nam fermentum - dolor et luctus euismod. - destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 - - - file: token.json - metaprotocol: brc-20 - - - file: tulip.png - metadata: - author: Satoshi Nakamoto - destination: bc1pdqrcrxa8vx6gy75mfdfj84puhxffh4fq46h3gkp6jxdd0vjcsdyspfxcv6 + # path to inscription content +- file: mango.avif + # inscription to delegate content to (optional) + delegate: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0 + # destination (optional, if no destination is specified a new wallet change address will be used) + destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 + # inscription metadata (optional) + metadata: + title: Delicious Mangos + description: > + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam semper, + ligula ornare laoreet tincidunt, odio nisi euismod tortor, vel blandit + metus est et odio. Nullam venenatis, urna et molestie vestibulum, orci + mi efficitur risus, eu malesuada diam lorem sed velit. Nam fermentum + dolor et luctus euismod. + # inscription metaprotocol (optional) + metaprotocol: DOPEPROTOCOL-42069 + +- file: token.json + +- file: tulip.png + destination: bc1pdqrcrxa8vx6gy75mfdfj84puhxffh4fq46h3gkp6jxdd0vjcsdyspfxcv6 + metadata: + author: Satoshi Nakamoto diff --git a/bip.mediawiki b/bip.mediawiki index 68ee6cfe44..3a72fcdf99 100644 --- a/bip.mediawiki +++ b/bip.mediawiki @@ -157,7 +157,7 @@ Any single-sat transfer can be accomplished in a single transaction, but the resulting transaction may contain outputs below the dust limit, and thus be non-standard and difficult to get included in a block. Consider a scenario where Alice owns an output containing the range of sats [0,10], the current -dust limit is 5 sats, and Alice wishes to send send sat 4 and 6 to Bob, but +dust limit is 5 sats, and Alice wishes to send sat 4 and 6 to Bob, but retain ordinal 5. Alice could construct a transaction with three outputs of size 5, 1, and 5, containing sats [0,4], 5, and [6,10], respectively. The second output is under the dust limit, and so such a transaction would be diff --git a/crates/audit-content-security-policy/Cargo.toml b/crates/audit-content-security-policy/Cargo.toml new file mode 100644 index 0000000000..e57d9da2e7 --- /dev/null +++ b/crates/audit-content-security-policy/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "audit-content-security-policy" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +colored = "2.0.4" +reqwest = { version = "0.11.22", features = ["blocking"] } diff --git a/crates/audit-content-security-policy/src/main.rs b/crates/audit-content-security-policy/src/main.rs new file mode 100644 index 0000000000..1b4776d72a --- /dev/null +++ b/crates/audit-content-security-policy/src/main.rs @@ -0,0 +1,93 @@ +use {colored::Colorize, reqwest::blocking::get, std::process}; + +const SERVERS: &[(&str, &str, &str)] = &[ + ( + "regtest.ordinals.net", + "/content/41bf99a297ca79d181160a91fc0efc8a71170ee24b87783c9c11b0fcbe23615fi0", + "https://regtest.ordinals.com/content/", + ), + ( + "regtest.ordinals.com", + "/content/41bf99a297ca79d181160a91fc0efc8a71170ee24b87783c9c11b0fcbe23615fi0", + "https://regtest.ordinals.com/content/", + ), + ( + "signet.ordinals.net", + "/content/7e1bc3b56b872aaf4d1aaf1565fac72182313c9142b207f9398afe263e234135i0", + "https://signet.ordinals.com/content/", + ), + ( + "signet.ordinals.com", + "/content/7e1bc3b56b872aaf4d1aaf1565fac72182313c9142b207f9398afe263e234135i0", + "https://signet.ordinals.com/content/", + ), + ( + "testnet.ordinals.net", + "/content/0a1b4e4acf89686e4d012561014041bffd57a62254486f24cb5b0a216c04f102i0", + "https://testnet.ordinals.com/content/", + ), + ( + "testnet.ordinals.com", + "/content/0a1b4e4acf89686e4d012561014041bffd57a62254486f24cb5b0a216c04f102i0", + "https://testnet.ordinals.com/content/", + ), + ( + "alpha.ordinals.net", + "/content/6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0", + "https://ordinals.com/content/", + ), + ( + "bravo.ordinals.net", + "/content/6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0", + "https://ordinals.com/content/", + ), + ( + "charlie.ordinals.net", + "/content/6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0", + "https://ordinals.com/content/", + ), + ( + "ordinals.com", + "/content/6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0", + "https://ordinals.com/content/", + ), +]; + +fn main() { + let mut failures = 0; + + for (host, path, needle) in SERVERS { + eprint!("GET {host}"); + + let response = get(format!("https://{host}{path}")).unwrap(); + + let mut fail = false; + + if !response.status().is_success() { + eprint!(" {}", response.status().to_string().red()); + fail = true; + } + + let headers = response.headers(); + + let content_security_policy = headers + .get("content-security-policy") + .map(|value| value.to_str().unwrap().to_string()) + .unwrap_or_default(); + + if !content_security_policy.contains(needle) { + fail = true; + } + + if fail { + eprintln!(" {}", "FAIL".red()); + failures += 1; + } else { + eprintln!(" {}", "PASS".green()); + } + } + + if failures > 0 { + process::exit(1); + } +} diff --git a/crates/ordinals/Cargo.toml b/crates/ordinals/Cargo.toml new file mode 100644 index 0000000000..bb13ec2729 --- /dev/null +++ b/crates/ordinals/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ordinals" +version = "0.0.4" +edition = "2021" +description = "Library for interoperating with ordinals and inscriptions" +homepage = "https://github.com/ordinals/ord" +repository = "https://github.com/ordinals/ord" +license = "CC0-1.0" +rust-version = "1.67" + +[dependencies] +bitcoin = { version = "0.30.1", features = ["rand"] } +derive_more = "0.99.17" +serde = { version = "1.0.137", features = ["derive"] } +thiserror = "1.0.56" + +[dev-dependencies] +serde_json = { version = "1.0.81", features = ["preserve_order"] } diff --git a/src/decimal_sat.rs b/crates/ordinals/src/decimal_sat.rs similarity index 92% rename from src/decimal_sat.rs rename to crates/ordinals/src/decimal_sat.rs index a47dd3af15..185a04dac4 100644 --- a/src/decimal_sat.rs +++ b/crates/ordinals/src/decimal_sat.rs @@ -1,9 +1,9 @@ use super::*; #[derive(PartialEq, Debug)] -pub(crate) struct DecimalSat { - height: Height, - offset: u64, +pub struct DecimalSat { + pub height: Height, + pub offset: u64, } impl From for DecimalSat { diff --git a/src/degree.rs b/crates/ordinals/src/degree.rs similarity index 91% rename from src/degree.rs rename to crates/ordinals/src/degree.rs index fb1d77157b..058e7a3176 100644 --- a/src/degree.rs +++ b/crates/ordinals/src/degree.rs @@ -1,11 +1,11 @@ use super::*; #[derive(PartialEq, Debug)] -pub(crate) struct Degree { - pub(crate) hour: u32, - pub(crate) minute: u32, - pub(crate) second: u32, - pub(crate) third: u64, +pub struct Degree { + pub hour: u32, + pub minute: u32, + pub second: u32, + pub third: u64, } impl Display for Degree { diff --git a/crates/ordinals/src/deserialize_from_str.rs b/crates/ordinals/src/deserialize_from_str.rs new file mode 100644 index 0000000000..175c640652 --- /dev/null +++ b/crates/ordinals/src/deserialize_from_str.rs @@ -0,0 +1,44 @@ +use super::*; + +pub struct DeserializeFromStr(pub T); + +impl<'de, T: FromStr> DeserializeFromStr +where + T::Err: Display, +{ + pub fn with(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(DeserializeFromStr::::deserialize(deserializer)?.0) + } +} + +impl<'de, T: FromStr> Deserialize<'de> for DeserializeFromStr +where + T::Err: Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self( + FromStr::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)?, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_from_str() { + assert_eq!( + serde_json::from_str::>("\"1\"") + .unwrap() + .0, + 1, + ); + } +} diff --git a/src/epoch.rs b/crates/ordinals/src/epoch.rs similarity index 93% rename from src/epoch.rs rename to crates/ordinals/src/epoch.rs index d8157897d8..43d0b872dc 100644 --- a/src/epoch.rs +++ b/crates/ordinals/src/epoch.rs @@ -1,10 +1,10 @@ use super::*; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Serialize, PartialOrd)] -pub(crate) struct Epoch(pub(crate) u32); +pub struct Epoch(pub u32); impl Epoch { - pub(crate) const STARTING_SATS: [Sat; 34] = [ + pub const STARTING_SATS: [Sat; 34] = [ Sat(0), Sat(1050000000000000), Sat(1575000000000000), @@ -40,9 +40,9 @@ impl Epoch { Sat(2099999997480000), Sat(Sat::SUPPLY), ]; - pub(crate) const FIRST_POST_SUBSIDY: Epoch = Self(33); + pub const FIRST_POST_SUBSIDY: Epoch = Self(33); - pub(crate) fn subsidy(self) -> u64 { + pub fn subsidy(self) -> u64 { if self < Self::FIRST_POST_SUBSIDY { (50 * COIN_VALUE) >> self.0 } else { @@ -50,13 +50,13 @@ impl Epoch { } } - pub(crate) fn starting_sat(self) -> Sat { + pub fn starting_sat(self) -> Sat { *Self::STARTING_SATS .get(usize::try_from(self.0).unwrap()) .unwrap_or_else(|| Self::STARTING_SATS.last().unwrap()) } - pub(crate) fn starting_height(self) -> Height { + pub fn starting_height(self) -> Height { Height(self.0 * SUBSIDY_HALVING_INTERVAL) } } @@ -225,7 +225,7 @@ mod tests { assert_eq!(Epoch::from(Sat(1)), 0); assert_eq!(Epoch::from(Epoch(1).starting_sat()), 1); assert_eq!(Epoch::from(Epoch(1).starting_sat() + 1), 1); - assert_eq!(Epoch::from(Sat(u64::max_value())), 33); + assert_eq!(Epoch::from(Sat(u64::MAX)), 33); } #[test] @@ -237,6 +237,6 @@ mod tests { #[test] fn first_post_subsidy() { assert_eq!(Epoch::FIRST_POST_SUBSIDY.subsidy(), 0); - assert!((Epoch(Epoch::FIRST_POST_SUBSIDY.0 - 1)).subsidy() > 0); + assert!(Epoch(Epoch::FIRST_POST_SUBSIDY.0 - 1).subsidy() > 0); } } diff --git a/src/height.rs b/crates/ordinals/src/height.rs similarity index 91% rename from src/height.rs rename to crates/ordinals/src/height.rs index e96a66f8f4..0aaa38921e 100644 --- a/src/height.rs +++ b/crates/ordinals/src/height.rs @@ -1,25 +1,25 @@ use super::*; #[derive(Copy, Clone, Debug, Display, FromStr, Ord, Eq, Serialize, PartialEq, PartialOrd)] -pub(crate) struct Height(pub(crate) u32); +pub struct Height(pub u32); impl Height { - pub(crate) fn n(self) -> u32 { + pub fn n(self) -> u32 { self.0 } - pub(crate) fn subsidy(self) -> u64 { + pub fn subsidy(self) -> u64 { Epoch::from(self).subsidy() } - pub(crate) fn starting_sat(self) -> Sat { + pub fn starting_sat(self) -> Sat { let epoch = Epoch::from(self); let epoch_starting_sat = epoch.starting_sat(); let epoch_starting_height = epoch.starting_height(); epoch_starting_sat + u64::from(self.n() - epoch_starting_height.n()) * epoch.subsidy() } - pub(crate) fn period_offset(self) -> u32 { + pub fn period_offset(self) -> u32 { self.0 % DIFFCHANGE_INTERVAL } } @@ -106,7 +106,7 @@ mod tests { u64::from(SUBSIDY_HALVING_INTERVAL) * 5000000000 + 2500000000 ); assert_eq!( - Height(u32::max_value()).starting_sat(), + Height(u32::MAX).starting_sat(), *Epoch::STARTING_SATS.last().unwrap() ); } diff --git a/crates/ordinals/src/lib.rs b/crates/ordinals/src/lib.rs new file mode 100644 index 0000000000..5f178ade34 --- /dev/null +++ b/crates/ordinals/src/lib.rs @@ -0,0 +1,39 @@ +//! Types for interoperating with ordinals and inscriptions. + +use { + bitcoin::constants::{COIN_VALUE, DIFFCHANGE_INTERVAL, SUBSIDY_HALVING_INTERVAL}, + bitcoin::{ + consensus::{Decodable, Encodable}, + OutPoint, + }, + derive_more::{Display, FromStr}, + serde::{Deserialize, Deserializer, Serialize, Serializer}, + std::{ + cmp, + fmt::{self, Display, Formatter}, + io, + num::ParseIntError, + ops::{Add, AddAssign, Sub}, + str::FromStr, + }, + thiserror::Error, +}; + +pub const CYCLE_EPOCHS: u32 = 6; + +pub use { + decimal_sat::DecimalSat, degree::Degree, epoch::Epoch, height::Height, rarity::Rarity, sat::Sat, + sat_point::SatPoint, +}; + +#[doc(hidden)] +pub use self::deserialize_from_str::DeserializeFromStr; + +mod decimal_sat; +mod degree; +mod deserialize_from_str; +mod epoch; +mod height; +mod rarity; +mod sat; +mod sat_point; diff --git a/src/rarity.rs b/crates/ordinals/src/rarity.rs similarity index 93% rename from src/rarity.rs rename to crates/ordinals/src/rarity.rs index 65f1eb788f..eb98e15dc4 100644 --- a/src/rarity.rs +++ b/crates/ordinals/src/rarity.rs @@ -33,7 +33,7 @@ impl TryFrom for Rarity { } impl Display for Rarity { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!( f, "{}", @@ -75,7 +75,7 @@ impl From for Rarity { } impl FromStr for Rarity { - type Err = Error; + type Err = String; fn from_str(s: &str) -> Result { match s { @@ -85,7 +85,7 @@ impl FromStr for Rarity { "epic" => Ok(Self::Epic), "legendary" => Ok(Self::Legendary), "mythic" => Ok(Self::Mythic), - _ => Err(anyhow!("invalid rarity: {s}")), + _ => Err(format!("invalid rarity `{s}`")), } } } @@ -104,7 +104,7 @@ impl<'de> Deserialize<'de> for Rarity { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } @@ -172,13 +172,6 @@ mod tests { case("mythic", Rarity::Mythic); } - #[test] - fn from_str_err() { - "abc".parse::().unwrap_err(); - - "".parse::().unwrap_err(); - } - #[test] fn conversions_with_u8() { for &expected in &[ @@ -196,4 +189,9 @@ mod tests { assert_eq!(Rarity::try_from(6), Err(6)); } + + #[test] + fn error() { + assert_eq!("foo".parse::().unwrap_err(), "invalid rarity `foo`"); + } } diff --git a/src/sat.rs b/crates/ordinals/src/sat.rs similarity index 75% rename from src/sat.rs rename to crates/ordinals/src/sat.rs index 1d736b7210..1665d2cd22 100644 --- a/src/sat.rs +++ b/crates/ordinals/src/sat.rs @@ -1,75 +1,75 @@ -use super::*; +use {super::*, std::num::ParseFloatError}; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Ord, PartialOrd, Deserialize, Serialize)] #[serde(transparent)] pub struct Sat(pub u64); impl Sat { - pub(crate) const LAST: Self = Self(Self::SUPPLY - 1); - pub(crate) const SUPPLY: u64 = 2099999997690000; + pub const LAST: Self = Self(Self::SUPPLY - 1); + pub const SUPPLY: u64 = 2099999997690000; - pub(crate) fn n(self) -> u64 { + pub fn n(self) -> u64 { self.0 } - pub(crate) fn degree(self) -> Degree { + pub fn degree(self) -> Degree { self.into() } - pub(crate) fn height(self) -> Height { + pub fn height(self) -> Height { self.epoch().starting_height() + u32::try_from(self.epoch_position() / self.epoch().subsidy()).unwrap() } - pub(crate) fn cycle(self) -> u32 { + pub fn cycle(self) -> u32 { Epoch::from(self).0 / CYCLE_EPOCHS } - pub(crate) fn nineball(self) -> bool { + pub fn nineball(self) -> bool { self.n() >= 50 * COIN_VALUE * 9 && self.n() < 50 * COIN_VALUE * 10 } - pub(crate) fn percentile(self) -> String { + pub fn percentile(self) -> String { format!("{}%", (self.0 as f64 / Self::LAST.0 as f64) * 100.0) } - pub(crate) fn epoch(self) -> Epoch { + pub fn epoch(self) -> Epoch { self.into() } - pub(crate) fn period(self) -> u32 { + pub fn period(self) -> u32 { self.height().n() / DIFFCHANGE_INTERVAL } - pub(crate) fn third(self) -> u64 { + pub fn third(self) -> u64 { self.epoch_position() % self.epoch().subsidy() } - pub(crate) fn epoch_position(self) -> u64 { + pub fn epoch_position(self) -> u64 { self.0 - self.epoch().starting_sat().0 } - pub(crate) fn decimal(self) -> DecimalSat { + pub fn decimal(self) -> DecimalSat { self.into() } - pub(crate) fn rarity(self) -> Rarity { + pub fn rarity(self) -> Rarity { self.into() } /// `Sat::rarity` is expensive and is called frequently when indexing. /// Sat::is_common only checks if self is `Rarity::Common` but is /// much faster. - pub(crate) fn common(self) -> bool { + pub fn common(self) -> bool { let epoch = self.epoch(); (self.0 - epoch.starting_sat().0) % epoch.subsidy() != 0 } - pub(crate) fn coin(self) -> bool { + pub fn coin(self) -> bool { self.n() % COIN_VALUE == 0 } - pub(crate) fn name(self) -> String { + pub fn name(self) -> String { let mut x = Self::SUPPLY - self.0; let mut name = String::new(); while x > 0 { @@ -84,42 +84,53 @@ impl Sat { name.chars().rev().collect() } - fn from_name(s: &str) -> Result { + fn from_name(s: &str) -> Result { let mut x = 0; for c in s.chars() { match c { 'a'..='z' => { x = x * 26 + c as u64 - 'a' as u64 + 1; if x > Self::SUPPLY { - bail!("sat name out of range"); + return Err(ErrorKind::NameRange.error(s)); } } - _ => bail!("invalid character in sat name: {c}"), + _ => return Err(ErrorKind::NameCharacter.error(s)), } } Ok(Sat(Self::SUPPLY - x)) } - fn from_degree(degree: &str) -> Result { + fn from_degree(degree: &str) -> Result { let (cycle_number, rest) = degree .split_once('°') - .ok_or_else(|| anyhow!("missing degree symbol"))?; - let cycle_number = cycle_number.parse::()?; + .ok_or_else(|| ErrorKind::MissingDegree.error(degree))?; + + let cycle_number = cycle_number + .parse::() + .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; let (epoch_offset, rest) = rest .split_once('′') - .ok_or_else(|| anyhow!("missing minute symbol"))?; - let epoch_offset = epoch_offset.parse::()?; + .ok_or_else(|| ErrorKind::MissingMinute.error(degree))?; + + let epoch_offset = epoch_offset + .parse::() + .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; + if epoch_offset >= SUBSIDY_HALVING_INTERVAL { - bail!("invalid epoch offset"); + return Err(ErrorKind::EpochOffset.error(degree)); } let (period_offset, rest) = rest .split_once('″') - .ok_or_else(|| anyhow!("missing second symbol"))?; - let period_offset = period_offset.parse::()?; + .ok_or_else(|| ErrorKind::MissingSecond.error(degree))?; + + let period_offset = period_offset + .parse::() + .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?; + if period_offset >= DIFFCHANGE_INTERVAL { - bail!("invalid period offset"); + return Err(ErrorKind::PeriodOffset.error(degree)); } let cycle_start_epoch = cycle_number * CYCLE_EPOCHS; @@ -131,7 +142,7 @@ impl Sat { let relationship = period_offset + SUBSIDY_HALVING_INTERVAL * CYCLE_EPOCHS - epoch_offset; if relationship % HALVING_INCREMENT != 0 { - bail!("relationship between epoch offset and period offset must be multiple of 336"); + return Err(ErrorKind::EpochPeriodMismatch.error(degree)); } let epochs_since_cycle_start = relationship % DIFFCHANGE_INTERVAL / HALVING_INCREMENT; @@ -141,44 +152,61 @@ impl Sat { let height = Height(epoch * SUBSIDY_HALVING_INTERVAL + epoch_offset); let (block_offset, rest) = match rest.split_once('‴') { - Some((block_offset, rest)) => (block_offset.parse::()?, rest), + Some((block_offset, rest)) => ( + block_offset + .parse::() + .map_err(|source| ErrorKind::ParseInt { source }.error(degree))?, + rest, + ), None => (0, rest), }; if !rest.is_empty() { - bail!("trailing characters"); + return Err(ErrorKind::TrailingCharacters.error(degree)); } if block_offset >= height.subsidy() { - bail!("invalid block offset"); + return Err(ErrorKind::BlockOffset.error(degree)); } Ok(height.starting_sat() + block_offset) } - fn from_decimal(decimal: &str) -> Result { + fn from_decimal(decimal: &str) -> Result { let (height, offset) = decimal .split_once('.') - .ok_or_else(|| anyhow!("missing period"))?; - let height = Height(height.parse()?); - let offset = offset.parse::()?; + .ok_or_else(|| ErrorKind::MissingPeriod.error(decimal))?; + + let height = Height( + height + .parse() + .map_err(|source| ErrorKind::ParseInt { source }.error(decimal))?, + ); + + let offset = offset + .parse::() + .map_err(|source| ErrorKind::ParseInt { source }.error(decimal))?; if offset >= height.subsidy() { - bail!("invalid block offset"); + return Err(ErrorKind::BlockOffset.error(decimal)); } Ok(height.starting_sat() + offset) } - fn from_percentile(percentile: &str) -> Result { + fn from_percentile(percentile: &str) -> Result { if !percentile.ends_with('%') { - bail!("invalid percentile: {}", percentile); + return Err(ErrorKind::Percentile.error(percentile)); } - let percentile = percentile[..percentile.len() - 1].parse::()?; + let percentile_string = percentile; + + let percentile = percentile[..percentile.len() - 1] + .parse::() + .map_err(|source| ErrorKind::ParseFloat { source }.error(percentile))?; if percentile < 0.0 { - bail!("invalid percentile: {}", percentile); + return Err(ErrorKind::Percentile.error(percentile_string)); } let last = Sat::LAST.n() as f64; @@ -186,7 +214,7 @@ impl Sat { let n = (percentile / 100.0 * last).round(); if n > last { - bail!("invalid percentile: {}", percentile); + return Err(ErrorKind::Percentile.error(percentile_string)); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] @@ -194,6 +222,71 @@ impl Sat { } } +#[derive(Debug, Error)] +pub struct Error { + input: String, + kind: ErrorKind, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "failed to parse sat `{}`: {}", self.input, self.kind) + } +} + +#[derive(Debug, Error)] +pub enum ErrorKind { + IntegerRange, + NameRange, + NameCharacter, + Percentile, + BlockOffset, + MissingPeriod, + TrailingCharacters, + MissingDegree, + MissingMinute, + MissingSecond, + PeriodOffset, + EpochOffset, + EpochPeriodMismatch, + ParseInt { source: ParseIntError }, + ParseFloat { source: ParseFloatError }, +} + +impl ErrorKind { + fn error(self, input: &str) -> Error { + Error { + input: input.to_string(), + kind: self, + } + } +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::IntegerRange => write!(f, "invalid integer range"), + Self::NameRange => write!(f, "invalid name range"), + Self::NameCharacter => write!(f, "invalid character in name"), + Self::Percentile => write!(f, "invalid percentile"), + Self::BlockOffset => write!(f, "invalid block offset"), + Self::MissingPeriod => write!(f, "missing period"), + Self::TrailingCharacters => write!(f, "trailing character"), + Self::MissingDegree => write!(f, "missing degree symbol"), + Self::MissingMinute => write!(f, "missing minute symbol"), + Self::MissingSecond => write!(f, "missing second symbol"), + Self::PeriodOffset => write!(f, "invalid period offset"), + Self::EpochOffset => write!(f, "invalid epoch offset"), + Self::EpochPeriodMismatch => write!( + f, + "relationship between epoch offset and period offset must be multiple of 336" + ), + Self::ParseInt { source } => write!(f, "invalid integer: {source}"), + Self::ParseFloat { source } => write!(f, "invalid float: {source}"), + } + } +} + impl PartialEq for Sat { fn eq(&self, other: &u64) -> bool { self.0 == *other @@ -223,7 +316,7 @@ impl AddAssign for Sat { impl FromStr for Sat { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { if s.chars().any(|c| c.is_ascii_lowercase()) { Self::from_name(s) } else if s.contains('°') { @@ -233,9 +326,12 @@ impl FromStr for Sat { } else if s.contains('.') { Self::from_decimal(s) } else { - let sat = Self(s.parse()?); + let sat = Self( + s.parse() + .map_err(|source| ErrorKind::ParseInt { source }.error(s))?, + ); if sat > Self::LAST { - Err(anyhow!("invalid sat")) + Err(ErrorKind::IntegerRange.error(s)) } else { Ok(sat) } @@ -656,4 +752,16 @@ mod tests { ); } } + + #[test] + fn error_display() { + assert_eq!( + Error { + input: "foo".into(), + kind: ErrorKind::Percentile + } + .to_string(), + "failed to parse sat `foo`: invalid percentile", + ); + } } diff --git a/src/sat_point.rs b/crates/ordinals/src/sat_point.rs similarity index 55% rename from src/sat_point.rs rename to crates/ordinals/src/sat_point.rs index 75c034cf82..2398a14edc 100644 --- a/src/sat_point.rs +++ b/crates/ordinals/src/sat_point.rs @@ -1,6 +1,15 @@ -use super::*; - -#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, Default)] +use {super::*, bitcoin::transaction::ParseOutPointError}; + +/// A satpoint identifies the location of a sat in an output. +/// +/// The string representation of a satpoint consists of that of an outpoint, +/// which identifies and output, followed by `:OFFSET`. For example, the string +/// representation of the first sat of the genesis block coinbase output is +/// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:0`, +/// that of the second sat of the genesis block coinbase output is +/// `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0:1`, and +/// so on and so on. +#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, Default, Hash)] pub struct SatPoint { pub outpoint: OutPoint, pub offset: u64, @@ -44,7 +53,7 @@ impl<'de> Deserialize<'de> for SatPoint { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } @@ -52,21 +61,61 @@ impl FromStr for SatPoint { type Err = Error; fn from_str(s: &str) -> Result { - let (outpoint, offset) = s - .rsplit_once(':') - .ok_or_else(|| anyhow!("invalid satpoint: {s}"))?; + let (outpoint, offset) = s.rsplit_once(':').ok_or_else(|| Error::Colon(s.into()))?; Ok(SatPoint { - outpoint: outpoint.parse()?, - offset: offset.parse()?, + outpoint: outpoint + .parse::() + .map_err(|err| Error::Outpoint { + outpoint: outpoint.into(), + err, + })?, + offset: offset.parse::().map_err(|err| Error::Offset { + offset: offset.into(), + err, + })?, }) } } +#[derive(Debug, Error)] +pub enum Error { + #[error("satpoint `{0}` missing colon")] + Colon(String), + #[error("satpoint offset `{offset}` invalid: {err}")] + Offset { offset: String, err: ParseIntError }, + #[error("satpoint outpoint `{outpoint}` invalid: {err}")] + Outpoint { + outpoint: String, + err: ParseOutPointError, + }, +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn error() { + assert_eq!( + "foo".parse::().unwrap_err().to_string(), + "satpoint `foo` missing colon" + ); + + assert_eq!( + "foo:bar".parse::().unwrap_err().to_string(), + "satpoint outpoint `foo` invalid: OutPoint not in : format" + ); + + assert_eq!( + "1111111111111111111111111111111111111111111111111111111111111111:1:bar" + .parse::() + .unwrap_err() + .to_string(), + "satpoint offset `bar` invalid: invalid digit found in string" + ); + } + #[test] fn from_str_ok() { assert_eq!( diff --git a/test-bitcoincore-rpc/Cargo.toml b/crates/test-bitcoincore-rpc/Cargo.toml similarity index 92% rename from test-bitcoincore-rpc/Cargo.toml rename to crates/test-bitcoincore-rpc/Cargo.toml index d20b7d4aa7..c66f81ef2b 100644 --- a/test-bitcoincore-rpc/Cargo.toml +++ b/crates/test-bitcoincore-rpc/Cargo.toml @@ -9,11 +9,12 @@ repository = "https://github.com/ordinals/ord" [dependencies] bitcoin = { version = "0.30.0", features = ["serde", "rand"] } +base64 = "0.21.0" hex = "0.4.3" jsonrpc-core = "18.0.0" jsonrpc-derive = "18.0.0" jsonrpc-http-server = "18.0.0" -ord-bitcoincore-rpc = "0.17.1" +ord-bitcoincore-rpc = "0.17.2" reqwest = { version = "0.11.10", features = ["blocking"] } serde = { version = "1.0.137", features = ["derive"] } serde_json = { version = "1.0.81" } diff --git a/test-bitcoincore-rpc/src/api.rs b/crates/test-bitcoincore-rpc/src/api.rs similarity index 83% rename from test-bitcoincore-rpc/src/api.rs rename to crates/test-bitcoincore-rpc/src/api.rs index ecf763ef1b..8ca8f32a37 100644 --- a/test-bitcoincore-rpc/src/api.rs +++ b/crates/test-bitcoincore-rpc/src/api.rs @@ -22,12 +22,23 @@ pub trait Api { verbose: bool, ) -> Result; + #[rpc(name = "getblockstats")] + fn get_block_stats(&self, height: usize) -> Result; + #[rpc(name = "getblock")] fn get_block(&self, blockhash: BlockHash, verbosity: u64) -> Result; #[rpc(name = "getblockcount")] fn get_block_count(&self) -> Result; + #[rpc(name = "gettxout")] + fn get_tx_out( + &self, + txid: Txid, + vout: u32, + include_mempool: Option, + ) -> Result, jsonrpc_core::Error>; + #[rpc(name = "getwalletinfo")] fn get_wallet_info(&self) -> Result; @@ -155,11 +166,33 @@ pub trait Api { ) -> Result; #[rpc(name = "listdescriptors")] - fn list_descriptors(&self) -> Result; + fn list_descriptors( + &self, + _with_private_keys: Option, + ) -> Result; #[rpc(name = "loadwallet")] fn load_wallet(&self, wallet: String) -> Result; #[rpc(name = "listwallets")] fn list_wallets(&self) -> Result, jsonrpc_core::Error>; + + #[rpc(name = "listwalletdir")] + fn list_wallet_dir(&self) -> Result; + + #[rpc(name = "walletprocesspsbt")] + fn wallet_process_psbt( + &self, + psbt: String, + sign: Option, + sighash_type: Option<()>, + bip32derivs: Option, + ) -> Result; + + #[rpc(name = "finalizepsbt")] + fn finalize_psbt( + &self, + psbt: String, + extract: Option, + ) -> Result; } diff --git a/test-bitcoincore-rpc/src/lib.rs b/crates/test-bitcoincore-rpc/src/lib.rs similarity index 87% rename from test-bitcoincore-rpc/src/lib.rs rename to crates/test-bitcoincore-rpc/src/lib.rs index 4263313822..e13a1e0c81 100644 --- a/test-bitcoincore-rpc/src/lib.rs +++ b/crates/test-bitcoincore-rpc/src/lib.rs @@ -17,13 +17,15 @@ use { Wtxid, }, bitcoincore_rpc::json::{ - Bip125Replaceable, CreateRawTransactionInput, Descriptor, EstimateMode, GetBalancesResult, - GetBalancesResultEntry, GetBlockHeaderResult, GetBlockchainInfoResult, GetDescriptorInfoResult, - GetNetworkInfoResult, GetRawTransactionResult, GetTransactionResult, - GetTransactionResultDetail, GetTransactionResultDetailCategory, GetWalletInfoResult, - ImportDescriptors, ImportMultiResult, ListDescriptorsResult, ListTransactionResult, - ListUnspentResultEntry, LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, - Timestamp, WalletTxInfo, + Bip125Replaceable, CreateRawTransactionInput, Descriptor, EstimateMode, FeeRatePercentiles, + FinalizePsbtResult, GetBalancesResult, GetBalancesResultEntry, GetBlockHeaderResult, + GetBlockStatsResult, GetBlockchainInfoResult, GetDescriptorInfoResult, GetNetworkInfoResult, + GetRawTransactionResult, GetRawTransactionResultVout, GetRawTransactionResultVoutScriptPubKey, + GetTransactionResult, GetTransactionResultDetail, GetTransactionResultDetailCategory, + GetTxOutResult, GetWalletInfoResult, ImportDescriptors, ImportMultiResult, + ListDescriptorsResult, ListTransactionResult, ListUnspentResultEntry, ListWalletDirItem, + ListWalletDirResult, LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, + Timestamp, WalletProcessPsbtResult, WalletTxInfo, }, jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, @@ -100,7 +102,7 @@ impl Builder { Ok(_) => break, Err(err) => { if i == 400 { - panic!("Server failed to start: {err}"); + panic!("mock bitcoind server failed to start: {err}"); } } } @@ -135,16 +137,9 @@ pub struct TransactionTemplate<'a> { pub outputs: usize, } -#[derive(Clone, Debug, PartialEq)] -pub struct Sent { - pub amount: f64, - pub address: Address, - pub locked: Vec, -} - #[derive(Serialize, Deserialize)] pub struct JsonOutPoint { - txid: bitcoin::Txid, + txid: Txid, vout: u32, } @@ -258,10 +253,6 @@ impl Handle { self.state().descriptors.push(desc); } - pub fn sent(&self) -> Vec { - self.state().sent.clone() - } - pub fn lock(&self, output: OutPoint) { self.state().locked.insert(output); } @@ -287,6 +278,10 @@ impl Handle { pub fn cookie_file(&self) -> PathBuf { self.tempdir.path().join(".cookie") } + + pub fn get_locked(&self) -> BTreeSet { + self.state().get_locked() + } } impl Drop for Handle { diff --git a/test-bitcoincore-rpc/src/server.rs b/crates/test-bitcoincore-rpc/src/server.rs similarity index 80% rename from test-bitcoincore-rpc/src/server.rs rename to crates/test-bitcoincore-rpc/src/server.rs index f678329ddc..23a7af2c50 100644 --- a/test-bitcoincore-rpc/src/server.rs +++ b/crates/test-bitcoincore-rpc/src/server.rs @@ -1,7 +1,9 @@ use { super::*, + base64::Engine, bitcoin::{ consensus::Decodable, + psbt::Psbt, secp256k1::{rand, KeyPair, Secp256k1, XOnlyPublicKey}, Witness, }, @@ -117,7 +119,10 @@ impl Api for Server { Ok( serde_json::to_value(GetBlockHeaderResult { bits: String::new(), - chainwork: Vec::new(), + chainwork: hex::decode( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), confirmations: 0, difficulty: 0.0, hash: block_hash, @@ -142,6 +147,50 @@ impl Api for Server { } } + fn get_block_stats(&self, height: usize) -> Result { + let Some(block_hash) = self.state().hashes.get(height).cloned() else { + return Err(Self::not_found()); + }; + + Ok(GetBlockStatsResult { + avg_fee: Amount::ZERO, + avg_fee_rate: Amount::ZERO, + avg_tx_size: 0, + block_hash, + fee_rate_percentiles: FeeRatePercentiles { + fr_10th: Amount::ZERO, + fr_25th: Amount::ZERO, + fr_50th: Amount::ZERO, + fr_75th: Amount::ZERO, + fr_90th: Amount::ZERO, + }, + height: height.try_into().unwrap(), + ins: 0, + max_fee: Amount::ZERO, + max_fee_rate: Amount::ZERO, + max_tx_size: 0, + median_fee: Amount::ZERO, + median_time: 0, + median_tx_size: 0, + min_fee: Amount::ZERO, + min_fee_rate: Amount::ZERO, + min_tx_size: 0, + outs: 0, + subsidy: Amount::ZERO, + sw_total_size: 0, + sw_total_weight: 0, + sw_txs: 0, + time: 0, + total_out: Amount::ZERO, + total_size: 0, + total_weight: 0, + total_fee: Amount::ZERO, + txs: 0, + utxo_increase: 0, + utxo_size_inc: 0, + }) + } + fn get_block( &self, block_hash: BlockHash, @@ -166,6 +215,34 @@ impl Api for Server { ) } + fn get_tx_out( + &self, + txid: Txid, + vout: u32, + _include_mempool: Option, + ) -> Result, jsonrpc_core::Error> { + Ok( + self + .state() + .utxos + .get(&OutPoint { txid, vout }) + .map(|&value| GetTxOutResult { + bestblock: BlockHash::all_zeros(), + confirmations: 0, + value, + script_pub_key: GetRawTransactionResultVoutScriptPubKey { + asm: String::new(), + hex: Vec::new(), + req_sigs: None, + type_: None, + addresses: Vec::new(), + address: None, + }, + coinbase: false, + }), + ) + } + fn get_wallet_info(&self) -> Result { if let Some(wallet_name) = self.state().loaded_wallets.first().cloned() { Ok(GetWalletInfoResult { @@ -360,6 +437,7 @@ impl Api for Server { fn send_raw_transaction(&self, tx: String) -> Result { let tx: Transaction = deserialize(&hex::decode(tx).unwrap()).unwrap(); + self.state.lock().unwrap().mempool.push(tx.clone()); Ok(tx.txid().to_string()) @@ -433,12 +511,6 @@ impl Api for Server { state.mempool.push(transaction); - state.sent.push(Sent { - address: address.assume_checked(), - amount, - locked, - }); - Ok(txid) } @@ -484,7 +556,7 @@ impl Api for Server { assert_eq!(blockhash, None, "Blockhash param is unsupported"); if verbose.unwrap_or(false) { match self.state().transactions.get(&txid) { - Some(_) => Ok( + Some(transaction) => Ok( serde_json::to_value(GetRawTransactionResult { in_active_chain: Some(true), hex: Vec::new(), @@ -495,7 +567,23 @@ impl Api for Server { version: 2, locktime: 0, vin: Vec::new(), - vout: Vec::new(), + vout: transaction + .output + .iter() + .enumerate() + .map(|(n, output)| GetRawTransactionResultVout { + n: n.try_into().unwrap(), + value: Amount::from_sat(output.value), + script_pub_key: GetRawTransactionResultVoutScriptPubKey { + asm: String::new(), + hex: Vec::new(), + req_sigs: None, + type_: None, + addresses: Vec::new(), + address: None, + }, + }) + .collect(), blockhash: None, confirmations: Some(1), time: None, @@ -610,7 +698,7 @@ impl Api for Server { &self, _label: Option, _address_type: Option, - ) -> Result { + ) -> Result { let secp256k1 = Secp256k1::new(); let key_pair = KeyPair::new(&secp256k1, &mut rand::thread_rng()); let (public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair); @@ -688,7 +776,10 @@ impl Api for Server { Ok(true) } - fn list_descriptors(&self) -> Result { + fn list_descriptors( + &self, + _with_private_keys: Option, + ) -> Result { Ok(ListDescriptorsResult { wallet_name: "ord".into(), descriptors: self @@ -729,4 +820,83 @@ impl Api for Server { .collect::>(), ) } + + fn list_wallet_dir(&self) -> Result { + Ok(ListWalletDirResult { + wallets: self + .list_wallets()? + .into_iter() + .map(|name| ListWalletDirItem { name }) + .collect(), + }) + } + + fn wallet_process_psbt( + &self, + psbt: String, + sign: Option, + sighash_type: Option<()>, + bip32derivs: Option, + ) -> Result { + assert!(sighash_type.is_none()); + assert!(bip32derivs.is_none()); + + let mut psbt = Psbt::deserialize( + &base64::engine::general_purpose::STANDARD + .decode(psbt) + .unwrap(), + ) + .unwrap(); + + for (i, txin) in psbt.unsigned_tx.input.iter().enumerate() { + psbt.inputs[i].witness_utxo = Some( + self + .state() + .transactions + .get(&txin.previous_output.txid) + .unwrap() + .output[txin.previous_output.vout as usize] + .clone(), + ); + } + + if let Some(sign) = sign { + if sign { + for input in psbt.inputs.iter_mut() { + input.final_script_witness = Some(Witness::from_slice(&[&[0; 64]])); + } + } + } + + Ok(WalletProcessPsbtResult { + psbt: base64::engine::general_purpose::STANDARD.encode(psbt.serialize()), + complete: false, + }) + } + + fn finalize_psbt( + &self, + psbt: String, + _extract: Option, + ) -> Result { + let mut transaction = Psbt::deserialize( + &base64::engine::general_purpose::STANDARD + .decode(psbt) + .unwrap(), + ) + .unwrap() + .unsigned_tx; + + for input in &mut transaction.input { + if input.witness.is_empty() { + input.witness = Witness::from_slice(&[&[0; 64]]); + } + } + + Ok(FinalizePsbtResult { + psbt: None, + hex: Some(serialize(&transaction)), + complete: true, + }) + } } diff --git a/test-bitcoincore-rpc/src/state.rs b/crates/test-bitcoincore-rpc/src/state.rs similarity index 98% rename from test-bitcoincore-rpc/src/state.rs rename to crates/test-bitcoincore-rpc/src/state.rs index fc962056d7..72bf46bd7c 100644 --- a/test-bitcoincore-rpc/src/state.rs +++ b/crates/test-bitcoincore-rpc/src/state.rs @@ -1,5 +1,6 @@ use super::*; +#[derive(Debug)] pub(crate) struct State { pub(crate) blocks: BTreeMap, pub(crate) change_addresses: Vec
          , @@ -11,7 +12,6 @@ pub(crate) struct State { pub(crate) mempool: Vec, pub(crate) network: Network, pub(crate) nonce: u32, - pub(crate) sent: Vec, pub(crate) transactions: BTreeMap, pub(crate) utxos: BTreeMap, pub(crate) version: usize, @@ -38,7 +38,6 @@ impl State { mempool: Vec::new(), network, nonce: 0, - sent: Vec::new(), transactions: BTreeMap::new(), utxos: BTreeMap::new(), version, @@ -207,4 +206,8 @@ impl State { 0 } + + pub(crate) fn get_locked(&self) -> BTreeSet { + self.locked.clone() + } } diff --git a/crates/update-contributors/Cargo.toml b/crates/update-contributors/Cargo.toml new file mode 100644 index 0000000000..b9bd76920e --- /dev/null +++ b/crates/update-contributors/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "update-contributors" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +regex = "1.5.4" diff --git a/crates/update-contributors/src/main.rs b/crates/update-contributors/src/main.rs new file mode 100644 index 0000000000..6f6bd8a5eb --- /dev/null +++ b/crates/update-contributors/src/main.rs @@ -0,0 +1,40 @@ +use { + regex::{Captures, Regex}, + std::{fs, process::Command, str}, +}; + +fn author(pr: u64) -> String { + eprintln!("#{pr}"); + let output = Command::new("sh") + .args([ + "-c", + &format!("gh pr view {pr} --json author | jq -r .author.login"), + ]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + + str::from_utf8(&output.stdout).unwrap().trim().to_owned() +} + +fn main() { + fs::write( + "CHANGELOG.md", + &*Regex::new(r"\(#(\d+)( by @[a-z]+)?\)") + .unwrap() + .replace_all( + &fs::read_to_string("CHANGELOG.md").unwrap(), + |captures: &Captures| { + let pr = captures[1].parse().unwrap(); + let contributor = author(pr); + format!("([#{pr}](https://github.com/ordinals/ord/pull/{pr}) by [{contributor}](https://github.com/{contributor}))") + }, + ), + ) + .unwrap(); +} diff --git a/deploy/ord.service b/deploy/ord.service index c795ef06d6..d43f14b810 100644 --- a/deploy/ord.service +++ b/deploy/ord.service @@ -19,7 +19,8 @@ ExecStart=/usr/local/bin/ord \ --acme-contact mailto:casey@rodarmor.com \ --csp-origin https://${CSP_ORIGIN} \ --http \ - --https + --https \ + --disable-json-api Group=ord LimitNOFILE=65536 MemoryDenyWriteExecute=true diff --git a/deploy/setup b/deploy/setup index b32ac5d891..0ef2216abe 100755 --- a/deploy/setup +++ b/deploy/setup @@ -10,6 +10,8 @@ BRANCH=$3 COMMIT=$4 REVISION="ord-$BRANCH-$COMMIT" +export DEBIAN_FRONTEND=noninteractive + touch ~/.hushlogin hostnamectl set-hostname $DOMAIN @@ -25,6 +27,8 @@ apt-get install --yes \ ufw \ vim +apt-get remove --yes --auto-remove + ufw default allow outgoing ufw default deny incoming diff --git a/docs/po/ar.po b/docs/po/ar.po index 96dcc0c55f..b67f05bc01 100644 --- a/docs/po/ar.po +++ b/docs/po/ar.po @@ -127,12 +127,12 @@ msgid "For more details on ordinal theory, see the [overview](overview.md)." msgstr "للحصول على مزيد من التفاصيل حول نظرية ordinal ، راجع [overview](overview.md)." #: src\introduction.md:29 -msgid "For more details on inscriptions, see [inscriptions](inscriptions.md)." -msgstr "لمزيد من التفاصيل حول الإنسكريبشين، راجع [inscriptions](inscriptions.md)." +msgid "For more details on inscriptions, see [inscriptions](wallet.md)." +msgstr "لمزيد من التفاصيل حول الإنسكريبشين، راجع [inscriptions](wallet.md)." #: src\introduction.md:31 -msgid "When you're ready to get your hands dirty, a good place to start is with [inscriptions](guides/inscriptions.md), a curious species of digital artifact enabled by ordinal theory." -msgstr "عندما تكون جاهزًا لبدء العمل، مكانًا جيدًا للبداية هو [inscriptions](guides/inscriptions.md)، وهي نوع فضولي من الأصول الرقمية digital artifact الممكّنة بواسطة نظرية أوردينالس." +msgid "When you're ready to get your hands dirty, a good place to start is with [inscriptions](guides/wallet.md), a curious species of digital artifact enabled by ordinal theory." +msgstr "عندما تكون جاهزًا لبدء العمل، مكانًا جيدًا للبداية هو [inscriptions](guides/wallet.md)، وهي نوع فضولي من الأصول الرقمية digital artifact الممكّنة بواسطة نظرية أوردينالس." #: src\introduction.md:35 msgid "Links" @@ -1591,239 +1591,239 @@ msgstr "أو بالنسبة المئوية، نسبة إمداد بيتكوين msgid "[100%](https://ordinals.com/search/100%)" msgstr "" -#: src\guides/inscriptions.md:1 +#: src\guides/wallet.md:1 msgid "Ordinal Inscription Guide" msgstr "دليل صناعه الأوردينال" -#: src\guides/inscriptions.md:4 +#: src\guides/wallet.md:4 msgid "Individual sats can be inscribed with arbitrary content, creating Bitcoin-native digital artifacts that can be held in a Bitcoin wallet and transferred using Bitcoin transactions. Inscriptions are as durable, immutable, secure, and decentralized as Bitcoin itself." msgstr "يمكن أن تُرسم العملات الفردية بمحتوى تعسفي، مما يُنشئ قطع فنية رقمية متناسبة مع بيتكوين يمكن الاحتفاظ بها في محفظة بيتكوين ونقلها باستخدام المعاملات المالية في بيتكوين. إن النقوش متينة وثابتة وآمنة ولامركزية تمامًا مثل بيتكوين نفسه." -#: src\guides/inscriptions.md:9 +#: src\guides/wallet.md:9 msgid "Working with inscriptions requires a Bitcoin full node, to give you a view of the current state of the Bitcoin blockchain, and a wallet that can create inscriptions and perform sat control when constructing transactions to send inscriptions to another wallet." msgstr "العمل مع الأنسكريبشين يتطلب وجود نود بيتكوين كاملة لمنحك نظرة على الحالة الحالية لسلسلة كتل بيتكوين، ومحفظة قادرة على إنشاء الأنسكريبشين وأداء التحكم في العملات عند بناء المعاملات لإرسالها إلى محفظة أخرى." -#: src\guides/inscriptions.md:14 +#: src\guides/wallet.md:14 msgid "Bitcoin Core provides both a Bitcoin full node and wallet. However, the Bitcoin Core wallet cannot create inscriptions and does not perform sat control." msgstr "توفر Bitcoin Core كل من نود بيتكوين كاملة ومحفظة. ومع ذلك، لا يمكن لمحفظة Bitcoin Core إنشاء النقوش ولا تؤدي إلى التحكم في العملات." -#: src\guides/inscriptions.md:17 +#: src\guides/wallet.md:17 msgid "This requires [`ord`](https://github.com/ordinals/ord), the ordinal utility. `ord` doesn't implement its own wallet, so `ord wallet` subcommands interact with Bitcoin Core wallets." msgstr "هذا يتطلب [`ord`](https://github.com/ordinals/ord), وهو أداة ترتيبية. أورد لا تنفذ محفظة خاصة بها، لذا تفاعل أوامر محفظة أورد مع محافظ Bitcoin Core." -#: src\guides/inscriptions.md:21 +#: src\guides/wallet.md:21 msgid "This guide covers:" msgstr "تشمل هذه الدليل الفقرات التالية:" -#: src\guides/inscriptions.md:23 src\guides/inscriptions.md:39 +#: src\guides/wallet.md:23 src\guides/wallet.md:39 msgid "Installing Bitcoin Core" msgstr "تثبيت Bitcoin Core" -#: src\guides/inscriptions.md:24 +#: src\guides/wallet.md:24 msgid "Syncing the Bitcoin blockchain" msgstr "مزامنة Bitcoin blockchain" -#: src\guides/inscriptions.md:25 +#: src\guides/wallet.md:25 msgid "Creating a Bitcoin Core wallet" msgstr " إنشاء محفظة Bitcoin Core" -#: src\guides/inscriptions.md:26 +#: src\guides/wallet.md:26 msgid "Using `ord wallet receive` to receive sats" msgstr "استخدام `ord wallet receive` لاستقبال العملات" -#: src\guides/inscriptions.md:27 +#: src\guides/wallet.md:27 msgid "Creating inscriptions with `ord wallet inscribe`" msgstr " إنشاء الأنسكريبشين باستخدام `ord wallet inscribe`" -#: src\guides/inscriptions.md:28 +#: src\guides/wallet.md:28 msgid "Sending inscriptions with `ord wallet send`" msgstr "إرسال الأنسكريبشين باستخدام `ord wallet send`" -#: src\guides/inscriptions.md:29 +#: src\guides/wallet.md:29 msgid "Receiving inscriptions with `ord wallet receive`" msgstr "استقبال الأنسكريبشين باستخدام `ord wallet receive`" -#: src\guides/inscriptions.md:31 +#: src\guides/wallet.md:31 msgid "Getting Help" msgstr "الحصول على المساعدة" -#: src\guides/inscriptions.md:34 +#: src\guides/wallet.md:34 msgid "If you get stuck, try asking for help on the [Ordinals Discord Server](https://discord.com/invite/87cjuz4FYg), or checking GitHub for relevant [issues](https://github.com/ordinals/ord/issues) and [discussions](https://github.com/ordinals/ord/discussions)." msgstr "إذا واجهتك مشكلة، جرب أن تطلب المساعدة على [الديسكورد](https://discord.com/invite/87cjuz4FYg) ، أو تفقد موقع[issues](https://github.com/ordinals/ord/issues) [discussions](https://github.com/ordinals/ord/discussions) GitHub للقضايا والمناقشات ذات الصلة." -#: src\guides/inscriptions.md:42 +#: src\guides/wallet.md:42 msgid "Bitcoin Core is available from [bitcoincore.org](https://bitcoincore.org/) on the [download page](https://bitcoincore.org/en/download/)." msgstr "يتوفر Bitcoin Core من موقع [bitcoincore.org](https://bitcoincore.org/) على [صفحه التحميل](https://bitcoincore.org/en/download/)." -#: src\guides/inscriptions.md:45 +#: src\guides/wallet.md:45 msgid "Making inscriptions requires Bitcoin Core 24 or newer." msgstr "إن إجراء الإنسكريبشين يتطلب Bitcoin Core 24 أو الإصدارات الأحدث." -#: src\guides/inscriptions.md:47 +#: src\guides/wallet.md:47 msgid "This guide does not cover installing Bitcoin Core in detail. Once Bitcoin Core is installed, you should be able to run `bitcoind -version` successfully from the command line." msgstr "لا يتضمن هذا الدليل تفاصيل تثبيت Bitcoin Core بالتفصيل. بمجرد تثبيت Bitcoin Core، يجب أن تتمكن من تشغيل bitcoind -version بنجاح من أوامر الجهاز." -#: src\guides/inscriptions.md:51 +#: src\guides/wallet.md:51 msgid "Configuring Bitcoin Core" msgstr "تهيئة Bitcoin Core" -#: src\guides/inscriptions.md:54 +#: src\guides/wallet.md:54 msgid "`ord` requires Bitcoin Core's transaction index." msgstr "تتطلب `ord` فهرس المعاملات الخاص بـ Bitcoin Core." -#: src\guides/inscriptions.md:56 +#: src\guides/wallet.md:56 msgid "To configure your Bitcoin Core node to maintain a transaction index, add the following to your `bitcoin.conf`:" msgstr "لتهيئة نود Bitcoin Core الخاص بك للحفاظ على فهرس المعاملات، أضف ما يلي إلى ملف bitcoin.conf الخاص بك:" -#: src\guides/inscriptions.md:59 src\guides/sat-hunting.md:30 +#: src\guides/wallet.md:59 src\guides/sat-hunting.md:30 msgid "" "```\n" "txindex=1\n" "```" msgstr "" -#: src\guides/inscriptions.md:63 +#: src\guides/wallet.md:63 msgid "Or, run `bitcoind` with `-txindex`:" msgstr "" -#: src\guides/inscriptions.md:65 src\guides/inscriptions.md:74 +#: src\guides/wallet.md:65 src\guides/wallet.md:74 msgid "" "```\n" "bitcoind -txindex\n" "```" msgstr "" -#: src\guides/inscriptions.md:69 +#: src\guides/wallet.md:69 msgid "Syncing the Bitcoin Blockchain" msgstr "مزامنة شبكه البيتكوين" -#: src\guides/inscriptions.md:72 +#: src\guides/wallet.md:72 msgid "To sync the chain, run:" msgstr "لبدء مزامنه السلسلة:" -#: src\guides/inscriptions.md:78 +#: src\guides/wallet.md:78 msgid "…and leave it running until `getblockcount`:" msgstr "...واتركها تعمل حتى يتم استدعاء الأمر `getblockcount`:" -#: src\guides/inscriptions.md:80 +#: src\guides/wallet.md:80 msgid "" "```\n" "bitcoin-cli getblockcount\n" "```" msgstr "" -#: src\guides/inscriptions.md:84 +#: src\guides/wallet.md:84 msgid "agrees with the block count on a block explorer like [the mempool.space block explorer](https://mempool.space/). `ord` interacts with `bitcoind`, so you should leave `bitcoind` running in the background when you're using `ord`." msgstr "يتم قبول عدد البلوكات من خلال المتصفح مثل [the mempool.space block explorer](https://mempool.space/).ال `ord` يتفاعل مع `bitcoind` لذالك يجب أن تبقي النود فعال بلخلفيه عند استعمالك له." -#: src\guides/inscriptions.md:88 +#: src\guides/wallet.md:88 msgid "Installing `ord`" msgstr "تحميل برنامج أورد `ord`" -#: src\guides/inscriptions.md:91 +#: src\guides/wallet.md:91 msgid "The `ord` utility is written in Rust and can be built from [source](https://github.com/ordinals/ord). Pre-built binaries are available on the [releases page](https://github.com/ordinals/ord/releases)." msgstr "أدوات `ord` مكتوبه بلغه الداست ويمكن بنائها من[المصدر](https://github.com/ordinals/ord).الثنائيات المبنية مسبقًا متوفرة على [صفحه الإصدار](https://github.com/ordinals/ord/releases)." -#: src\guides/inscriptions.md:95 +#: src\guides/wallet.md:95 msgid "You can install the latest pre-built binary from the command line with:" msgstr "يمكنك تحميل الثنائيات المبنية مسبقًا بإعطاء الأمر التالي:" -#: src\guides/inscriptions.md:97 +#: src\guides/wallet.md:97 msgid "" "```sh\n" "curl --proto '=https' --tlsv1.2 -fsLS https://ordinals.com/install.sh | bash -s\n" "```" msgstr "" -#: src\guides/inscriptions.md:101 +#: src\guides/wallet.md:101 msgid "Once `ord` is installed, you should be able to run:" msgstr "بمجرد الانتهاء من تحميل ال `ord`، يجب أن تكون قادرًا على تشغيل:" -#: src\guides/inscriptions.md:103 +#: src\guides/wallet.md:103 msgid "" "```\n" "ord --version\n" "```" msgstr "" -#: src\guides/inscriptions.md:107 +#: src\guides/wallet.md:107 msgid "Which prints out `ord`'s version number." msgstr "والذي يشير الى الإصدار `ord` الحالي." -#: src\guides/inscriptions.md:109 +#: src\guides/wallet.md:109 msgid "Creating a Bitcoin Core Wallet" msgstr "إنشاء محفظة بيتكوين كور" -#: src\guides/inscriptions.md:112 +#: src\guides/wallet.md:112 msgid "`ord` uses Bitcoin Core to manage private keys, sign transactions, and broadcast transactions to the Bitcoin network." msgstr "أورد `ord` يستخدم بيتكوين كور ليدير المفاتيح الخاصة وتواقيع المعاملات وإرسالها الى شبكه البيتكوين." -#: src\guides/inscriptions.md:115 +#: src\guides/wallet.md:115 msgid "To create a Bitcoin Core wallet named `ord` for use with `ord`, run:" msgstr "لإنشاء محفظة Bitcoin Core تحمل اسم `ord` واستخدامها, اعطي الأمر:" -#: src\guides/inscriptions.md:117 +#: src\guides/wallet.md:117 msgid "" "```\n" "ord wallet create\n" "```" msgstr "" -#: src\guides/inscriptions.md:121 +#: src\guides/wallet.md:121 msgid "Receiving Sats" msgstr "استقبال الساتس" -#: src\guides/inscriptions.md:124 +#: src\guides/wallet.md:124 msgid "Inscriptions are made on individual sats, using normal Bitcoin transactions that pay fees in sats, so your wallet will need some sats." msgstr "يتم إنشاء الأنسكريبشين على الساتوشيات فردية باستخدام معاملات بيتكوين العادية التي تدفع الرسوم بوحدات الساتوشي، لذا ستحتاج محفظتك إلى بعض الساتوشيات." -#: src\guides/inscriptions.md:127 +#: src\guides/wallet.md:127 msgid "Get a new address from your `ord` wallet by running:" msgstr "إحصل على عنوان جديد من محفظة ال `ord` من خلال الأمر التالي:" -#: src\guides/inscriptions.md:129 src\guides/inscriptions.md:201 src\guides/inscriptions.md:229 +#: src\guides/wallet.md:129 src\guides/wallet.md:201 src\guides/wallet.md:229 msgid "" "```\n" "ord wallet receive\n" "```" msgstr "" -#: src\guides/inscriptions.md:133 +#: src\guides/wallet.md:133 msgid "And send it some funds." msgstr "وأرسل لها بعض التمويل." -#: src\guides/inscriptions.md:135 +#: src\guides/wallet.md:135 msgid "You can see pending transactions with:" msgstr "يمكنك رؤية المعاملات المعلقة بالأمر التالي:" -#: src\guides/inscriptions.md:137 src\guides/inscriptions.md:213 src\guides/inscriptions.md:240 +#: src\guides/wallet.md:137 src\guides/wallet.md:213 src\guides/wallet.md:240 msgid "" "```\n" "ord wallet transactions\n" "```" msgstr "" -#: src\guides/inscriptions.md:141 +#: src\guides/wallet.md:141 msgid "Once the transaction confirms, you should be able to see the transactions outputs with `ord wallet outputs`." msgstr "بمجرد تأكيد الصفقة، يجب أن تكون قادرًا على رؤية المعاملات من خلال `ord wallet outputs`." -#: src\guides/inscriptions.md:144 +#: src\guides/wallet.md:144 msgid "Creating Inscription Content" msgstr "إنشاء محتوى إنسكريبشين" -#: src\guides/inscriptions.md:147 +#: src\guides/wallet.md:147 msgid "Sats can be inscribed with any kind of content, but the `ord` wallet only supports content types that can be displayed by the `ord` block explorer." msgstr "يمكن إنسكرايب الساتوشيات بأي نوع من المحتوى، ولكن محفظة `ord` تدعم فقط أنواع المحتوى التي يمكن عرضها عبر مستكشف الكتل `ord`." -#: src\guides/inscriptions.md:150 +#: src\guides/wallet.md:150 msgid "Additionally, inscriptions are included in transactions, so the larger the content, the higher the fee that the inscription transaction must pay." msgstr "بالإضافة إلى ذلك، تُدرج الإنسكريبشين في المعاملات، لذلك كلما زاد حجم المحتوى، زادت الرسوم التي يجب دفعها لعملية الإنسكريبشين ." -#: src\guides/inscriptions.md:153 +#: src\guides/wallet.md:153 msgid "Inscription content is included in transaction witnesses, which receive the witness discount. To calculate the approximate fee that an inscribe transaction will pay, divide the content size by four and multiply by the fee rate." msgstr "يتم تضمين محتوى الإنسكريبشين في شهادات المعاملات، والتي تحصل على الشاهد. لحساب الرسوم التقريبية التي ستدفعها معاملة الإنسكريبشين ، قسم حجم المحتوى على أربعة ثم اضرب الناتج بمعدل الرسوم." -#: src\guides/inscriptions.md:157 +#: src\guides/wallet.md:157 msgid "" "Inscription transactions must be less than 400,000 weight units, or they will not be relayed by Bitcoin Core. One byte of inscription content costs one weight unit. Since an inscription transaction includes not just the inscription content, limit inscription content to " "less than 400,000 weight units. 390,000 weight units should be safe." @@ -1831,97 +1831,97 @@ msgstr "" "يجب أن تكون المعاملات أقل من 400,000 وحدة وزنية، وإلا فلن يتم نقلها بواسطة بيتكوين كور. بايت واحد من المحتوى يكلف وحدة وزنية واحدة. نظرًا لأن المعاملات لا تشمل فقط المحتوى ، يجب تقييد محتوى الإنسكريبشين لأقل من 400,000 وحدة وزنية. 390,000 وحدة وزنية يجب أن تكون آمنة " "وصحيحه." -#: src\guides/inscriptions.md:163 +#: src\guides/wallet.md:163 msgid "Creating Inscriptions" msgstr "إنشاء الإنسكريبشينس" -#: src\guides/inscriptions.md:166 +#: src\guides/wallet.md:166 msgid "To create an inscription with the contents of `FILE`, run:" msgstr "لإنشاء الإنسكريبشين مع محتوى `FILE` , اعطي الأمر:" -#: src\guides/inscriptions.md:168 +#: src\guides/wallet.md:168 msgid "" "```\n" "ord wallet inscribe --fee-rate FEE_RATE --file FILE\n" "```" msgstr "" -#: src\guides/inscriptions.md:172 +#: src\guides/wallet.md:172 msgid "" "Ord will output two transactions IDs, one for the commit transaction, and one for the reveal transaction, and the inscription ID. Inscription IDs are of the form `TXIDiN`, where `TXID` is the transaction ID of the reveal transaction, and `N` is the index of the " "inscription in the reveal transaction." msgstr "أورد سيقوم بإخراج رقمين معرّفين للمعاملات، واحد لمعاملة الالتزام، وآخر لمعاملة الكشف، وكذلك مُعرف إنسكريبشين . مُعرفات إنسكريبشين على الشكل `TXIDiN`، حيث `TXID` هو مُعرّف المعاملة لمعاملة الكشف، و `N` هو المؤشر الإنسكريبشين في معاملة الكشف." -#: src\guides/inscriptions.md:177 +#: src\guides/wallet.md:177 msgid "The commit transaction commits to a tapscript containing the content of the inscription, and the reveal transaction spends from that tapscript, revealing the content on chain and inscribing it on the first sat of the input that contains the corresponding tapscript." msgstr "تُؤكد معاملة الالتزام على tapscript يحتوي على محتوى الإنسكريبشين، وتقوم معاملة الكشف بإنفاق من هذا الـ tapscript، مكشفةً المحتوى على الشبكة وتسجيله على أول ساتوشي في المدخل الذي يحتوي على الـ tapscript المقابل." -#: src\guides/inscriptions.md:182 +#: src\guides/wallet.md:182 msgid "Wait for the reveal transaction to be mined. You can check the status of the commit and reveal transactions using [the mempool.space block explorer](https://mempool.space/)." msgstr "إنتظر حتى يتم تعدين المعاملة. يمكنك التحقق منها من خلال[the mempool.space block explorer](https://mempool.space/)." -#: src\guides/inscriptions.md:186 +#: src\guides/wallet.md:186 msgid "Once the reveal transaction has been mined, the inscription ID should be printed when you run:" msgstr "بمجرد أن يتم تعدين الصفقة (reveal transaction)، يجب أن يتم طباعة معرّف الإنسكريبشين (inscription ID) عند تشغيل الأمر:" -#: src\guides/inscriptions.md:189 src\guides/inscriptions.md:220 src\guides/inscriptions.md:246 +#: src\guides/wallet.md:189 src\guides/wallet.md:220 src\guides/wallet.md:246 msgid "" "```\n" "ord wallet inscriptions\n" "```" msgstr "" -#: src\guides/inscriptions.md:193 +#: src\guides/wallet.md:193 msgid "And when you visit [the ordinals explorer](https://ordinals.com/) at `ordinals.com/inscription/INSCRIPTION_ID`." msgstr "وعند زيارتك [the ordinals explorer](https://ordinals.com/) على `ordinals.com/inscription/INSCRIPTION_ID`." -#: src\guides/inscriptions.md:196 +#: src\guides/wallet.md:196 msgid "Sending Inscriptions" msgstr "إرسال الإنسكريبشينس" -#: src\guides/inscriptions.md:199 +#: src\guides/wallet.md:199 msgid "Ask the recipient to generate a new address by running:" msgstr "أطلب من المستلم إنشاء عنوان جديد عن طريق تشغيل:" -#: src\guides/inscriptions.md:205 +#: src\guides/wallet.md:205 msgid "Send the inscription by running:" msgstr "أرسل الإنسكريبشين من خلال:" -#: src\guides/inscriptions.md:207 +#: src\guides/wallet.md:207 msgid "" "```\n" "ord wallet send --fee-rate
          \n" "```" msgstr "" -#: src\guides/inscriptions.md:211 src\guides/inscriptions.md:239 +#: src\guides/wallet.md:211 src\guides/wallet.md:239 msgid "See the pending transaction with:" msgstr "تفقد حاله المعاملة من خلال:" -#: src\guides/inscriptions.md:217 +#: src\guides/wallet.md:217 msgid "Once the send transaction confirms, the recipient can confirm receipt by running:" msgstr "بمجرد تأكيد المعاملة المرسلة، يمكن للمستلم تأكيد الاستلام عن طريق الأمر :" -#: src\guides/inscriptions.md:224 +#: src\guides/wallet.md:224 msgid "Receiving Inscriptions" msgstr "استقبال الإنسكريبشينس" -#: src\guides/inscriptions.md:227 +#: src\guides/wallet.md:227 msgid "Generate a new receive address using:" msgstr "قم بإنشاء عنوان استقبال جديد باستخدام:" -#: src\guides/inscriptions.md:233 +#: src\guides/wallet.md:233 msgid "The sender can transfer the inscription to your address using:" msgstr "يمكن للمرسل نقل الإنسكريبشين إلى عنوانك باستخدام:" -#: src\guides/inscriptions.md:235 +#: src\guides/wallet.md:235 msgid "" "```\n" "ord wallet send ADDRESS INSCRIPTION_ID\n" "```" msgstr "" -#: src\guides/inscriptions.md:244 +#: src\guides/wallet.md:244 msgid "Once the send transaction confirms, you can can confirm receipt by running:" msgstr "بمجرد تأكيد المعاملة المرسلة، يمكن للمستلم تأكيد الاستلام عن طريق الأمر :" @@ -2691,8 +2691,8 @@ msgid "Ord can be tested using the following flags to specify the test network. msgstr "يمكن اختبار Ord باستخدام الأعلام التالية لتحديد شبكة الاختبار. لمزيد من المعلومات حول تشغيل Bitcoin Core لأغراض الاختبار، انظر [Bitcoin's developer documentation](https://developer.bitcoin.org/examples/testing.html)." #: src\guides/testing.md:7 -msgid "Most `ord` commands in [inscriptions](inscriptions.md) and [explorer](explorer.md) can be run with the following network flags:" -msgstr "معظم أوامر `ord` في [inscriptions](inscriptions.md) و[explorer](explorer.md) يمكن تشغيلها باستخدام الأعلام الشبكية التالية:" +msgid "Most `ord` commands in [inscriptions](wallet.md) and [explorer](explorer.md) can be run with the following network flags:" +msgstr "معظم أوامر `ord` في [inscriptions](wallet.md) و[explorer](explorer.md) يمكن تشغيلها باستخدام الأعلام الشبكية التالية:" #: src\guides/testing.md:10 msgid "Network" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4396d327ba..902d3fafb7 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,20 +10,22 @@ Summary - [Pointer](inscriptions/pointer.md) - [Provenance](inscriptions/provenance.md) - [Recursion](inscriptions/recursion.md) + - [Rendering](inscriptions/rendering.md) - [FAQ](faq.md) - [Contributing](contributing.md) - [Donate](donate.md) - [Guides](guides.md) - [Explorer](guides/explorer.md) - - [Inscriptions](guides/inscriptions.md) + - [Wallet](guides/wallet.md) - [Batch Inscribing](guides/batch-inscribing.md) - - [Sat Hunting](guides/sat-hunting.md) - - [Teleburning](guides/teleburning.md) - [Collecting](guides/collecting.md) - [Sparrow Wallet](guides/collecting/sparrow-wallet.md) - - [Testing](guides/testing.md) - [Moderation](guides/moderation.md) - [Reindexing](guides/reindexing.md) + - [Sat Hunting](guides/sat-hunting.md) + - [Settings](guides/settings.md) + - [Teleburning](guides/teleburning.md) + - [Testing](guides/testing.md) - [Bounties](bounties.md) - [Bounty 0: 100,000 sats Claimed!](bounty/0.md) - [Bounty 1: 200,000 sats Claimed!](bounty/1.md) diff --git a/docs/src/guides/collecting/sparrow-wallet.md b/docs/src/guides/collecting/sparrow-wallet.md index 623308c3f9..f72206b653 100644 --- a/docs/src/guides/collecting/sparrow-wallet.md +++ b/docs/src/guides/collecting/sparrow-wallet.md @@ -78,7 +78,7 @@ This UTXO (Inscription) is now un-spendable within the Sparrow Wallet until you ## Importing into `ord` wallet -For details on setting up Bitcoin Core and the `ord` wallet check out the [Inscriptions Guide](../inscriptions.md) +For details on setting up Bitcoin Core and the `ord` wallet check out the [Wallet Guide](../wallet.md) When setting up `ord`, instead of running `ord wallet create` to create a brand-new wallet, you can import your existing wallet using `ord wallet restore "BIP39 SEED PHRASE"` using the seed phrase you generated with Sparrow Wallet. diff --git a/docs/src/guides/explorer.md b/docs/src/guides/explorer.md index 1f120646e4..84fdf974d9 100644 --- a/docs/src/guides/explorer.md +++ b/docs/src/guides/explorer.md @@ -2,8 +2,11 @@ Ordinal Explorer ================ The `ord` binary includes a block explorer. We host an instance of the block -explorer on mainnet at [ordinals.com](https://ordinals.com), and on signet at -[signet.ordinals.com](https://signet.ordinals.com). +explorer on mainnet at [ordinals.com](https://ordinals.com), on signet at +[signet.ordinals.com](https://signet.ordinals.com), and on testnet at +[testnet.ordinals.com](https://testnet.ordinals.com). As of version 0.16.0 the +wallet needs `ord server` running in the background. This is analogous to how +`bitcoin-cli` needs `bitcoind` running in the background. ### Running The Explorer The server can be run locally with: @@ -14,14 +17,10 @@ To specify a port add the `--http-port` flag: `ord server --http-port 8080` -To enable the JSON-API endpoints add the `--enable-json-api` or `-j` flag (see -[here](#json-api) for more info): +The JSON-API endpoints are enabled by default, to disable them add the +`--disable-json-api` flag (see [here](#json-api) for more info): -`ord server --enable-json-api` - -To test how your inscriptions will look you can run: - -`ord preview ...` +`ord server --disable-json-api` Search ------ @@ -76,7 +75,7 @@ been issued when they are mined: JSON-API -------- -You can run `ord server` with the `--enable-json-api` flag to access endpoints that +By default the `ord server` gives access to endpoints that return JSON instead of HTML if you set the HTTP `Accept: application/json` header. The structure of these objects closely follows what is shown in the HTML. These endpoints are: diff --git a/docs/src/guides/sat-hunting.md b/docs/src/guides/sat-hunting.md index def8331dbb..2a30999dad 100644 --- a/docs/src/guides/sat-hunting.md +++ b/docs/src/guides/sat-hunting.md @@ -67,7 +67,7 @@ wallet is named `foo`: 2. Display any rare ordinals wallet `foo`'s UTXOs: ```sh - ord --wallet foo --index-sats wallet sats + ord --index-sats wallet --name foo sats ``` ### Searching for Rare Ordinals in a Non-Bitcoin Core Wallet @@ -127,7 +127,7 @@ your wallet of funds. bitcoin-cli getwalletinfo ``` -7. Display your wallet's rare ordinals: +6. Display your wallet's rare ordinals: ```sh ord wallet sats @@ -200,7 +200,7 @@ those multiple descriptors into Bitcoin Core. bitcoin-cli loadwallet foo-watch-only ``` -4. Now import the descriptors, with the correct checksums, into Bitcoin Core. +5. Now import the descriptors, with the correct checksums, into Bitcoin Core. ```sh bitcoin-cli \ @@ -222,7 +222,7 @@ those multiple descriptors into Bitcoin Core. instead of `0`. This will reduce the time it takes for Bitcoin Core to search for your wallet's UTXOs. -5. Check that everything worked: +6. Check that everything worked: ```sh bitcoin-cli getwalletinfo diff --git a/docs/src/guides/settings.md b/docs/src/guides/settings.md new file mode 100644 index 0000000000..ba6f893199 --- /dev/null +++ b/docs/src/guides/settings.md @@ -0,0 +1,60 @@ +Settings +======== + +`ord` can be configured with the command line, environment variables, a +configuration file, and default values. + +The command line takes precedence over environment variables, which take +precedence over the configuration file, which takes precedence over defaults. + +The path to the configuration file can be given with `--config `. +`ord` will error if `` doesn't exist. + +The path to a directory containing a configuration file name named `ord.yaml` +can be given with `--config-dir ` or `--data-dir +` in which case the config path is `/ord.yaml` +or `/ord.yaml`. It is not an error if it does not exist. + +If none of `--config`, `--config-dir`, or `--data-dir` are given, and a file +named `ord.yaml` exists in the default data directory, it will be loaded. + +For a setting named `--setting-name` on the command line, the environment +variable will be named `ORD_SETTING_NAME`, and the config file field will be +named `setting_name`. For example, the data directory can be configured with +`--data-dir` on the command line, the `ORD_DATA_DIR` environment variable, or +`data_dir` in the config file. + +See `ord --help` for documentation of all the settings. + +`ord`'s current configuration can be viewed as JSON with the `ord settings` +command. + +Example Configuration +--------------------- + +```yaml +{{#include ../../../ord.yaml}} +``` + +Hiding Inscription Content +-------------------------- + +Inscription content can be selectively prevented from being served by `ord +server`. + +Unlike other settings, this can only be configured with the configuration file +or environment variables. + +To hide inscriptions with an environment variable: + +``` +export ORD_HIDDEN='6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0' +``` + +Or with the configuration file: + +```yaml +hidden: +- 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 +- 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 +``` diff --git a/docs/src/guides/testing.md b/docs/src/guides/testing.md index 19cb9c50d4..f1f1fa070c 100644 --- a/docs/src/guides/testing.md +++ b/docs/src/guides/testing.md @@ -1,10 +1,54 @@ Testing ======= +Test Environment +---------------- + +`ord env ` creates a test environment in ``, spins up +`bitcoind` and `ord server` instances, prints example commands for interacting +with the test `bitcoind` and `ord server` instances, waits for `CTRL-C`, and +then shuts down `bitcoind` and `ord server`. + +`ord env` tries to use port 9000 for `bitcoind`'s RPC interface, and port +`9001` for `ord`'s RPC interface, but will fall back to random unused ports. + +Inside of the env directory, `ord env` will write `bitcoind`'s configuration to +`bitcoin.conf`, `ord`'s configuration to `ord.yaml`, and the env configuration +to `env.json`. + +`env.json` contains the commands needed to invoke `bitcoin-cli` and `ord +wallet`, as well as the ports `bitcoind` and `ord server` are listening on. + +These can be extracted into shell commands using `jq`: + +```shell +bitcoin=`jq -r '.bitcoin_cli_command | join(" ")' env/env.json` +$bitcoin listunspent + +ord=`jq -r '.ord_wallet_command | join(" ")' env/env.json` +$ord outputs +``` + +If `ord` is in the `$PATH` and the env directory is `env`, the `bitcoin-cli` +command will be: + +``` +bitcoin-cli -datadir=env` +``` + +And the `ord` will be: + +``` +ord --data-dir env +``` + +Test Networks +------------- + Ord can be tested using the following flags to specify the test network. For more information on running Bitcoin Core for testing, see [Bitcoin's developer documentation](https://developer.bitcoin.org/examples/testing.html). -Most `ord` commands in [inscriptions](inscriptions.md) and [explorer](explorer.md) +Most `ord` commands in [wallet](wallet.md) and [explorer](explorer.md) can be run with the following network flags: | Network | Flag | @@ -18,45 +62,53 @@ Regtest doesn't require downloading the blockchain or indexing ord. Example ------- -Run bitcoind in regtest with: +Run `bitcoind` in regtest with: + ``` bitcoind -regtest -txindex ``` +Run `ord server` in regtest with: + +``` +ord --regtest server +``` + Create a wallet in regtest with: + ``` -ord -r wallet create +ord --regtest wallet create ``` Get a regtest receive address with: + ``` -ord -r wallet receive +ord --regtest wallet receive ``` Mine 101 blocks (to unlock the coinbase) with: + ``` bitcoin-cli -regtest generatetoaddress 101 ``` Inscribe in regtest with: + ``` -ord -r wallet inscribe --fee-rate 1 --file +ord --regtest wallet inscribe --fee-rate 1 --file ``` Mine the inscription with: -``` -bitcoin-cli -regtest generatetoaddress 1 -``` -View the inscription in the regtest explorer: ``` -ord -r server +bitcoin-cli -regtest generatetoaddress 1 ``` By default, browsers don't support compression over HTTP. To test compressed content over HTTP, use the `--decompress` flag: + ``` -ord -r server --decompress +ord --regtest server --decompress ``` Testing Recursion @@ -64,22 +116,42 @@ Testing Recursion When testing out [recursion](../inscriptions/recursion.md), inscribe the dependencies first (example with [p5.js](https://p5js.org)): + ``` -ord -r wallet inscribe --fee-rate 1 --file p5.js +ord --regtest wallet inscribe --fee-rate 1 --file p5.js ``` -This should return a `inscription_id` which you can then reference in your -recursive inscription. -ATTENTION: These ids will be different when inscribing on -mainnet or signet, so be sure to change those in your recursive inscription for -each chain. +This will return the inscription ID of the dependency which you can then +reference in your inscription. + +However, inscription IDs differ between mainnet and test chains, so you must +change the inscription IDs in your inscription to the mainnet inscription IDs of +your dependencies before making the final inscription on mainnet. Then you can inscribe your recursive inscription with: + ``` -ord -r wallet inscribe --fee-rate 1 --file recursive-inscription.html +ord --regtest wallet inscribe --fee-rate 1 --file recursive-inscription.html ``` + Finally you will have to mine some blocks and start the server: + ``` bitcoin-cli generatetoaddress 6 -ord -r server +``` + +### Mainnet Dependencies + +To avoid having to change dependency inscription IDs to mainnet inscription IDs, +you may utilize a content proxy when testing. `ord server` accepts a +`--content-proxy` option, which takes the URL of a another `ord server` +instance. When making a request to `/content/` when a content +proxy is set and the inscription is not found, `ord server` will forward the +request to the content proxy. This allows you to run a test `ord server` +instance with a mainnet content proxy. You can then use mainnet inscription IDs +in your test inscription, which will then return the content of the mainnet +inscriptions. + +``` +ord --regtest server --content-proxy https://ordinals.com ``` diff --git a/docs/src/guides/inscriptions.md b/docs/src/guides/wallet.md similarity index 78% rename from docs/src/guides/inscriptions.md rename to docs/src/guides/wallet.md index 8692bed9fd..6e9b30d9a1 100644 --- a/docs/src/guides/inscriptions.md +++ b/docs/src/guides/wallet.md @@ -1,5 +1,5 @@ -Ordinal Inscription Guide -========================= +Wallet +====== Individual sats can be inscribed with arbitrary content, creating Bitcoin-native digital artifacts that can be held in a Bitcoin wallet and @@ -158,18 +158,115 @@ ord --version Which prints out `ord`'s version number. -Creating a Bitcoin Core Wallet ------------------------------- +Creating a Wallet +----------------- -`ord` uses Bitcoin Core to manage private keys, sign transactions, and -broadcast transactions to the Bitcoin network. +`ord` uses `bitcoind` to manage private keys, sign transactions, and +broadcast transactions to the Bitcoin network. Additionally the `ord wallet` +requires [`ord server`](explorer.md) running in the background. Make sure these +programs are running: -To create a Bitcoin Core wallet named `ord` for use with `ord`, run: +``` +bitcoind -txindex +``` + +``` +ord server +``` + +To create a wallet named `ord`, the default, for use with `ord wallet`, run: ``` ord wallet create ``` +This will print out your seed phrase mnemonic, store it somewhere safe. + +``` +{ + "mnemonic": "dignity buddy actor toast talk crisp city annual tourist orient similar federal", + "passphrase": "" +} +``` + +If you want to specify a different name or use an `ord server` running on a +non-default URL you can set these options: + +``` +ord wallet --name foo --server-url http://127.0.0.1:8080 create +``` + +To see all available wallet options you can run: + +``` +ord wallet help +``` + +Restoring and Dumping Wallet +---------------------------- + +The `ord` wallet uses descriptors, so you can export the output descriptors and +import them into another descriptor-based wallet. To export the wallet +descriptors, which include your private keys: + +``` +$ ord wallet dump +========================================== += THIS STRING CONTAINS YOUR PRIVATE KEYS = += DO NOT SHARE WITH ANYONE = +========================================== +{ + "wallet_name": "ord", + "descriptors": [ + { + "desc": "tr([551ac972/86'/1'/0']tprv8h4xBhrfZwX9o1XtUMmz92yNiGRYjF9B1vkvQ858aN1UQcACZNqN9nFzj3vrYPa4jdPMfw4ooMuNBfR4gcYm7LmhKZNTaF4etbN29Tj7UcH/0/*)#uxn94yt5", + "timestamp": 1296688602, + "active": true, + "internal": false, + "range": [ + 0, + 999 + ], + "next": 0 + }, + { + "desc": "tr([551ac972/86'/1'/0']tprv8h4xBhrfZwX9o1XtUMmz92yNiGRYjF9B1vkvQ858aN1UQcACZNqN9nFzj3vrYPa4jdPMfw4ooMuNBfR4gcYm7LmhKZNTaF4etbN29Tj7UcH/1/*)#djkyg3mv", + "timestamp": 1296688602, + "active": true, + "internal": true, + "range": [ + 0, + 999 + ], + "next": 0 + } + ] +} +``` + +An `ord` wallet can be restored from a mnemonic: + +``` +ord wallet restore --from mnemonic +``` + +Type your mnemonic and press return. + +To restore from a descriptor in `descriptor.json`: + +``` +cat descriptor.json | ord wallet restore --from descriptor +``` + +To restore from a descriptor in the clipboard: + +``` +ord wallet restore --from descriptor +``` + +Paste the descriptor into the terminal and press CTRL-D on unix and CTRL-Z +on Windows. + Receiving Sats -------------- @@ -301,7 +398,7 @@ ord wallet receive The sender can transfer the inscription to your address using: ``` -ord wallet send ADDRESS INSCRIPTION_ID +ord wallet send --fee-rate ADDRESS INSCRIPTION_ID ``` See the pending transaction with: diff --git a/docs/src/inscriptions.md b/docs/src/inscriptions.md index 3104cee8b9..73f3237924 100644 --- a/docs/src/inscriptions.md +++ b/docs/src/inscriptions.md @@ -60,9 +60,9 @@ few restrictions is that individual data pushes may not be larger than 520 bytes. The inscription content is contained within the input of a reveal transaction, -and the inscription is made on the first sat of its input. This sat can then be -tracked using the familiar rules of ordinal theory, allowing it to be -transferred, bought, sold, lost to fees, and recovered. +and the inscription is made on the first sat of its input if it has no pointer +field. This sat can then be tracked using the familiar rules of ordinal +theory, allowing it to be transferred, bought, sold, lost to fees, and recovered. Content ------- @@ -124,6 +124,22 @@ go through the inputs consecutively and look for all inscription `envelopes`. | 3 | 0 | | | 4 | 1 | i6 | +Inscription Numbers +------------------- + +Inscriptions are assigned inscription numbers starting at zero, first by the +order reveal transactions appear in blocks, and the order that reveal envelopes +appear in those transactions. + +Due to a historical bug in `ord` which cannot be fixed without changing a great +many inscription numbers, inscriptions which are revealed and then immediately +spent to fees are numbered as if they appear last in the block in which they +are revealed. + +Inscriptions which are cursed are numbered starting at negative one, counting +down. Cursed inscriptions on and after the jubilee at block 824544 are +vindicated, and are assigned positive inscription numbers. + Sandboxing ---------- @@ -133,3 +149,16 @@ off-chain content, thus keeping inscriptions immutable and self-contained. This is accomplished by loading HTML and SVG inscriptions inside `iframes` with the `sandbox` attribute, as well as serving inscription content with `Content-Security-Policy` headers. + +Reinscriptions +-------------- + +Previously inscribed sats can be reinscribed with the `--reinscribe` command if +the inscription is present in the wallet. This will only append an inscription to +a sat, not change the initial inscription. + +Reinscribe with satpoint: +`ord wallet inscribe --fee-rate --reinscribe --file --satpoint ` + +Reinscribe on a sat (requires sat index): +`ord --index-sats wallet inscribe --fee-rate --reinscribe --file --sat ` diff --git a/docs/src/inscriptions/recursion.md b/docs/src/inscriptions/recursion.md index 5b116f084f..d5839ba8f9 100644 --- a/docs/src/inscriptions/recursion.md +++ b/docs/src/inscriptions/recursion.md @@ -2,11 +2,21 @@ Recursion ========= An important exception to [sandboxing](../inscriptions.md#sandboxing) is -recursion: access to `ord`'s `/content` endpoint is permitted, allowing -inscriptions to access the content of other inscriptions by requesting -`/content/`. +recursion. Recursive endpoints are whitelisted endpoints that allow access to +on-chain data, including the content of other inscriptions. -This has a number of interesting use-cases: +Since changes to recursive endpoints might break inscriptions that rely on +them, recursive endpoints have backwards-compatibility guarantees not shared by +`ord server`'s other endpoints. In particular: + +- Recursive endpoints will not be removed +- Object fields returned by recursive endpoints will not be renamed or change types + +However, additional object fields may be added or reordered, so inscriptions +must handle additional, unexpected fields, and must not expect fields to be +returned in a specific order. + +Recursion has a number of interesting use-cases: - Remixing the content of existing inscriptions. @@ -22,12 +32,15 @@ This has a number of interesting use-cases: The recursive endpoints are: +- `/content/`: the content of the inscription with `` - `/r/blockhash/`: block hash at given block height. - `/r/blockhash`: latest block hash. - `/r/blockheight`: latest block height. +- `/r/blockinfo/`: block info. `` may be a block height or block hash. - `/r/blocktime`: UNIX time stamp of latest block. - `/r/children/`: the first 100 child inscription ids. - `/r/children//`: the set of 100 child inscription ids on ``. +- `/r/inscription/:inscription_id`: information about an inscription - `/r/metadata/`: JSON string containing the hex-encoded CBOR metadata. - `/r/sat/`: the first 100 inscription ids on a sat. - `/r/sat//`: the set of 100 inscription ids on ``. @@ -49,16 +62,50 @@ plain-text responses. Examples -------- +- `/r/blockhash/0`: + +```json +"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" +``` + - `/r/blockheight`: ```json 777000 ``` -- `/r/blockhash/0`: +- `/r/blockinfo/0`: ```json -"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" +{ + "average_fee": 0, + "average_fee_rate": 0, + "bits": 486604799, + "chainwork": "0000000000000000000000000000000000000000000000000000000100010001", + "confirmations": 0, + "difficulty": 0.0, + "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + "height": 0, + "max_fee": 0, + "max_fee_rate": 0, + "max_tx_size": 0, + "median_fee": 0, + "median_time": 1231006505, + "merkle_root": "0000000000000000000000000000000000000000000000000000000000000000", + "min_fee": 0, + "min_fee_rate": 0, + "next_block": null, + "nonce": 0, + "previous_block": null, + "subsidy": 5000000000, + "target": "00000000ffff0000000000000000000000000000000000000000000000000000", + "timestamp": 1231006505, + "total_fee": 0, + "total_size": 0, + "total_weight": 0, + "transaction_count": 1, + "version": 1 +} ``` - `/r/blocktime`: @@ -67,6 +114,41 @@ Examples 1700770905 ``` +- `/r/children/60bcf821240064a9c55225c4f01711b0ebbcab39aa3fafeefe4299ab158536fai0/49`: + +```json +{ + "ids":[ + "7cd66b8e3a63dcd2fada917119830286bca0637267709d6df1ca78d98a1b4487i4900", + "7cd66b8e3a63dcd2fada917119830286bca0637267709d6df1ca78d98a1b4487i4901", + ... + "7cd66b8e3a63dcd2fada917119830286bca0637267709d6df1ca78d98a1b4487i4935", + "7cd66b8e3a63dcd2fada917119830286bca0637267709d6df1ca78d98a1b4487i4936" + ], + "more":false, + "page":49 +} +``` + +- `r/inscription/3bd72a7ef68776c9429961e43043ff65efa7fb2d8bb407386a9e3b19f149bc36i0` + +```json +{ + "charms": [], + "content_type": "image/png", + "content_length": 144037, + "fee": 36352, + "height": 209, + "id": "3bd72a7ef68776c9429961e43043ff65efa7fb2d8bb407386a9e3b19f149bc36i0", + "number": 2, + "output": "3bd72a7ef68776c9429961e43043ff65efa7fb2d8bb407386a9e3b19f149bc36:0", + "sat": null, + "satpoint": "3bd72a7ef68776c9429961e43043ff65efa7fb2d8bb407386a9e3b19f149bc36:0:0", + "timestamp": 1708312562, + "value": 10000 +} +``` + - `/r/metadata/35b66389b44535861c44b2b18ed602997ee11db9a30d384ae89630c9fc6f011fi3`: ```json diff --git a/docs/src/inscriptions/rendering.md b/docs/src/inscriptions/rendering.md new file mode 100644 index 0000000000..e2876f2961 --- /dev/null +++ b/docs/src/inscriptions/rendering.md @@ -0,0 +1,35 @@ +Rendering +========= + +Aspect Ratio +------------ + +Inscriptions should be rendered with a square aspect ratio. Non-square aspect +ratio inscriptions should not be cropped, and should instead be centered and +resized to fit within their container. + +Maximum Size +------------ + +The `ord` explorer, used by [ordinals.com](https://ordinals.com/), displays +inscription previews with a maximum size of 576 by 576 pixels, making it a +reasonable choice when choosing a maximum display size. + +Image Rendering +--------------- + +The CSS `image-rendering` property controls how images are resampled when +upscaled and downscaled. + +When downscaling image inscriptions, `image-rendering: auto`, should be used. +This is desirable even when downscaling pixel art. + +When upscaling image inscriptions other than AVIF, `image-rendering: pixelated` +should be used. This is desirable when upscaling pixel art, since it preserves +the sharp edges of pixels. It is undesirable when upscaling non-pixel art, but +should still be used for visual compatibility with the `ord` explorer. + +When upscaling AVIF and JPEG XL inscriptions, `image-rendering: auto` should be +used. This allows inscribers to opt-in to non-pixelated upscaling for non-pixel +art inscriptions. Until such time as JPEG XL is widely supported by browsers, +it is not a recommended image format. diff --git a/docs/src/introduction.md b/docs/src/introduction.md index 28686f5270..f5c314e9a5 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -29,7 +29,7 @@ For more details on ordinal theory, see the [overview](overview.md). For more details on inscriptions, see [inscriptions](inscriptions.md). When you're ready to get your hands dirty, a good place to start is with -[inscriptions](guides/inscriptions.md), a curious species of digital artifact +[inscriptions](guides/wallet.md), a curious species of digital artifact enabled by ordinal theory. Links @@ -48,4 +48,3 @@ Videos - [Ordinal Theory Explained: Satoshi Serial Numbers and NFTs on Bitcoin](https://www.youtube.com/watch?v=rSS0O2KQpsI) - [Ordinals Workshop with Rodarmor](https://www.youtube.com/watch?v=MC_haVa6N3I) -- [Ordinal Art: Mint Your own NFTs on Bitcoin w/ @rodarmor](https://www.youtube.com/watch?v=j5V33kV3iqo) diff --git a/docs/theme/favicon.png b/docs/theme/favicon.png new file mode 120000 index 0000000000..5ca3e9ed5f --- /dev/null +++ b/docs/theme/favicon.png @@ -0,0 +1 @@ +../../static/favicon.png \ No newline at end of file diff --git a/docs/theme/favicon.svg b/docs/theme/favicon.svg new file mode 120000 index 0000000000..13d947797b --- /dev/null +++ b/docs/theme/favicon.svg @@ -0,0 +1 @@ +../../static/favicon.svg \ No newline at end of file diff --git a/justfile b/justfile index 296f35809d..8b590dac14 100644 --- a/justfile +++ b/justfile @@ -18,24 +18,40 @@ clippy: cargo clippy --all --all-targets -- --deny warnings deploy branch remote chain domain: - ssh root@{{domain}} 'mkdir -p deploy \ + ssh root@{{domain}} '\ + export DEBIAN_FRONTEND=noninteractive \ + && mkdir -p deploy \ && apt-get update --yes \ && apt-get upgrade --yes \ && apt-get install --yes git rsync' rsync -avz deploy/checkout root@{{domain}}:deploy/checkout ssh root@{{domain}} 'cd deploy && ./checkout {{branch}} {{remote}} {{chain}} {{domain}}' -deploy-mainnet-alpha branch='master' remote='ordinals/ord': (deploy branch remote 'main' 'alpha.ordinals.net') +deploy-mainnet-alpha branch='master' remote='ordinals/ord': \ + (deploy branch remote 'main' 'alpha.ordinals.net') -deploy-mainnet-bravo branch='master' remote='ordinals/ord': (deploy branch remote 'main' 'bravo.ordinals.net') +deploy-mainnet-bravo branch='master' remote='ordinals/ord': \ + (deploy branch remote 'main' 'bravo.ordinals.net') -deploy-mainnet-charlie branch='master' remote='ordinals/ord': (deploy branch remote 'main' 'charlie.ordinals.net') +deploy-mainnet-charlie branch='master' remote='ordinals/ord': \ + (deploy branch remote 'main' 'charlie.ordinals.net') -deploy-regtest branch='master' remote='ordinals/ord': (deploy branch remote 'regtest' 'regtest.ordinals.net') +deploy-regtest branch='master' remote='ordinals/ord': \ + (deploy branch remote 'regtest' 'regtest.ordinals.net') -deploy-signet branch='master' remote='ordinals/ord': (deploy branch remote 'signet' 'signet.ordinals.net') +deploy-signet branch='master' remote='ordinals/ord': \ + (deploy branch remote 'signet' 'signet.ordinals.net') -deploy-testnet branch='master' remote='ordinals/ord': (deploy branch remote 'test' 'testnet.ordinals.net') +deploy-testnet branch='master' remote='ordinals/ord': \ + (deploy branch remote 'test' 'testnet.ordinals.net') + +deploy-all: \ + deploy-regtest \ + deploy-testnet \ + deploy-signet \ + deploy-mainnet-alpha \ + deploy-mainnet-bravo \ + deploy-mainnet-charlie servers := 'alpha bravo charlie regtest signet testnet' @@ -83,7 +99,7 @@ open: open http://localhost doc: - cargo doc --all --open + cargo doc --workspace --exclude audit-content-security-policy --exclude audit-cache --open prepare-release revision='master': #!/usr/bin/env bash @@ -137,6 +153,7 @@ update-modern-normalize: download-log unit='ord' host='alpha.ordinals.net': ssh root@{{host}} 'mkdir -p tmp && journalctl -u {{unit}} > tmp/{{unit}}.log' + mkdir -p tmp/{{unit}} rsync --progress --compress root@{{host}}:tmp/{{unit}}.log tmp/{{unit}}.log graph log: @@ -146,9 +163,11 @@ flamegraph dir=`git branch --show-current`: ./bin/flamegraph $1 serve-docs: build-docs - open http://127.0.0.1:8080 python3 -m http.server --directory docs/build/html --bind 127.0.0.1 8080 +open-docs: + open http://127.0.0.1:8080 + build-docs: #!/usr/bin/env bash mdbook build docs -d build @@ -161,20 +180,25 @@ update-changelog: echo >> CHANGELOG.md git log --pretty='format:- %s' >> CHANGELOG.md -preview-examples: - cargo run preview examples/* - convert-logo-to-favicon: convert -background none -resize 256x256 logo.svg static/favicon.png update-mdbook-theme: - curl https://raw.githubusercontent.com/rust-lang/mdBook/v0.4.35/src/theme/index.hbs > docs/theme/index.hbs + curl \ + https://raw.githubusercontent.com/rust-lang/mdBook/v0.4.35/src/theme/index.hbs \ + > docs/theme/index.hbs audit-cache: cargo run --package audit-cache +audit-content-security-policy: + cargo run --package audit-content-security-policy + coverage: cargo llvm-cov benchmark-server: cargo bench --bench server + +update-contributors: + cargo run --release --package update-contributors diff --git a/ord.yaml b/ord.yaml index 84488fe97a..8a3dcceade 100644 --- a/ord.yaml +++ b/ord.yaml @@ -1,10 +1,30 @@ -# Example Config +# example config -# use username `bar` and password `foo` for bitcoind RPC calls -bitcoin_rpc_user: bar -bitcoin_rpc_pass: foo +# see `ord --help` for setting documentation -# prevent `ord server` from serving the content of the inscriptions below +bitcoin_data_dir: /var/lib/bitcoin +bitcoin_rpc_password: bar +bitcoin_rpc_url: https://localhost:8000 +bitcoin_rpc_username: foo +chain: mainnet +commit_interval: 10000 +config: /var/lib/ord/ord.yaml +config_dir: /var/lib/ord +cookie_file: /var/lib/bitcoin/.cookie +data_dir: /var/lib/ord +first_inscription_height: 100 +height_limit: 1000 hidden: - 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 - 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 +index: /var/lib/ord/index.redb +index_cache_size: 1000000000 +index_runes: true +index_sats: true +index_spent_sats: true +index_transactions: true +integration_test: true +no_index_inscriptions: true +server_password: bar +server_url: http://localhost:8888 +server_username: foo diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000000..146fe2f1c6 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,196 @@ +use { + super::{ + target_as_block_hash, BlockHash, Chain, Deserialize, Height, InscriptionId, OutPoint, Pile, + Rarity, SatPoint, Serialize, SpacedRune, TxMerkleNode, TxOut, + }, + serde_hex::{SerHex, Strict}, +}; + +pub use crate::templates::{ + BlocksHtml as Blocks, RuneHtml as Rune, RunesHtml as Runes, StatusHtml as Status, + TransactionHtml as Transaction, +}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Block { + pub hash: BlockHash, + pub target: BlockHash, + pub best_height: u32, + pub height: u32, + pub inscriptions: Vec, +} + +impl Block { + pub(crate) fn new( + block: bitcoin::Block, + height: Height, + best_height: Height, + inscriptions: Vec, + ) -> Self { + Self { + hash: block.header.block_hash(), + target: target_as_block_hash(block.header.target()), + height: height.0, + best_height: best_height.0, + inscriptions, + } + } +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockInfo { + pub average_fee: u64, + pub average_fee_rate: u64, + pub bits: u32, + #[serde(with = "SerHex::")] + pub chainwork: [u8; 32], + pub confirmations: i32, + pub difficulty: f64, + pub hash: BlockHash, + pub height: u32, + pub max_fee: u64, + pub max_fee_rate: u64, + pub max_tx_size: u32, + pub median_fee: u64, + pub median_time: Option, + pub merkle_root: TxMerkleNode, + pub min_fee: u64, + pub min_fee_rate: u64, + pub next_block: Option, + pub nonce: u32, + pub previous_block: Option, + pub subsidy: u64, + pub target: BlockHash, + pub timestamp: u64, + pub total_fee: u64, + pub total_size: usize, + pub total_weight: usize, + pub transaction_count: u64, + pub version: u32, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Children { + pub ids: Vec, + pub more: bool, + pub page: usize, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +pub struct Inscription { + pub address: Option, + pub charms: Vec, + pub children: Vec, + pub content_length: Option, + pub content_type: Option, + pub fee: u64, + pub height: u32, + pub id: InscriptionId, + pub next: Option, + pub number: i32, + pub parent: Option, + pub previous: Option, + pub rune: Option, + pub sat: Option, + pub satpoint: SatPoint, + pub timestamp: i64, + pub value: Option, + + // ---- Ordzaar ---- + pub inscription_sequence: u32, + // ---- Ordzaar ---- +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct InscriptionRecursive { + pub charms: Vec, + pub content_type: Option, + pub content_length: Option, + pub fee: u64, + pub height: u32, + pub id: InscriptionId, + pub number: i32, + pub output: OutPoint, + pub sat: Option, + pub satpoint: SatPoint, + pub timestamp: i64, + pub value: Option, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Inscriptions { + pub ids: Vec, + pub more: bool, + pub page_index: u32, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub address: Option, + pub indexed: bool, + pub inscriptions: Vec, + pub runes: Vec<(SpacedRune, Pile)>, + pub sat_ranges: Option>, + pub script_pubkey: String, + pub spent: bool, + pub transaction: String, + pub value: u64, +} + +impl Output { + pub fn new( + chain: Chain, + inscriptions: Vec, + outpoint: OutPoint, + output: TxOut, + indexed: bool, + runes: Vec<(SpacedRune, Pile)>, + sat_ranges: Option>, + spent: bool, + ) -> Self { + Self { + address: chain + .address_from_script(&output.script_pubkey) + .ok() + .map(|address| address.to_string()), + indexed, + inscriptions, + runes, + sat_ranges, + script_pubkey: output.script_pubkey.to_asm_string(), + spent, + transaction: outpoint.txid.to_string(), + value: output.value, + } + } +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Sat { + pub number: u64, + pub decimal: String, + pub degree: String, + pub name: String, + pub block: u32, + pub cycle: u32, + pub epoch: u32, + pub period: u32, + pub offset: u64, + pub rarity: Rarity, + pub percentile: String, + pub satpoint: Option, + pub timestamp: i64, + pub inscriptions: Vec, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct SatInscription { + pub id: Option, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct SatInscriptions { + pub ids: Vec, + pub more: bool, + pub page: u64, +} diff --git a/src/arguments.rs b/src/arguments.rs index f348c5cfa3..2e9b0bd2da 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -21,6 +21,28 @@ pub(crate) struct Arguments { impl Arguments { pub(crate) fn run(self) -> SubcommandResult { - self.subcommand.run(self.options) + let mut env: BTreeMap = BTreeMap::new(); + + for (var, value) in env::vars_os() { + let Some(var) = var.to_str() else { + continue; + }; + + let Some(key) = var.strip_prefix("ORD_") else { + continue; + }; + + env.insert( + key.into(), + value.into_string().map_err(|value| { + anyhow!( + "environment variable `{var}` not valid unicode: `{}`", + value.to_string_lossy() + ) + })?, + ); + } + + self.subcommand.run(Settings::load(self.options)?) } } diff --git a/src/chain.rs b/src/chain.rs index 1186ac63ad..fe925a2d63 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -70,6 +70,13 @@ impl Chain { bitcoin::blockdata::constants::genesis_block(self.network()) } + pub(crate) fn genesis_coinbase_outpoint(self) -> OutPoint { + OutPoint { + txid: self.genesis_block().coinbase().unwrap().txid(), + vout: 0, + } + } + pub(crate) fn address_from_script( self, script: &Script, @@ -77,12 +84,12 @@ impl Chain { Address::from_script(script, self.network()) } - pub(crate) fn join_with_data_dir(self, data_dir: &Path) -> PathBuf { + pub(crate) fn join_with_data_dir(self, data_dir: impl AsRef) -> PathBuf { match self { - Self::Mainnet => data_dir.to_owned(), - Self::Testnet => data_dir.join("testnet3"), - Self::Signet => data_dir.join("signet"), - Self::Regtest => data_dir.join("regtest"), + Self::Mainnet => data_dir.as_ref().to_owned(), + Self::Testnet => data_dir.as_ref().join("testnet3"), + Self::Signet => data_dir.as_ref().join("signet"), + Self::Regtest => data_dir.as_ref().join("regtest"), } } } @@ -101,3 +108,34 @@ impl Display for Chain { ) } } + +impl FromStr for Chain { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "mainnet" => Ok(Self::Mainnet), + "regtest" => Ok(Self::Regtest), + "signet" => Ok(Self::Signet), + "testnet" => Ok(Self::Testnet), + _ => bail!("invalid chain `{s}`"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_str() { + assert_eq!("mainnet".parse::().unwrap(), Chain::Mainnet); + assert_eq!("regtest".parse::().unwrap(), Chain::Regtest); + assert_eq!("signet".parse::().unwrap(), Chain::Signet); + assert_eq!("testnet".parse::().unwrap(), Chain::Testnet); + assert_eq!( + "foo".parse::().unwrap_err().to_string(), + "invalid chain `foo`" + ); + } +} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 15e6189171..0000000000 --- a/src/config.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::*; - -#[derive(Deserialize, Default, PartialEq, Debug)] -#[serde(deny_unknown_fields)] -pub(crate) struct Config { - pub(crate) hidden: HashSet, - pub(crate) bitcoin_rpc_pass: Option, - pub(crate) bitcoin_rpc_user: Option, -} - -impl Config { - pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { - self.hidden.contains(&inscription_id) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn inscriptions_can_be_hidden() { - let a = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let b = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi1" - .parse::() - .unwrap(); - - let config = Config { - hidden: iter::once(a).collect(), - ..Default::default() - }; - - assert!(config.is_hidden(a)); - assert!(!config.is_hidden(b)); - } - - #[test] - fn example_config_file_is_valid() { - let _: Config = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); - } -} diff --git a/src/decimal.rs b/src/decimal.rs index 035ff98613..f66b533ddc 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -1,7 +1,7 @@ use super::*; #[derive(Debug, PartialEq, Copy, Clone)] -pub(crate) struct Decimal { +pub struct Decimal { value: u128, scale: u8, } @@ -24,6 +24,30 @@ impl Decimal { } } +impl Display for Decimal { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let magnitude = 10u128.pow(self.scale.into()); + + let integer = self.value / magnitude; + let mut fraction = self.value % magnitude; + + write!(f, "{integer}")?; + + if fraction > 0 { + let mut width = self.scale.into(); + + while fraction % 10 == 0 { + fraction /= 10; + width -= 1; + } + + write!(f, ".{fraction:0>width$}", width = width)?; + } + + Ok(()) + } +} + impl FromStr for Decimal { type Err = Error; @@ -39,20 +63,15 @@ impl FromStr for Decimal { integer.parse::()? }; - let decimal = if decimal.is_empty() { - 0 + let (decimal, scale) = if decimal.is_empty() { + (0, 0) } else { - decimal.parse::()? + let trailing_zeros = decimal.chars().rev().take_while(|c| *c == '0').count(); + let significant_digits = decimal.chars().count() - trailing_zeros; + let decimal = decimal.parse::()? / 10u128.pow(u32::try_from(trailing_zeros).unwrap()); + (decimal, u8::try_from(significant_digits).unwrap()) }; - let scale = s - .trim_end_matches('0') - .chars() - .skip_while(|c| *c != '.') - .skip(1) - .count() - .try_into()?; - Ok(Self { value: integer * 10u128.pow(u32::from(scale)) + decimal, scale, @@ -66,6 +85,24 @@ impl FromStr for Decimal { } } +impl Serialize for Decimal { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Decimal { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + DeserializeFromStr::with(deserializer) + } +} + #[cfg(test)] mod tests { use super::*; @@ -99,6 +136,7 @@ mod tests { case("1.11", 111, 2); case("1.", 1, 0); case(".1", 1, 1); + case("1.10", 11, 1); } #[test] @@ -149,4 +187,65 @@ mod tests { case("123.456", 3, 123456); case("123.456", 6, 123456000); } + + #[test] + fn to_string() { + #[track_caller] + fn case(decimal: Decimal, string: &str) { + assert_eq!(decimal.to_string(), string); + assert_eq!(decimal, string.parse::().unwrap()); + } + + case(Decimal { value: 1, scale: 0 }, "1"); + case(Decimal { value: 1, scale: 1 }, "0.1"); + case( + Decimal { + value: 101, + scale: 2, + }, + "1.01", + ); + case( + Decimal { + value: 1234, + scale: 6, + }, + "0.001234", + ); + case( + Decimal { + value: 12, + scale: 0, + }, + "12", + ); + case( + Decimal { + value: 12, + scale: 1, + }, + "1.2", + ); + case( + Decimal { + value: 12, + scale: 2, + }, + "0.12", + ); + case( + Decimal { + value: 123456, + scale: 3, + }, + "123.456", + ); + case( + Decimal { + value: 123456789, + scale: 6, + }, + "123.456789", + ); + } } diff --git a/src/deserialize_from_str.rs b/src/deserialize_from_str.rs deleted file mode 100644 index f4c537f546..0000000000 --- a/src/deserialize_from_str.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::*; - -pub(crate) struct DeserializeFromStr(pub(crate) T); - -impl<'de, T: FromStr> Deserialize<'de> for DeserializeFromStr -where - T::Err: Display, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(Self( - FromStr::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)?, - )) - } -} diff --git a/src/index.rs b/src/index.rs index 2ecde7af18..00e9cf475f 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4,35 +4,40 @@ use { Entry, HeaderValue, InscriptionEntry, InscriptionEntryValue, InscriptionIdValue, OutPointValue, RuneEntryValue, RuneIdValue, SatPointValue, SatRange, TxidValue, }, + event::Event, reorg::*, runes::{Rune, RuneId}, updater::Updater, }, super::*, crate::{ - subcommand::{find::FindRangeOutput, server::InscriptionQuery}, + subcommand::{find::FindRangeOutput, server::query}, templates::StatusHtml, }, bitcoin::block::Header, - bitcoincore_rpc::{json::GetBlockHeaderResult, Client}, + bitcoincore_rpc::{ + json::{GetBlockHeaderResult, GetBlockStatsResult}, + Client, + }, chrono::SubsecRound, indicatif::{ProgressBar, ProgressStyle}, log::log_enabled, redb::{ Database, DatabaseError, MultimapTable, MultimapTableDefinition, MultimapTableHandle, - ReadOnlyTable, ReadableMultimapTable, ReadableTable, RedbKey, RedbValue, RepairSession, - StorageError, Table, TableDefinition, TableHandle, WriteTransaction, + ReadOnlyTable, ReadableMultimapTable, ReadableTable, RepairSession, StorageError, Table, + TableDefinition, TableHandle, TableStats, WriteTransaction, }, std::{ - collections::{BTreeSet, HashMap}, + collections::HashMap, io::{BufWriter, Write}, sync::{Mutex, Once}, }, }; -pub use self::entry::RuneEntry; +pub use {self::entry::RuneEntry, entry::MintEntry}; pub(crate) mod entry; +pub mod event; mod fetcher; mod reorg; mod rtx; @@ -41,7 +46,7 @@ mod updater; #[cfg(test)] pub(crate) mod testing; -const SCHEMA_VERSION: u64 = 16; +const SCHEMA_VERSION: u64 = 18; macro_rules! define_table { ($name:ident, $key:ty, $value:ty) => { @@ -59,6 +64,7 @@ macro_rules! define_multimap_table { define_multimap_table! { SATPOINT_TO_SEQUENCE_NUMBER, &SatPointValue, u32 } define_multimap_table! { SAT_TO_SEQUENCE_NUMBER, u64, u32 } define_multimap_table! { SEQUENCE_NUMBER_TO_CHILDREN, u32, u32 } +define_table! { CONTENT_TYPE_TO_COUNT, Option<&[u8]>, u64 } define_table! { HEIGHT_TO_BLOCK_HEADER, u32, &HeaderValue } define_table! { HEIGHT_TO_LAST_SEQUENCE_NUMBER, u32, u32 } define_table! { HOME_INSCRIPTIONS, u32, InscriptionIdValue } @@ -78,12 +84,6 @@ define_table! { TRANSACTION_ID_TO_RUNE, &TxidValue, u128 } define_table! { TRANSACTION_ID_TO_TRANSACTION, &TxidValue, &[u8] } define_table! { WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP, u32, u128 } -#[derive(Debug, PartialEq)] -pub enum List { - Spent, - Unspent(Vec<(u64, u64)>), -} - #[derive(Copy, Clone)] pub(crate) enum Statistic { Schema = 0, @@ -99,6 +99,8 @@ pub(crate) enum Statistic { SatRanges = 10, UnboundInscriptions = 11, IndexTransactions = 12, + IndexSpentSats = 13, + InitialSyncTime = 14, } impl Statistic { @@ -145,6 +147,21 @@ pub(crate) struct TableInfo { tree_height: u32, } +impl From for TableInfo { + fn from(stats: TableStats) -> Self { + Self { + branch_pages: stats.branch_pages(), + fragmented_bytes: stats.fragmented_bytes(), + leaf_pages: stats.leaf_pages(), + metadata_bytes: stats.metadata_bytes(), + proportion: 0.0, + stored_bytes: stats.stored_bytes(), + total_bytes: stats.stored_bytes() + stats.metadata_bytes() + stats.fragmented_bytes(), + tree_height: stats.tree_height(), + } + } +} + #[derive(Serialize)] pub(crate) struct TransactionInfo { pub(crate) starting_block_count: u32, @@ -164,7 +181,7 @@ pub(crate) struct InscriptionInfo { pub(crate) charms: u16, } -trait BitcoinCoreRpcResultExt { +pub(crate) trait BitcoinCoreRpcResultExt { fn into_option(self) -> Result>; } @@ -191,27 +208,33 @@ pub struct Index { client: Client, database: Database, durability: redb::Durability, + event_sender: Option>, first_inscription_height: u32, genesis_block_coinbase_transaction: Transaction, genesis_block_coinbase_txid: Txid, height_limit: Option, index_runes: bool, index_sats: bool, + index_spent_sats: bool, index_transactions: bool, - options: Options, + settings: Settings, path: PathBuf, started: DateTime, unrecoverably_reorged: AtomicBool, } impl Index { - pub fn open(options: &Options) -> Result { - let client = options.bitcoin_rpc_client(None)?; + pub fn open(settings: &Settings) -> Result { + Index::open_with_event_sender(settings, None) + } - let path = options - .index - .clone() - .unwrap_or(options.data_dir().clone().join("index.redb")); + pub fn open_with_event_sender( + settings: &Settings, + event_sender: Option>, + ) -> Result { + let client = settings.bitcoin_rpc_client(None)?; + + let path = settings.index().to_owned(); if let Err(err) = fs::create_dir_all(path.parent().unwrap()) { bail!( @@ -220,16 +243,9 @@ impl Index { ); } - let db_cache_size = match options.db_cache_size { - Some(db_cache_size) => db_cache_size, - None => { - let mut sys = System::new(); - sys.refresh_memory(); - usize::try_from(sys.total_memory() / 4)? - } - }; + let index_cache_size = settings.index_cache_size(); - log::info!("Setting DB cache size to {} bytes", db_cache_size); + log::info!("Setting index cache size to {} bytes", index_cache_size); let durability = if cfg!(test) { redb::Durability::None @@ -237,42 +253,40 @@ impl Index { redb::Durability::Immediate }; - let index_runes; - let index_sats; - let index_transactions; - let index_path = path.clone(); let once = Once::new(); let progress_bar = Mutex::new(None); + let integration_test = settings.integration_test(); - let database = match Database::builder() - .set_cache_size(db_cache_size) - .set_repair_callback(move |progress: &mut RepairSession| { - once.call_once(|| println!("Index file `{}` needs recovery. This can take a long time, especially for the --index-sats index.", index_path.display())); + let repair_callback = move |progress: &mut RepairSession| { + once.call_once(|| println!("Index file `{}` needs recovery. This can take a long time, especially for the --index-sats index.", index_path.display())); - if !(cfg!(test) || log_enabled!(log::Level::Info) || integration_test()) { - let mut guard = progress_bar.lock().unwrap(); + if !(cfg!(test) || log_enabled!(log::Level::Info) || integration_test) { + let mut guard = progress_bar.lock().unwrap(); - let progress_bar = guard.get_or_insert_with(|| { - let progress_bar = ProgressBar::new(100); - progress_bar.set_style( - ProgressStyle::with_template("[repairing database] {wide_bar} {pos}/{len}").unwrap(), - ); - progress_bar - }); + let progress_bar = guard.get_or_insert_with(|| { + let progress_bar = ProgressBar::new(100); + progress_bar.set_style( + ProgressStyle::with_template("[repairing database] {wide_bar} {pos}/{len}").unwrap(), + ); + progress_bar + }); - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - progress_bar.set_position((progress.progress() * 100.0) as u64); - } - }) + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + progress_bar.set_position((progress.progress() * 100.0) as u64); + } + }; + + let database = match Database::builder() + .set_cache_size(index_cache_size) + .set_repair_callback(repair_callback) .open(&path) { Ok(database) => { { - let tx = database.begin_read()?; - let statistics = tx.open_table(STATISTIC_TO_COUNT)?; - - let schema_version = statistics + let schema_version = database + .begin_read()? + .open_table(STATISTIC_TO_COUNT)? .get(&Statistic::Schema.key())? .map(|x| x.value()) .unwrap_or(0); @@ -291,11 +305,6 @@ impl Index { cmp::Ordering::Equal => { } } - - - index_runes = Self::is_statistic_set(&statistics, Statistic::IndexRunes)?; - index_sats = Self::is_statistic_set(&statistics, Statistic::IndexSats)?; - index_transactions = Self::is_statistic_set(&statistics, Statistic::IndexTransactions)?; } database @@ -304,7 +313,7 @@ impl Index { if error.kind() == io::ErrorKind::NotFound => { let database = Database::builder() - .set_cache_size(db_cache_size) + .set_cache_size(index_cache_size) .create(&path)?; let mut tx = database.begin_write()?; @@ -314,6 +323,7 @@ impl Index { tx.open_multimap_table(SATPOINT_TO_SEQUENCE_NUMBER)?; tx.open_multimap_table(SAT_TO_SEQUENCE_NUMBER)?; tx.open_multimap_table(SEQUENCE_NUMBER_TO_CHILDREN)?; + tx.open_table(CONTENT_TYPE_TO_COUNT)?; tx.open_table(HEIGHT_TO_BLOCK_HEADER)?; tx.open_table(HEIGHT_TO_LAST_SEQUENCE_NUMBER)?; tx.open_table(HOME_INSCRIPTIONS)?; @@ -334,17 +344,34 @@ impl Index { let mut outpoint_to_sat_ranges = tx.open_table(OUTPOINT_TO_SAT_RANGES)?; let mut statistics = tx.open_table(STATISTIC_TO_COUNT)?; - if options.index_sats { + if settings.index_sats() { outpoint_to_sat_ranges.insert(&OutPoint::null().store(), [].as_slice())?; } - index_runes = options.index_runes(); - index_sats = options.index_sats; - index_transactions = options.index_transactions; + Self::set_statistic( + &mut statistics, + Statistic::IndexRunes, + u64::from(settings.index_runes()), + )?; + + Self::set_statistic( + &mut statistics, + Statistic::IndexSats, + u64::from(settings.index_sats() || settings.index_spent_sats()), + )?; + + Self::set_statistic( + &mut statistics, + Statistic::IndexSpentSats, + u64::from(settings.index_spent_sats()), + )?; + + Self::set_statistic( + &mut statistics, + Statistic::IndexTransactions, + u64::from(settings.index_transactions()), + )?; - Self::set_statistic(&mut statistics, Statistic::IndexRunes, u64::from(index_runes))?; - Self::set_statistic(&mut statistics, Statistic::IndexSats, u64::from(index_sats))?; - Self::set_statistic(&mut statistics, Statistic::IndexTransactions, u64::from(index_transactions))?; Self::set_statistic(&mut statistics, Statistic::Schema, SCHEMA_VERSION)?; } @@ -355,21 +382,37 @@ impl Index { Err(error) => bail!("failed to open index: {error}"), }; + let index_runes; + let index_sats; + let index_spent_sats; + let index_transactions; + + { + let tx = database.begin_read()?; + let statistics = tx.open_table(STATISTIC_TO_COUNT)?; + index_runes = Self::is_statistic_set(&statistics, Statistic::IndexRunes)?; + index_sats = Self::is_statistic_set(&statistics, Statistic::IndexSats)?; + index_spent_sats = Self::is_statistic_set(&statistics, Statistic::IndexSpentSats)?; + index_transactions = Self::is_statistic_set(&statistics, Statistic::IndexTransactions)?; + } + let genesis_block_coinbase_transaction = - options.chain().genesis_block().coinbase().unwrap().clone(); + settings.chain().genesis_block().coinbase().unwrap().clone(); Ok(Self { genesis_block_coinbase_txid: genesis_block_coinbase_transaction.txid(), client, database, durability, - first_inscription_height: options.first_inscription_height(), + event_sender, + first_inscription_height: settings.first_inscription_height(), genesis_block_coinbase_transaction, - height_limit: options.height_limit, + height_limit: settings.height_limit(), index_runes, index_sats, + index_spent_sats, index_transactions, - options: options.clone(), + settings: settings.clone(), path, started: Utc::now(), unrecoverably_reorged: AtomicBool::new(false), @@ -381,18 +424,15 @@ impl Index { self.durability = durability; } - pub(crate) fn check_sync(&self, utxos: &BTreeMap) -> Result { - let rtx = self.database.begin_read()?; - let outpoint_to_value = rtx.open_table(OUTPOINT_TO_VALUE)?; - for outpoint in utxos.keys() { - if outpoint_to_value.get(&outpoint.store())?.is_none() { - return Err(anyhow!( - "output in Bitcoin Core wallet but not in ord index: {outpoint}" - )); - } - } - - Ok(true) + pub(crate) fn contains_output(&self, output: &OutPoint) -> Result { + Ok( + self + .database + .begin_read()? + .open_table(OUTPOINT_TO_VALUE)? + .get(&output.store())? + .is_some(), + ) } pub(crate) fn has_rune_index(&self) -> bool { @@ -428,16 +468,29 @@ impl Index { let blessed_inscriptions = statistic(Statistic::BlessedInscriptions)?; let cursed_inscriptions = statistic(Statistic::CursedInscriptions)?; + let initial_sync_time = statistic(Statistic::InitialSyncTime)?; + + let mut content_type_counts = rtx + .open_table(CONTENT_TYPE_TO_COUNT)? + .iter()? + .map(|result| { + result.map(|(key, value)| (key.value().map(|slice| slice.into()), value.value())) + }) + .collect::>, u64)>, StorageError>>()?; + + content_type_counts.sort_by_key(|(_content_type, count)| Reverse(*count)); Ok(StatusHtml { blessed_inscriptions, - chain: self.options.chain(), + chain: self.settings.chain(), + content_type_counts, cursed_inscriptions, height, + initial_sync_time: Duration::from_micros(initial_sync_time), inscriptions: blessed_inscriptions + cursed_inscriptions, lost_sats: statistic(Statistic::LostSats)?, minimum_rune_for_next_block: Rune::minimum_at_height( - self.options.chain(), + self.settings.chain(), Height(next_height), ), rune_index: statistic(Statistic::IndexRunes)? != 0, @@ -451,139 +504,43 @@ impl Index { } pub(crate) fn info(&self) -> Result { - fn insert_table_info( - tables: &mut BTreeMap, - wtx: &WriteTransaction, - database_total_bytes: u64, - definition: TableDefinition, - ) { - let stats = wtx.open_table(definition).unwrap().stats().unwrap(); - - let fragmented_bytes = stats.fragmented_bytes(); - let metadata_bytes = stats.metadata_bytes(); - let stored_bytes = stats.stored_bytes(); - let total_bytes = stored_bytes + metadata_bytes + fragmented_bytes; - - tables.insert( - definition.name().into(), - TableInfo { - branch_pages: stats.branch_pages(), - fragmented_bytes, - leaf_pages: stats.leaf_pages(), - metadata_bytes, - proportion: total_bytes as f64 / database_total_bytes as f64, - stored_bytes, - total_bytes, - tree_height: stats.tree_height(), - }, - ); - } - - fn insert_multimap_table_info( - tables: &mut BTreeMap, - wtx: &WriteTransaction, - database_total_bytes: u64, - definition: MultimapTableDefinition, - ) { - let stats = wtx - .open_multimap_table(definition) - .unwrap() - .stats() - .unwrap(); - - let fragmented_bytes = stats.fragmented_bytes(); - let metadata_bytes = stats.metadata_bytes(); - let stored_bytes = stats.stored_bytes(); - let total_bytes = stored_bytes + metadata_bytes + fragmented_bytes; - - tables.insert( - definition.name().into(), - TableInfo { - branch_pages: stats.branch_pages(), - fragmented_bytes, - leaf_pages: stats.leaf_pages(), - metadata_bytes, - proportion: total_bytes as f64 / database_total_bytes as f64, - stored_bytes, - total_bytes, - tree_height: stats.tree_height(), - }, - ); - } + let stats = self.database.begin_write()?.stats()?; - let wtx = self.begin_write()?; - - let stats = wtx.stats()?; - - let fragmented_bytes = stats.fragmented_bytes(); - let metadata_bytes = stats.metadata_bytes(); - let stored_bytes = stats.stored_bytes(); - let total_bytes = fragmented_bytes + metadata_bytes + stored_bytes; + let rtx = self.database.begin_read()?; let mut tables: BTreeMap = BTreeMap::new(); - insert_multimap_table_info(&mut tables, &wtx, total_bytes, SATPOINT_TO_SEQUENCE_NUMBER); - insert_multimap_table_info(&mut tables, &wtx, total_bytes, SAT_TO_SEQUENCE_NUMBER); - insert_multimap_table_info(&mut tables, &wtx, total_bytes, SEQUENCE_NUMBER_TO_CHILDREN); - insert_table_info(&mut tables, &wtx, total_bytes, HEIGHT_TO_BLOCK_HEADER); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - HEIGHT_TO_LAST_SEQUENCE_NUMBER, - ); - insert_table_info(&mut tables, &wtx, total_bytes, HOME_INSCRIPTIONS); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - INSCRIPTION_ID_TO_SEQUENCE_NUMBER, - ); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER, - ); - insert_table_info(&mut tables, &wtx, total_bytes, OUTPOINT_TO_RUNE_BALANCES); - insert_table_info(&mut tables, &wtx, total_bytes, OUTPOINT_TO_SAT_RANGES); - insert_table_info(&mut tables, &wtx, total_bytes, OUTPOINT_TO_VALUE); - insert_table_info(&mut tables, &wtx, total_bytes, RUNE_ID_TO_RUNE_ENTRY); - insert_table_info(&mut tables, &wtx, total_bytes, RUNE_TO_RUNE_ID); - insert_table_info(&mut tables, &wtx, total_bytes, SAT_TO_SATPOINT); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY, - ); - insert_table_info(&mut tables, &wtx, total_bytes, SEQUENCE_NUMBER_TO_RUNE_ID); - insert_table_info(&mut tables, &wtx, total_bytes, SEQUENCE_NUMBER_TO_SATPOINT); - insert_table_info(&mut tables, &wtx, total_bytes, STATISTIC_TO_COUNT); - insert_table_info(&mut tables, &wtx, total_bytes, TRANSACTION_ID_TO_RUNE); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - TRANSACTION_ID_TO_TRANSACTION, - ); - insert_table_info( - &mut tables, - &wtx, - total_bytes, - WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP, - ); + for handle in rtx.list_tables()? { + let name = handle.name().into(); + let stats = rtx.open_untyped_table(handle)?.stats()?; + tables.insert(name, stats.into()); + } + + for handle in rtx.list_multimap_tables()? { + let name = handle.name().into(); + let stats = rtx.open_untyped_multimap_table(handle)?.stats()?; + tables.insert(name, stats.into()); + } - for table in wtx.list_tables()? { + for table in rtx.list_tables()? { assert!(tables.contains_key(table.name())); } - for table in wtx.list_multimap_tables()? { + for table in rtx.list_multimap_tables()? { assert!(tables.contains_key(table.name())); } + let total_bytes = tables + .values() + .map(|table_info| table_info.total_bytes) + .sum(); + + tables.values_mut().for_each(|table_info| { + table_info.proportion = table_info.total_bytes as f64 / total_bytes as f64 + }); + let info = { - let statistic_to_count = wtx.open_table(STATISTIC_TO_COUNT)?; + let statistic_to_count = rtx.open_table(STATISTIC_TO_COUNT)?; let sat_ranges = statistic_to_count .get(&Statistic::SatRanges.key())? .map(|x| x.value()) @@ -593,26 +550,26 @@ impl Index { .map(|x| x.value()) .unwrap_or(0); Info { - blocks_indexed: wtx + index_path: self.path.clone(), + blocks_indexed: rtx .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0), branch_pages: stats.branch_pages(), - fragmented_bytes, + fragmented_bytes: stats.fragmented_bytes(), index_file_size: fs::metadata(&self.path)?.len(), - index_path: self.path.clone(), leaf_pages: stats.leaf_pages(), - metadata_bytes, + metadata_bytes: stats.metadata_bytes(), + sat_ranges, outputs_traversed, page_size: stats.page_size(), - sat_ranges, - stored_bytes, - tables, + stored_bytes: stats.stored_bytes(), total_bytes, - transactions: wtx + tables, + transactions: rtx .open_table(WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP)? .range(0..)? .flat_map(|result| { @@ -625,18 +582,34 @@ impl Index { }) .collect(), tree_height: stats.tree_height(), - utxos_indexed: wtx.open_table(OUTPOINT_TO_SAT_RANGES)?.len()?, + utxos_indexed: rtx.open_table(OUTPOINT_TO_SAT_RANGES)?.len()?, } }; Ok(info) } - pub(crate) fn update(&self) -> Result { - let mut updater = Updater::new(self)?; - + pub fn update(&self) -> Result { loop { - match updater.update_index() { + let wtx = self.begin_write()?; + + let mut updater = Updater { + height: wtx + .open_table(HEIGHT_TO_BLOCK_HEADER)? + .range(0..)? + .next_back() + .transpose()? + .map(|(height, _header)| height.value() + 1) + .unwrap_or(0), + index: self, + outputs_cached: 0, + outputs_inserted_since_flush: 0, + outputs_traversed: 0, + range_cache: HashMap::new(), + sat_ranges_since_flush: 0, + }; + + match updater.update_index(wtx) { Ok(ok) => return Ok(ok), Err(err) => { log::info!("{}", err.to_string()); @@ -644,8 +617,6 @@ impl Index { match err.downcast_ref() { Some(&ReorgError::Recoverable { height, depth }) => { Reorg::handle_reorg(self, height, depth)?; - - updater = Updater::new(self)?; } Some(&ReorgError::Unrecoverable) => { self @@ -668,7 +639,7 @@ impl Index { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0); @@ -710,7 +681,7 @@ impl Index { .nth(satpoint.outpoint.vout.try_into().unwrap()) .unwrap(); self - .options + .settings .chain() .address_from_script(&output.script_pubkey) .map(|address| address.to_string()) @@ -785,6 +756,15 @@ impl Index { .unwrap_or_default() } + #[cfg(test)] + pub(crate) fn inscription_number(&self, inscription_id: InscriptionId) -> i32 { + self + .get_inscription_entry(inscription_id) + .unwrap() + .unwrap() + .inscription_number + } + pub(crate) fn block_count(&self) -> Result { self.begin_read()?.block_count() } @@ -907,32 +887,6 @@ impl Index { Ok(entries) } - pub(crate) fn get_rune_balance(&self, outpoint: OutPoint, id: RuneId) -> Result { - let rtx = self.database.begin_read()?; - - let outpoint_to_balances = rtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?; - - let Some(balances) = outpoint_to_balances.get(&outpoint.store())? else { - return Ok(0); - }; - - let balances_buffer = balances.value(); - - let mut i = 0; - while i < balances_buffer.len() { - let (balance_id, length) = runes::varint::decode(&balances_buffer[i..]); - i += length; - let (amount, length) = runes::varint::decode(&balances_buffer[i..]); - i += length; - - if RuneId::try_from(balance_id).unwrap() == id { - return Ok(amount); - } - } - - Ok(0) - } - pub(crate) fn get_rune_balances_for_outpoint( &self, outpoint: OutPoint, @@ -974,22 +928,6 @@ impl Index { Ok(balances) } - pub(crate) fn get_runic_outputs(&self, outpoints: &[OutPoint]) -> Result> { - let rtx = self.database.begin_read()?; - - let outpoint_to_balances = rtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?; - - let mut runic = BTreeSet::new(); - - for outpoint in outpoints { - if outpoint_to_balances.get(&outpoint.store())?.is_some() { - runic.insert(*outpoint); - } - } - - Ok(runic) - } - pub(crate) fn get_rune_balance_map(&self) -> Result>> { let outpoint_balances = self.get_rune_balances()?; @@ -1057,6 +995,10 @@ impl Index { self.client.get_block_header_info(&hash).into_option() } + pub(crate) fn block_stats(&self, height: u64) -> Result> { + self.client.get_block_stats(height).into_option() + } + pub(crate) fn get_block_by_height(&self, height: u32) -> Result> { Ok( self @@ -1123,7 +1065,7 @@ impl Index { }; self - .get_children_by_sequence_number_paginated(sequence_number, usize::max_value(), 0) + .get_children_by_sequence_number_paginated(sequence_number, usize::MAX, 0) .map(|(children, _more)| children) } @@ -1257,6 +1199,8 @@ impl Index { let mut ids = rtx .open_multimap_table(SAT_TO_SEQUENCE_NUMBER)? .get(&sat.n())? + .skip(page_index.saturating_mul(page_size).try_into().unwrap()) + .take(page_size.saturating_add(1).try_into().unwrap()) .map(|result| { result .and_then(|sequence_number| { @@ -1267,8 +1211,6 @@ impl Index { }) .map_err(|err| err.into()) }) - .skip(page_index.saturating_mul(page_size).try_into().unwrap()) - .take(page_size.saturating_add(1).try_into().unwrap()) .collect::>>()?; let more = ids.len() > page_size.try_into().unwrap(); @@ -1454,33 +1396,6 @@ impl Index { self.client.get_raw_transaction(&txid, None).into_option() } - pub(crate) fn get_transaction_blockhash(&self, txid: Txid) -> Result> { - Ok( - self - .client - .get_raw_transaction_info(&txid, None) - .into_option()? - .and_then(|info| { - if info.in_active_chain.unwrap_or_default() { - info.blockhash - } else { - None - } - }), - ) - } - - pub(crate) fn is_transaction_in_active_chain(&self, txid: Txid) -> Result { - Ok( - self - .client - .get_raw_transaction_info(&txid, None) - .into_option()? - .and_then(|info| info.in_active_chain) - .unwrap_or(false), - ) - } - pub(crate) fn find(&self, sat: Sat) -> Result> { let sat = sat.0; let rtx = self.begin_read()?; @@ -1562,41 +1477,60 @@ impl Index { Ok(Some(result)) } - fn list_inner(&self, outpoint: OutPointValue) -> Result>> { + pub(crate) fn list(&self, outpoint: OutPoint) -> Result>> { Ok( self .database .begin_read()? .open_table(OUTPOINT_TO_SAT_RANGES)? - .get(&outpoint)? - .map(|outpoint| outpoint.value().to_vec()), + .get(&outpoint.store())? + .map(|outpoint| outpoint.value().to_vec()) + .map(|sat_ranges| { + sat_ranges + .chunks_exact(11) + .map(|chunk| SatRange::load(chunk.try_into().unwrap())) + .collect::>() + }), ) } - pub(crate) fn list(&self, outpoint: OutPoint) -> Result> { - if !self.index_sats || outpoint == unbound_outpoint() { - return Ok(None); + pub(crate) fn is_output_spent(&self, outpoint: OutPoint) -> Result { + Ok( + outpoint != OutPoint::null() + && outpoint != self.settings.chain().genesis_coinbase_outpoint() + && self + .client + .get_tx_out(&outpoint.txid, outpoint.vout, Some(true))? + .is_none(), + ) + } + + pub(crate) fn is_output_in_active_chain(&self, outpoint: OutPoint) -> Result { + if outpoint == OutPoint::null() { + return Ok(true); } - let array = outpoint.store(); + if outpoint == self.settings.chain().genesis_coinbase_outpoint() { + return Ok(true); + } - let sat_ranges = self.list_inner(array)?; + let Some(info) = self + .client + .get_raw_transaction_info(&outpoint.txid, None) + .into_option()? + else { + return Ok(false); + }; - match sat_ranges { - Some(sat_ranges) => Ok(Some(List::Unspent( - sat_ranges - .chunks_exact(11) - .map(|chunk| SatRange::load(chunk.try_into().unwrap())) - .collect(), - ))), - None => { - if self.is_transaction_in_active_chain(outpoint.txid)? { - Ok(Some(List::Spent)) - } else { - Ok(None) - } - } + if !info.in_active_chain.unwrap_or_default() { + return Ok(false); } + + if usize::try_from(outpoint.vout).unwrap() >= info.vout.len() { + return Ok(false); + } + + Ok(true) } pub(crate) fn block_time(&self, height: Height) -> Result { @@ -1613,7 +1547,7 @@ impl Index { let current = height_to_block_header .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height) .map(|x| x.value()) .unwrap_or(0); @@ -1625,35 +1559,14 @@ impl Index { Ok(Blocktime::Expected( Utc::now() .round_subsecs(0) - .checked_add_signed(chrono::Duration::seconds( - 10 * 60 * i64::from(expected_blocks), - )) - .ok_or_else(|| anyhow!("block timestamp out of range"))?, + .checked_add_signed( + chrono::Duration::try_seconds(10 * 60 * i64::from(expected_blocks)) + .context("timestamp out of range")?, + ) + .context("timestamp out of range")?, )) } - pub(crate) fn get_inscriptions( - &self, - utxos: &BTreeMap, - ) -> Result> { - let rtx = self.database.begin_read()?; - - let mut result = BTreeMap::new(); - - let satpoint_to_sequence_number = rtx.open_multimap_table(SATPOINT_TO_SEQUENCE_NUMBER)?; - let sequence_number_to_inscription_entry = - rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; - for utxo in utxos.keys() { - result.extend(Self::inscriptions_on_output( - &satpoint_to_sequence_number, - &sequence_number_to_inscription_entry, - *utxo, - )?); - } - - Ok(result) - } - pub(crate) fn get_inscriptions_paginated( &self, page_size: u32, @@ -1781,17 +1694,17 @@ impl Index { } pub fn inscription_info_benchmark(index: &Index, inscription_number: i32) { - Self::inscription_info(index, InscriptionQuery::Number(inscription_number)).unwrap(); + Self::inscription_info(index, query::Inscription::Number(inscription_number)).unwrap(); } pub(crate) fn inscription_info( index: &Index, - query: InscriptionQuery, + query: query::Inscription, ) -> Result> { let rtx = index.database.begin_read()?; let sequence_number = match query { - InscriptionQuery::Id(id) => { + query::Inscription::Id(id) => { let inscription_id_to_sequence_number = rtx.open_table(INSCRIPTION_ID_TO_SEQUENCE_NUMBER)?; @@ -1803,7 +1716,7 @@ impl Index { sequence_number } - InscriptionQuery::Number(inscription_number) => { + query::Inscription::Number(inscription_number) => { let inscription_number_to_sequence_number = rtx.open_table(INSCRIPTION_NUMBER_TO_SEQUENCE_NUMBER)?; @@ -2012,7 +1925,7 @@ impl Index { Some(sat) => { if self.index_sats { // unbound inscriptions should not be assigned to a sat - assert!(satpoint.outpoint != unbound_outpoint()); + assert_ne!(satpoint.outpoint, unbound_outpoint()); assert!(rtx .open_multimap_table(SAT_TO_SEQUENCE_NUMBER) @@ -2040,7 +1953,7 @@ impl Index { } None => { if self.index_sats { - assert!(satpoint.outpoint == unbound_outpoint()) + assert_eq!(satpoint.outpoint, unbound_outpoint()) } } } @@ -2093,11 +2006,7 @@ impl Index { #[cfg(test)] mod tests { - use { - super::*, - crate::index::testing::Context, - bitcoin::secp256k1::rand::{self, RngCore}, - }; + use {super::*, crate::index::testing::Context}; #[test] fn height_limit() { @@ -2236,7 +2145,7 @@ mod tests { ) .unwrap() .unwrap(), - List::Unspent(vec![(0, 50 * COIN_VALUE)]) + &[(0, 50 * COIN_VALUE)], ) } @@ -2246,7 +2155,7 @@ mod tests { let txid = context.mine_blocks(1)[0].txdata[0].txid(); assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(vec![(50 * COIN_VALUE, 100 * COIN_VALUE)]) + &[(50 * COIN_VALUE, 100 * COIN_VALUE)], ) } @@ -2267,12 +2176,12 @@ mod tests { assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(vec![(50 * COIN_VALUE, 75 * COIN_VALUE)]) + &[(50 * COIN_VALUE, 75 * COIN_VALUE)], ); assert_eq!( context.index.list(OutPoint::new(txid, 1)).unwrap().unwrap(), - List::Unspent(vec![(75 * COIN_VALUE, 100 * COIN_VALUE)]) + &[(75 * COIN_VALUE, 100 * COIN_VALUE)], ); } @@ -2292,10 +2201,10 @@ mod tests { assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(vec![ + &[ (50 * COIN_VALUE, 100 * COIN_VALUE), (100 * COIN_VALUE, 150 * COIN_VALUE) - ]), + ], ); } @@ -2315,12 +2224,12 @@ mod tests { assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(vec![(50 * COIN_VALUE, 7499999995)]), + &[(50 * COIN_VALUE, 7499999995)], ); assert_eq!( context.index.list(OutPoint::new(txid, 1)).unwrap().unwrap(), - List::Unspent(vec![(7499999995, 9999999990)]), + &[(7499999995, 9999999990)], ); assert_eq!( @@ -2329,7 +2238,7 @@ mod tests { .list(OutPoint::new(coinbase_txid, 0)) .unwrap() .unwrap(), - List::Unspent(vec![(10000000000, 15000000000), (9999999990, 10000000000)]) + &[(10000000000, 15000000000), (9999999990, 10000000000)], ); } @@ -2359,11 +2268,11 @@ mod tests { .list(OutPoint::new(coinbase_txid, 0)) .unwrap() .unwrap(), - List::Unspent(vec![ + &[ (15000000000, 20000000000), (9999999990, 10000000000), (14999999990, 15000000000) - ]) + ], ); } @@ -2382,7 +2291,7 @@ mod tests { assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(Vec::new()) + &[], ); } @@ -2409,7 +2318,7 @@ mod tests { assert_eq!( context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Unspent(Vec::new()) + &[], ); } @@ -2424,10 +2333,7 @@ mod tests { }); context.mine_blocks(1); let txid = context.rpc_server.tx(1, 0).txid(); - assert_eq!( - context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), - List::Spent, - ); + assert_matches!(context.index.list(OutPoint::new(txid, 0)).unwrap(), None); } #[test] @@ -3001,10 +2907,7 @@ mod tests { .args(["--index-sats", "--first-inscription-height", "10"]) .build(); - let null_ranges = || match context.index.list(OutPoint::null()).unwrap().unwrap() { - List::Unspent(ranges) => ranges, - _ => panic!(), - }; + let null_ranges = || context.index.list(OutPoint::null()).unwrap().unwrap(); assert!(null_ranges().is_empty()); @@ -3402,31 +3305,6 @@ mod tests { } } - #[test] - fn unsynced_index_fails() { - for context in Context::configurations() { - let mut entropy = [0; 16]; - rand::thread_rng().fill_bytes(&mut entropy); - let mnemonic = Mnemonic::from_entropy(&entropy).unwrap(); - crate::subcommand::wallet::initialize("ord".into(), &context.options, mnemonic.to_seed("")) - .unwrap(); - context.rpc_server.mine_blocks(1); - assert_regex_match!( - crate::subcommand::wallet::get_unspent_outputs( - &crate::subcommand::wallet::bitcoin_rpc_client_for_wallet_command( - "ord".to_string(), - &context.options, - ) - .unwrap(), - &context.index - ) - .unwrap_err() - .to_string(), - r"output in Bitcoin Core wallet but not in ord index: [[:xdigit:]]{64}:\d+" - ); - } - } - #[test] fn unrecognized_even_field_inscriptions_are_cursed_and_unbound() { for context in Context::configurations() { @@ -3460,15 +3338,7 @@ mod tests { None, ); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -3505,15 +3375,7 @@ mod tests { None, ); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); + assert_eq!(context.index.inscription_number(inscription_id), 0); } } @@ -3541,15 +3403,7 @@ mod tests { assert_eq!(context.rpc_server.height(), 109); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0, witness)], @@ -3562,15 +3416,7 @@ mod tests { assert_eq!(context.rpc_server.height(), 110); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); + assert_eq!(context.index.inscription_number(inscription_id), 0); } } @@ -3596,15 +3442,7 @@ mod tests { context.mine_blocks(1); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -3624,15 +3462,7 @@ mod tests { context.mine_blocks(1); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -3661,15 +3491,7 @@ mod tests { context.mine_blocks(1); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -3699,15 +3521,7 @@ mod tests { context.mine_blocks(1); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -3743,15 +3557,7 @@ mod tests { None, ); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); + assert_eq!(context.index.inscription_number(inscription_id), 0); } } @@ -3789,15 +3595,7 @@ mod tests { None, ); - assert_eq!( - context - .index - .get_inscription_entry(second_inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(second_inscription_id), -1); } } @@ -3852,35 +3650,9 @@ mod tests { Some(150 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(first) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); - - assert_eq!( - context - .index - .get_inscription_entry(second) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); - - assert_eq!( - context - .index - .get_inscription_entry(third) - .unwrap() - .unwrap() - .inscription_number, - -2 - ); + assert_eq!(context.index.inscription_number(first), 0); + assert_eq!(context.index.inscription_number(second), -1); + assert_eq!(context.index.inscription_number(third), -2); } } @@ -3956,35 +3728,9 @@ mod tests { Some(50 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(first) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); - - assert_eq!( - context - .index - .get_inscription_entry(second) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); - - assert_eq!( - context - .index - .get_inscription_entry(third) - .unwrap() - .unwrap() - .inscription_number, - -2 - ); + assert_eq!(context.index.inscription_number(first), 0); + assert_eq!(context.index.inscription_number(second), -1); + assert_eq!(context.index.inscription_number(third), -2); } } @@ -4076,15 +3822,7 @@ mod tests { Some(150 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(first) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); + assert_eq!(context.index.inscription_number(first), 0); assert_eq!( context @@ -4095,30 +3833,14 @@ mod tests { fourth ); - assert_eq!( - context - .index - .get_inscription_entry(fourth) - .unwrap() - .unwrap() - .inscription_number, - -3 - ); + assert_eq!(context.index.inscription_number(fourth), -3); - assert_eq!( - context - .index - .get_inscription_entry(ninth) - .unwrap() - .unwrap() - .inscription_number, - -8 - ); + assert_eq!(context.index.inscription_number(ninth), -8); } } #[test] - fn genesis_fee_distributed_evenly() { + fn inscription_fee_distributed_evenly() { for context in Context::configurations() { context.rpc_server.mine_blocks(1); @@ -4217,15 +3939,7 @@ mod tests { Some(100 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(cursed) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(cursed), -1); let witness = envelope(&[ b"ord", @@ -4253,15 +3967,7 @@ mod tests { Some(100 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(reinscription_on_cursed) - .unwrap() - .unwrap() - .inscription_number, - 1 - ); + assert_eq!(context.index.inscription_number(reinscription_on_cursed), 1); } } @@ -4298,15 +4004,7 @@ mod tests { Some(100 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(cursed) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(cursed), -1); let witness = envelope(&[ b"ord", @@ -4334,15 +4032,7 @@ mod tests { Some(100 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(reinscription_on_cursed) - .unwrap() - .unwrap() - .inscription_number, - 1 - ); + assert_eq!(context.index.inscription_number(reinscription_on_cursed), 1); let witness = envelope(&[ b"ord", @@ -4373,10 +4063,7 @@ mod tests { assert_eq!( context .index - .get_inscription_entry(second_reinscription_on_cursed) - .unwrap() - .unwrap() - .inscription_number, + .inscription_number(second_reinscription_on_cursed), -2 ); @@ -5104,15 +4791,7 @@ mod tests { Some(50 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(inscription_id), -1); } } @@ -5168,15 +4847,7 @@ mod tests { Some(50 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(child_inscription_id) - .unwrap() - .unwrap() - .inscription_number, - -1 - ); + assert_eq!(context.index.inscription_number(child_inscription_id), -1); assert_eq!( context @@ -5521,23 +5192,10 @@ mod tests { Some(50 * COIN_VALUE), ); - assert_eq!( - context - .index - .get_inscription_entry(inscription_id) - .unwrap() - .unwrap() - .inscription_number, - 0 - ); + assert_eq!(context.index.inscription_number(inscription_id), 0); assert_eq!( - context - .index - .get_inscription_entry(cursed_reinscription_id) - .unwrap() - .unwrap() - .inscription_number, + context.index.inscription_number(cursed_reinscription_id), -1 ); } @@ -5827,4 +5485,290 @@ mod tests { assert_eq!(sat, entry.sat); } } + + #[test] + fn index_spent_sats_retains_spent_sat_range_entries() { + let ranges = { + let context = Context::builder().arg("--index-sats").build(); + + context.mine_blocks(1); + + let outpoint = OutPoint { + txid: context.rpc_server.tx(1, 0).into(), + vout: 0, + }; + + let ranges = context.index.list(outpoint).unwrap().unwrap(); + + assert!(!ranges.is_empty()); + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks(1); + + assert!(context.index.list(outpoint).unwrap().is_none()); + + ranges + }; + + { + let context = Context::builder() + .arg("--index-sats") + .arg("--index-spent-sats") + .build(); + + context.mine_blocks(1); + + let outpoint = OutPoint { + txid: context.rpc_server.tx(1, 0).into(), + vout: 0, + }; + + let unspent_ranges = context.index.list(outpoint).unwrap().unwrap(); + + assert!(!unspent_ranges.is_empty()); + + assert_eq!(unspent_ranges, ranges); + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks(1); + + let spent_ranges = context.index.list(outpoint).unwrap().unwrap(); + + assert_eq!(spent_ranges, ranges); + } + } + + #[test] + fn index_spent_sats_implies_index_sats() { + let context = Context::builder().arg("--index-spent-sats").build(); + + context.mine_blocks(1); + + let outpoint = OutPoint { + txid: context.rpc_server.tx(1, 0).into(), + vout: 0, + }; + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks(1); + + assert!(context.index.list(outpoint).unwrap().is_some()); + } + + #[test] + fn spent_sats_are_retained_after_flush() { + let context = Context::builder().arg("--index-spent-sats").build(); + + context.mine_blocks(1); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks_with_update(1, false); + + let outpoint = OutPoint { txid, vout: 0 }; + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks(1); + + assert!(context.index.list(outpoint).unwrap().is_some()); + } + + #[test] + fn is_output_spent() { + let context = Context::builder().build(); + + assert!(!context.index.is_output_spent(OutPoint::null()).unwrap()); + assert!(!context + .index + .is_output_spent(Chain::Mainnet.genesis_coinbase_outpoint()) + .unwrap()); + + context.mine_blocks(1); + + assert!(!context + .index + .is_output_spent(OutPoint { + txid: context.rpc_server.tx(1, 0).txid(), + vout: 0, + }) + .unwrap()); + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + ..Default::default() + }); + + context.mine_blocks(1); + + assert!(context + .index + .is_output_spent(OutPoint { + txid: context.rpc_server.tx(1, 0).txid(), + vout: 0, + }) + .unwrap()); + } + + #[test] + fn is_output_in_active_chain() { + let context = Context::builder().build(); + + assert!(context + .index + .is_output_in_active_chain(OutPoint::null()) + .unwrap()); + + assert!(context + .index + .is_output_in_active_chain(Chain::Mainnet.genesis_coinbase_outpoint()) + .unwrap()); + + context.mine_blocks(1); + + assert!(context + .index + .is_output_in_active_chain(OutPoint { + txid: context.rpc_server.tx(1, 0).txid(), + vout: 0, + }) + .unwrap()); + + assert!(!context + .index + .is_output_in_active_chain(OutPoint { + txid: context.rpc_server.tx(1, 0).txid(), + vout: 1, + }) + .unwrap()); + + assert!(!context + .index + .is_output_in_active_chain(OutPoint { + txid: Txid::all_zeros(), + vout: 0, + }) + .unwrap()); + } + + #[test] + fn fee_spent_inscriptions_are_numbered_last_in_block() { + for context in Context::configurations() { + context.mine_blocks(2); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + fee: 50 * COIN_VALUE, + ..Default::default() + }); + + let a = InscriptionId { txid, index: 0 }; + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + let b = InscriptionId { txid, index: 0 }; + + context.mine_blocks(1); + + assert_eq!(context.index.inscription_number(a), 1); + assert_eq!(context.index.inscription_number(b), 0); + } + } + + #[test] + fn event_sender_channel() { + let (event_sender, mut event_receiver) = tokio::sync::mpsc::channel(1024); + let context = Context::builder().event_sender(event_sender).build(); + + context.mine_blocks(1); + + let inscription = Inscription::default(); + let create_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + fee: 0, + outputs: 1, + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { + txid: create_txid, + index: 0, + }; + let create_event = event_receiver.blocking_recv().unwrap(); + let expected_charms = if context.index.index_sats { 513 } else { 0 }; + assert_eq!( + create_event, + Event::InscriptionCreated { + inscription_id, + location: Some(SatPoint { + outpoint: OutPoint { + txid: create_txid, + vout: 0 + }, + offset: 0 + }), + sequence_number: 0, + block_height: 2, + charms: expected_charms, + parent_inscription_id: None + } + ); + + // Transfer inscription + let transfer_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0, Default::default())], + fee: 0, + outputs: 1, + ..Default::default() + }); + + context.mine_blocks(1); + + let transfer_event = event_receiver.blocking_recv().unwrap(); + assert_eq!( + transfer_event, + Event::InscriptionTransferred { + block_height: 3, + inscription_id, + new_location: SatPoint { + outpoint: OutPoint { + txid: transfer_txid, + vout: 0 + }, + offset: 0 + }, + old_location: SatPoint { + outpoint: OutPoint { + txid: create_txid, + vout: 0 + }, + offset: 0 + }, + sequence_number: 0, + } + ); + } } diff --git a/src/index/entry.rs b/src/index/entry.rs index 116ee448ea..91923082fe 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -31,11 +31,9 @@ impl Entry for Header { #[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] pub struct RuneEntry { pub burned: u128, - pub deadline: Option, pub divisibility: u8, - pub end: Option, pub etching: Txid, - pub limit: Option, + pub mint: Option, pub mints: u64, pub number: u64, pub rune: Rune, @@ -46,21 +44,30 @@ pub struct RuneEntry { } pub(super) type RuneEntryValue = ( - u128, // burned + u128, // burned + u8, // divisibility + (u128, u128), // etching + Option, // mint parameters + u64, // mints + u64, // number + u128, // rune + u32, // spacers + u128, // supply + Option, // symbol + u32, // timestamp +); + +#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize, Default)] +pub struct MintEntry { + pub deadline: Option, + pub end: Option, + pub limit: Option, +} + +type MintEntryValue = ( Option, // deadline - u8, // divisibility Option, // end - (u128, u128), // etching Option, // limit - ( - u64, // mints - u64, // number - ), - u128, // rune - u32, // spacers - u128, // supply - Option, // symbol - u32, // timestamp ); impl RuneEntry { @@ -76,11 +83,9 @@ impl Default for RuneEntry { fn default() -> Self { Self { burned: 0, - deadline: None, divisibility: 0, - end: None, etching: Txid::all_zeros(), - limit: None, + mint: None, mints: 0, number: 0, rune: Rune(0), @@ -98,12 +103,11 @@ impl Entry for RuneEntry { fn load( ( burned, - deadline, divisibility, - end, etching, - limit, - (mints, number), + mint, + mints, + number, rune, spacers, supply, @@ -113,9 +117,7 @@ impl Entry for RuneEntry { ) -> Self { Self { burned, - deadline, divisibility, - end, etching: { let low = etching.0.to_le_bytes(); let high = etching.1.to_le_bytes(); @@ -126,7 +128,11 @@ impl Entry for RuneEntry { high[14], high[15], ]) }, - limit, + mint: mint.map(|(deadline, end, limit)| MintEntry { + deadline, + end, + limit, + }), mints, number, rune: Rune(rune), @@ -140,9 +146,7 @@ impl Entry for RuneEntry { fn store(self) -> Self::Value { ( self.burned, - self.deadline, self.divisibility, - self.end, { let bytes = self.etching.to_byte_array(); ( @@ -156,8 +160,15 @@ impl Entry for RuneEntry { ]), ) }, - self.limit, - (self.mints, self.number), + self.mint.map( + |MintEntry { + deadline, + end, + limit, + }| (deadline, end, limit), + ), + self.mints, + self.number, self.rune.0, self.spacers, self.supply, @@ -315,7 +326,7 @@ impl Entry for OutPoint { type Value = OutPointValue; fn load(value: Self::Value) -> Self { - Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() + Decodable::consensus_decode(&mut Cursor::new(value)).unwrap() } fn store(self) -> Self::Value { @@ -331,7 +342,7 @@ impl Entry for SatPoint { type Value = SatPointValue; fn load(value: Self::Value) -> Self { - Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() + Decodable::consensus_decode(&mut Cursor::new(value)).unwrap() } fn store(self) -> Self::Value { @@ -434,15 +445,17 @@ mod tests { fn rune_entry() { let entry = RuneEntry { burned: 1, - deadline: Some(2), divisibility: 3, - end: Some(4), etching: Txid::from_byte_array([ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, ]), - limit: Some(5), + mint: Some(MintEntry { + deadline: Some(2), + end: Some(4), + limit: Some(5), + }), mints: 11, number: 6, rune: Rune(7), @@ -454,15 +467,14 @@ mod tests { let value = ( 1, - Some(2), 3, - Some(4), ( 0x0F0E0D0C0B0A09080706050403020100, 0x1F1E1D1C1B1A19181716151413121110, ), - Some(5), - (11, 6), + Some((Some(2), Some(4), Some(5))), + 11, + 6, 7, 8, 9, diff --git a/src/index/event.rs b/src/index/event.rs new file mode 100644 index 0000000000..c650691158 --- /dev/null +++ b/src/index/event.rs @@ -0,0 +1,20 @@ +use crate::{InscriptionId, SatPoint}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + InscriptionCreated { + block_height: u32, + charms: u16, + inscription_id: InscriptionId, + location: Option, + parent_inscription_id: Option, + sequence_number: u32, + }, + InscriptionTransferred { + block_height: u32, + inscription_id: InscriptionId, + new_location: SatPoint, + old_location: SatPoint, + sequence_number: u32, + }, +} diff --git a/src/index/fetcher.rs b/src/index/fetcher.rs index 46d160da4b..4fcf1dfc0f 100644 --- a/src/index/fetcher.rs +++ b/src/index/fetcher.rs @@ -25,18 +25,18 @@ struct JsonError { } impl Fetcher { - pub(crate) fn new(options: &Options) -> Result { + pub(crate) fn new(settings: &Settings) -> Result { let client = Client::new(); - let url = if options.rpc_url(None).starts_with("http://") { - options.rpc_url(None) + let url = if settings.bitcoin_rpc_url(None).starts_with("http://") { + settings.bitcoin_rpc_url(None) } else { - "http://".to_string() + &options.rpc_url(None) + "http://".to_string() + &settings.bitcoin_rpc_url(None) }; let url = Uri::try_from(&url).map_err(|e| anyhow!("Invalid rpc url {url}: {e}"))?; - let (user, password) = options.auth()?.get_user_pass()?; + let (user, password) = settings.bitcoin_credentials()?.get_user_pass()?; let auth = format!("{}:{}", user.unwrap(), password.unwrap()); let auth = format!( "Basic {}", @@ -79,10 +79,7 @@ impl Fetcher { log::info!("failed to fetch raw transactions, retrying: {}", error); - tokio::time::sleep(tokio::time::Duration::from_millis( - 100 * u64::pow(2, retries), - )) - .await; + tokio::time::sleep(Duration::from_millis(100 * u64::pow(2, retries))).await; retries += 1; continue; } @@ -113,7 +110,7 @@ impl Fetcher { .map_err(|e| anyhow!("Result for batched JSON-RPC response not valid hex: {e}")) }) .and_then(|hex| { - bitcoin::consensus::deserialize(&hex).map_err(|e| { + consensus::deserialize(&hex).map_err(|e| { anyhow!("Result for batched JSON-RPC response not valid bitcoin tx: {e}") }) }) diff --git a/src/index/reorg.rs b/src/index/reorg.rs index e9db3471e7..4004226a71 100644 --- a/src/index/reorg.rs +++ b/src/index/reorg.rs @@ -6,8 +6,8 @@ pub(crate) enum ReorgError { Unrecoverable, } -impl fmt::Display for ReorgError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for ReorgError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { ReorgError::Recoverable { height, depth } => { write!(f, "{depth} block deep reorg detected at height {height}") @@ -72,7 +72,7 @@ impl Reorg { log::info!( "successfully rolled back database to height {}", - index.block_count()? + index.begin_read()?.block_count()? ); Ok(()) @@ -86,7 +86,7 @@ impl Reorg { if (height < SAVEPOINT_INTERVAL || height % SAVEPOINT_INTERVAL == 0) && u32::try_from( index - .options + .settings .bitcoin_rpc_client(None)? .get_blockchain_info()? .headers, diff --git a/src/index/rtx.rs b/src/index/rtx.rs index de7a712952..fb02a06453 100644 --- a/src/index/rtx.rs +++ b/src/index/rtx.rs @@ -10,7 +10,7 @@ impl Rtx<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| Height(height.value())), ) } @@ -22,7 +22,7 @@ impl Rtx<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0), ) diff --git a/src/index/testing.rs b/src/index/testing.rs index 462f07336c..7788480a02 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -1,8 +1,9 @@ -use super::*; +use {super::*, std::ffi::OsString, tempfile::TempDir}; pub(crate) struct ContextBuilder { args: Vec, chain: Chain, + event_sender: Option>, tempdir: Option, } @@ -22,7 +23,7 @@ impl ContextBuilder { let command: Vec = vec![ "ord".into(), - "--rpc-url".into(), + "--bitcoin-rpc-url".into(), rpc_server.url().into(), "--data-dir".into(), tempdir.path().into(), @@ -32,14 +33,16 @@ impl ContextBuilder { ]; let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); - let index = Index::open(&options)?; + let index = Index::open_with_event_sender( + &Settings::from_options(options).or_defaults().unwrap(), + self.event_sender, + )?; index.update().unwrap(); Ok(Context { - options, + index, rpc_server, tempdir, - index, }) } @@ -62,28 +65,39 @@ impl ContextBuilder { self.tempdir = Some(tempdir); self } + + pub(crate) fn event_sender(mut self, sender: tokio::sync::mpsc::Sender) -> Self { + self.event_sender = Some(sender); + self + } } pub(crate) struct Context { - pub(crate) options: Options, + pub(crate) index: Index, pub(crate) rpc_server: test_bitcoincore_rpc::Handle, #[allow(unused)] pub(crate) tempdir: TempDir, - pub(crate) index: Index, } impl Context { pub(crate) fn builder() -> ContextBuilder { ContextBuilder { args: Vec::new(), - tempdir: None, chain: Chain::Regtest, + event_sender: None, + tempdir: None, } } pub(crate) fn mine_blocks(&self, n: u64) -> Vec { + self.mine_blocks_with_update(n, true) + } + + pub(crate) fn mine_blocks_with_update(&self, n: u64, update: bool) -> Vec { let blocks = self.rpc_server.mine_blocks(n); - self.index.update().unwrap(); + if update { + self.index.update().unwrap(); + } blocks } @@ -116,9 +130,9 @@ impl Context { balances.sort_by_key(|(id, _)| *id); } - assert_eq!(runes, self.index.runes().unwrap()); + pretty_assert_eq!(runes, self.index.runes().unwrap()); - assert_eq!(balances, self.index.get_rune_balances().unwrap()); + pretty_assert_eq!(balances, self.index.get_rune_balances().unwrap()); let mut outstanding: HashMap = HashMap::new(); @@ -129,7 +143,7 @@ impl Context { } for (id, entry) in runes { - assert_eq!( + pretty_assert_eq!( outstanding.get(id).copied().unwrap_or_default(), entry.supply - entry.burned ); diff --git a/src/index/updater.rs b/src/index/updater.rs index 7aa89768b8..aa3697d2b0 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -31,31 +31,20 @@ impl From for BlockData { } pub(crate) struct Updater<'index> { - range_cache: HashMap>, - height: u32, - index: &'index Index, - sat_ranges_since_flush: u64, - outputs_cached: u64, - outputs_inserted_since_flush: u64, - outputs_traversed: u64, + pub(super) height: u32, + pub(super) index: &'index Index, + pub(super) outputs_cached: u64, + pub(super) outputs_inserted_since_flush: u64, + pub(super) outputs_traversed: u64, + pub(super) range_cache: HashMap>, + pub(super) sat_ranges_since_flush: u64, } -impl<'index> Updater<'_> { - pub(crate) fn new(index: &'index Index) -> Result> { - Ok(Updater { - range_cache: HashMap::new(), - height: index.block_count()?, - index, - sat_ranges_since_flush: 0, - outputs_cached: 0, - outputs_inserted_since_flush: 0, - outputs_traversed: 0, - }) - } - - pub(crate) fn update_index(&mut self) -> Result { - let mut wtx = self.index.begin_write()?; +impl<'index> Updater<'index> { + pub(crate) fn update_index<'a>(&'a mut self, mut wtx: WriteTransaction<'a>) -> Result { + let start = Instant::now(); let starting_height = u32::try_from(self.index.client.get_block_count()?).unwrap() + 1; + let starting_index_height = self.height; wtx .open_table(WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP)? @@ -70,7 +59,7 @@ impl<'index> Updater<'_> { let mut progress_bar = if cfg!(test) || log_enabled!(log::Level::Info) || starting_height <= self.height - || integration_test() + || self.index.settings.integration_test() { None } else { @@ -84,13 +73,12 @@ impl<'index> Updater<'_> { let rx = Self::fetch_blocks_from(self.index, self.height, self.index.index_sats)?; - let (mut outpoint_sender, mut value_receiver) = Self::spawn_fetcher(self.index)?; + let (mut outpoint_sender, mut value_receiver) = Self::spawn_fetcher(&self.index.settings)?; let mut uncommitted = 0; let mut value_cache = HashMap::new(); while let Ok(block) = rx.recv() { self.index_block( - self.index, &mut outpoint_sender, &mut value_receiver, &mut wtx, @@ -112,7 +100,7 @@ impl<'index> Updater<'_> { uncommitted += 1; - if uncommitted == 5000 { + if uncommitted == self.index.settings.commit_interval() { self.commit(wtx, value_cache)?; value_cache = HashMap::new(); uncommitted = 0; @@ -121,7 +109,7 @@ impl<'index> Updater<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _hash)| height.value() + 1) .unwrap_or(0); if height != self.height { @@ -134,9 +122,8 @@ impl<'index> Updater<'_> { .insert( &self.height, &SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map(|duration| duration.as_millis()) - .unwrap_or(0), + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis(), )?; } @@ -145,6 +132,13 @@ impl<'index> Updater<'_> { } } + if starting_index_height == 0 && self.height > 0 { + wtx.open_table(STATISTIC_TO_COUNT)?.insert( + Statistic::InitialSyncTime.key(), + &u64::try_from(start.elapsed().as_micros())?, + )?; + } + if uncommitted > 0 { self.commit(wtx, value_cache)?; } @@ -165,7 +159,7 @@ impl<'index> Updater<'_> { let height_limit = index.height_limit; - let client = index.options.bitcoin_rpc_client(None)?; + let client = index.settings.bitcoin_rpc_client(None)?; let first_inscription_height = index.first_inscription_height; @@ -241,8 +235,8 @@ impl<'index> Updater<'_> { } } - fn spawn_fetcher(index: &Index) -> Result<(Sender, Receiver)> { - let fetcher = Fetcher::new(&index.options)?; + fn spawn_fetcher(settings: &Settings) -> Result<(Sender, Receiver)> { + let fetcher = Fetcher::new(settings)?; // Not sure if any block has more than 20k inputs, but none so far after first inscription block const CHANNEL_BUFFER_SIZE: usize = 20_000; @@ -258,7 +252,7 @@ impl<'index> Updater<'_> { // else runs a request, we keep this to 12. const PARALLEL_REQUESTS: usize = 12; - std::thread::spawn(move || { + thread::spawn(move || { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -312,7 +306,6 @@ impl<'index> Updater<'_> { fn index_block( &mut self, - index: &Index, outpoint_sender: &mut Sender, value_receiver: &mut Receiver, wtx: &mut WriteTransaction, @@ -340,8 +333,8 @@ impl<'index> Updater<'_> { let mut outpoint_to_value = wtx.open_table(OUTPOINT_TO_VALUE)?; - let index_inscriptions = - self.height >= index.first_inscription_height && !index.options.no_index_inscriptions; + let index_inscriptions = self.height >= self.index.first_inscription_height + && self.index.settings.index_inscriptions(); if index_inscriptions { // Send all missing input outpoints to be fetched right away @@ -377,6 +370,7 @@ impl<'index> Updater<'_> { } } + let mut content_type_to_count = wtx.open_table(CONTENT_TYPE_TO_COUNT)?; let mut height_to_block_header = wtx.open_table(HEIGHT_TO_BLOCK_HEADER)?; let mut height_to_last_sequence_number = wtx.open_table(HEIGHT_TO_LAST_SEQUENCE_NUMBER)?; let mut home_inscriptions = wtx.open_table(HOME_INSCRIPTIONS)?; @@ -416,7 +410,7 @@ impl<'index> Updater<'_> { let next_sequence_number = sequence_number_to_inscription_entry .iter()? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(number, _id)| number.value() + 1) .unwrap_or(0); @@ -424,8 +418,10 @@ impl<'index> Updater<'_> { let mut inscription_updater = InscriptionUpdater { blessed_inscription_count, - chain: self.index.options.chain(), + chain: self.index.settings.chain(), + content_type_to_count: &mut content_type_to_count, cursed_inscription_count, + event_sender: self.index.event_sender.as_ref(), flotsam: Vec::new(), height: self.height, home_inscription_count, @@ -471,16 +467,23 @@ impl<'index> Updater<'_> { for input in &tx.input { let key = input.previous_output.store(); - let sat_ranges = match self.range_cache.remove(&key) { + let sat_ranges = match if self.index.index_spent_sats { + self.range_cache.get(&key).cloned() + } else { + self.range_cache.remove(&key) + } { Some(sat_ranges) => { self.outputs_cached += 1; sat_ranges } - None => outpoint_to_sat_ranges - .remove(&key)? - .ok_or_else(|| anyhow!("Could not find outpoint {} in index", input.previous_output))? - .value() - .to_vec(), + None => if self.index.index_spent_sats { + outpoint_to_sat_ranges.get(&key)? + } else { + outpoint_to_sat_ranges.remove(&key)? + } + .ok_or_else(|| anyhow!("Could not find outpoint {} in index", input.previous_output))? + .value() + .to_vec(), }; for chunk in sat_ranges.chunks_exact(11) { @@ -542,7 +545,7 @@ impl<'index> Updater<'_> { } } else if index_inscriptions { for (tx, txid) in block.txdata.iter().skip(1).chain(block.txdata.first()) { - inscription_updater.index_envelopes(tx, *txid, None)?; + inscription_updater.index_inscriptions(tx, *txid, None)?; } } @@ -575,7 +578,7 @@ impl<'index> Updater<'_> { &inscription_updater.unbound_inscriptions, )?; - if index.index_runes && self.height >= self.index.options.first_rune_height() { + if self.index.index_runes && self.height >= self.index.settings.first_rune_height() { let mut outpoint_to_rune_balances = wtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?; let mut rune_id_to_rune_entry = wtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?; let mut rune_to_rune_id = wtx.open_table(RUNE_TO_RUNE_ID)?; @@ -591,7 +594,7 @@ impl<'index> Updater<'_> { height: self.height, id_to_entry: &mut rune_id_to_rune_entry, inscription_id_to_sequence_number: &mut inscription_id_to_sequence_number, - minimum: Rune::minimum_at_height(self.index.options.chain(), Height(self.height)), + minimum: Rune::minimum_at_height(self.index.settings.chain(), Height(self.height)), outpoint_to_balances: &mut outpoint_to_rune_balances, rune_to_id: &mut rune_to_rune_id, runes, @@ -647,7 +650,7 @@ impl<'index> Updater<'_> { index_inscriptions: bool, ) -> Result { if index_inscriptions { - inscription_updater.index_envelopes(tx, txid, Some(input_sat_ranges))?; + inscription_updater.index_inscriptions(tx, txid, Some(input_sat_ranges))?; } for (vout, output) in tx.output.iter().enumerate() { @@ -720,8 +723,8 @@ impl<'index> Updater<'_> { let mut outpoint_to_sat_ranges = wtx.open_table(OUTPOINT_TO_SAT_RANGES)?; - for (outpoint, sat_range) in self.range_cache.drain() { - outpoint_to_sat_ranges.insert(&outpoint, sat_range.as_slice())?; + for (outpoint, sat_ranges) in self.range_cache.drain() { + outpoint_to_sat_ranges.insert(&outpoint, sat_ranges.as_slice())?; } self.outputs_inserted_since_flush = 0; diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 19f99033cc..ee10151f1e 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -40,7 +40,9 @@ enum Origin { pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { pub(super) blessed_inscription_count: u64, pub(super) chain: Chain, + pub(super) content_type_to_count: &'a mut Table<'db, 'tx, Option<&'static [u8]>, u64>, pub(super) cursed_inscription_count: u64, + pub(super) event_sender: Option<&'a Sender>, pub(super) flotsam: Vec, pub(super) height: u32, pub(super) home_inscription_count: u64, @@ -68,7 +70,7 @@ pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { } impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { - pub(super) fn index_envelopes( + pub(super) fn index_inscriptions( &mut self, tx: &Transaction, txid: Txid, @@ -188,16 +190,24 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { None }; - let unbound = current_input_value == 0 - || curse == Some(Curse::UnrecognizedEvenField) - || inscription.payload.unrecognized_even_field; - let offset = inscription .payload .pointer() .filter(|&pointer| pointer < total_output_value) .unwrap_or(offset); + let content_type = inscription.payload.content_type.as_deref(); + + let content_type_count = self + .content_type_to_count + .get(content_type)? + .map(|entry| entry.value()) + .unwrap_or_default(); + + self + .content_type_to_count + .insert(content_type, content_type_count + 1)?; + floating_inscriptions.push(Flotsam { inscription_id, offset, @@ -208,7 +218,9 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { parent: inscription.payload.parent(), pointer: inscription.payload.pointer(), reinscription: inscribed_offsets.get(&offset).is_some(), - unbound, + unbound: current_input_value == 0 + || curse == Some(Curse::UnrecognizedEvenField) + || inscription.payload.unrecognized_even_field, vindicated: curse.is_some() && jubilant, }, }); @@ -389,14 +401,23 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { .satpoint_to_sequence_number .remove_all(&old_satpoint.store())?; - ( - false, - self - .id_to_sequence_number - .get(&inscription_id.store())? - .unwrap() - .value(), - ) + let sequence_number = self + .id_to_sequence_number + .get(&inscription_id.store())? + .unwrap() + .value(); + + if let Some(sender) = self.event_sender { + sender.blocking_send(Event::InscriptionTransferred { + block_height: self.height, + inscription_id, + new_location: new_satpoint, + old_location: old_satpoint, + sequence_number, + })?; + } + + (false, sequence_number) } Origin::New { cursed, @@ -411,13 +432,10 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { let inscription_number = if cursed { let number: i32 = self.cursed_inscription_count.try_into().unwrap(); self.cursed_inscription_count += 1; - - // because cursed numbers start at -1 -(number + 1) } else { let number: i32 = self.blessed_inscription_count.try_into().unwrap(); self.blessed_inscription_count += 1; - number }; @@ -478,7 +496,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.sat_to_sequence_number.insert(&n, &sequence_number)?; } - let parent = match parent { + let parent_sequence_number = match parent { Some(parent_id) => { let parent_sequence_number = self .id_to_sequence_number @@ -494,6 +512,17 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { None => None, }; + if let Some(sender) = self.event_sender { + sender.blocking_send(Event::InscriptionCreated { + block_height: self.height, + charms, + inscription_id, + location: (!unbound).then_some(new_satpoint), + parent_inscription_id: parent, + sequence_number, + })?; + } + self.sequence_number_to_entry.insert( sequence_number, &InscriptionEntry { @@ -502,7 +531,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { height: self.height, id: inscription_id, inscription_number, - parent, + parent: parent_sequence_number, sat, sequence_number, timestamp: self.timestamp, diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index c3ec79e410..1a1096a1d4 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -1,19 +1,18 @@ use { super::*, - crate::runes::{varint, Edict, Runestone, CLAIM_BIT}, + crate::runes::{varint, Edict, Runestone}, }; -fn claim(id: u128) -> Option { - (id & CLAIM_BIT != 0).then_some(id ^ CLAIM_BIT) +struct Claim { + id: u128, + limit: u128, } -struct Allocation { +struct Etched { balance: u128, - deadline: Option, divisibility: u8, - end: Option, id: u128, - limit: Option, + mint: Option, rune: Rune, spacers: u32, symbol: Option, @@ -45,26 +44,7 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { pub(super) fn index_runes(&mut self, index: usize, tx: &Transaction, txid: Txid) -> Result<()> { let runestone = Runestone::from_transaction(tx); - // A mapping of rune ID to un-allocated balance of that rune - let mut unallocated: HashMap = HashMap::new(); - - // Increment unallocated runes with the runes in this transaction's inputs - for input in &tx.input { - if let Some(guard) = self - .outpoint_to_balances - .remove(&input.previous_output.store())? - { - let buffer = guard.value(); - let mut i = 0; - while i < buffer.len() { - let (id, len) = varint::decode(&buffer[i..]); - i += len; - let (balance, len) = varint::decode(&buffer[i..]); - i += len; - *unallocated.entry(id).or_default() += balance; - } - } - } + let mut unallocated = self.unallocated(tx)?; let burn = runestone .as_ref() @@ -77,111 +57,28 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { .and_then(|default| usize::try_from(default).ok()) }); - // A vector of allocated transaction output rune balances let mut allocated: Vec> = vec![HashMap::new(); tx.output.len()]; if let Some(runestone) = runestone { - // Determine if this runestone contains a valid issuance - let mut allocation = match runestone.etching { - Some(etching) => { - if etching - .rune - .map(|rune| rune < self.minimum || rune.is_reserved()) - .unwrap_or_default() - || etching - .rune - .and_then(|rune| self.rune_to_id.get(rune.0).transpose()) - .transpose()? - .is_some() - { - None - } else { - let rune = if let Some(rune) = etching.rune { - rune - } else { - let reserved_runes = self - .statistic_to_count - .get(&Statistic::ReservedRunes.into())? - .map(|entry| entry.value()) - .unwrap_or_default(); - - self - .statistic_to_count - .insert(&Statistic::ReservedRunes.into(), reserved_runes + 1)?; + if let Some(claim) = runestone + .claim + .and_then(|id| self.claim(id).transpose()) + .transpose()? + { + *unallocated.entry(claim.id).or_default() += claim.limit; - Rune::reserved(reserved_runes.into()) - }; + let update = self + .updates + .entry(RuneId::try_from(claim.id).unwrap()) + .or_default(); - let (limit, term) = match (etching.limit, etching.term) { - (None, Some(term)) => (Some(runes::MAX_LIMIT), Some(term)), - (limit, term) => (limit, term), - }; + update.mints += 1; + update.supply += claim.limit; + } - // Construct an allocation, representing the new runes that may be - // allocated. Beware: Because it would require constructing a block - // with 2**16 + 1 transactions, there is no test that checks that - // an eching in a transaction with an out-of-bounds index is - // ignored. - match u16::try_from(index) { - Ok(index) => Some(Allocation { - balance: if let Some(limit) = limit { - if term == Some(0) { - 0 - } else { - limit - } - } else { - u128::max_value() - }, - deadline: etching.deadline, - divisibility: etching.divisibility, - end: term.map(|term| term + self.height), - id: u128::from(self.height) << 16 | u128::from(index), - limit, - rune, - spacers: etching.spacers, - symbol: etching.symbol, - }), - Err(_) => None, - } - } - } - None => None, - }; + let mut etched = self.etched(index, &runestone)?; if !burn { - let mut mintable: HashMap = HashMap::new(); - - let mut claims = runestone - .edicts - .iter() - .filter_map(|edict| claim(edict.id)) - .collect::>(); - claims.sort(); - claims.dedup(); - for id in claims { - if let Ok(key) = RuneId::try_from(id) { - if let Some(entry) = self.id_to_entry.get(&key.store())? { - let entry = RuneEntry::load(entry.value()); - if let Some(limit) = entry.limit { - if let Some(end) = entry.end { - if self.height >= end { - continue; - } - } - if let Some(deadline) = entry.deadline { - if self.timestamp >= deadline { - continue; - } - } - mintable.insert(id, limit); - } - } - } - } - - let limits = mintable.clone(); - for Edict { id, amount, output } in runestone.edicts { let Ok(output) = usize::try_from(output) else { continue; @@ -197,13 +94,8 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { // if no issuance was present, or if the issuance was invalid. // Additionally, replace ID 0 with the newly assigned ID, and // get the unallocated balance of the issuance. - match allocation.as_mut() { - Some(Allocation { balance, id, .. }) => (balance, *id), - None => continue, - } - } else if let Some(claim) = claim(id) { - match mintable.get_mut(&claim) { - Some(balance) => (balance, claim), + match etched.as_mut() { + Some(Etched { balance, id, .. }) => (balance, *id), None => continue, } } else { @@ -261,79 +153,10 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { allocate(balance, amount, output); } } - - // increment entries with minted runes - for (id, amount) in mintable { - let minted = limits[&id] - amount; - if minted > 0 { - let update = self - .updates - .entry(RuneId::try_from(id).unwrap()) - .or_default(); - update.mints += 1; - update.supply += minted; - } - } } - if let Some(Allocation { - balance, - deadline, - divisibility, - end, - id, - limit, - rune, - spacers, - symbol, - }) = allocation - { - let id = RuneId::try_from(id).unwrap(); - self.rune_to_id.insert(rune.0, id.store())?; - self.transaction_id_to_rune.insert(&txid.store(), rune.0)?; - let number = self.runes; - self.runes += 1; - self - .statistic_to_count - .insert(&Statistic::Runes.into(), self.runes)?; - self.id_to_entry.insert( - id.store(), - RuneEntry { - burned: 0, - deadline: deadline.and_then(|deadline| (!burn).then_some(deadline)), - divisibility, - etching: txid, - mints: 0, - number, - rune, - spacers, - supply: if let Some(limit) = limit { - if end == Some(self.height) { - 0 - } else { - limit - } - } else { - u128::max_value() - } - balance, - end: end.and_then(|end| (!burn).then_some(end)), - symbol, - limit: limit.and_then(|limit| (!burn).then_some(limit)), - timestamp: self.timestamp, - } - .store(), - )?; - - let inscription_id = InscriptionId { txid, index: 0 }; - - if let Some(sequence_number) = self - .inscription_id_to_sequence_number - .get(&inscription_id.store())? - { - self - .sequence_number_to_rune_id - .insert(sequence_number.value(), id.store())?; - } + if let Some(etched) = etched { + self.create_rune_entry(txid, burn, etched)?; } } @@ -419,15 +242,186 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { Ok(()) } -} -#[cfg(test)] -mod tests { - use super::*; + fn create_rune_entry(&mut self, txid: Txid, burn: bool, etched: Etched) -> Result { + let Etched { + balance, + divisibility, + id, + mint, + rune, + spacers, + symbol, + } = etched; + + let id = RuneId::try_from(id).unwrap(); + self.rune_to_id.insert(rune.0, id.store())?; + self.transaction_id_to_rune.insert(&txid.store(), rune.0)?; + let number = self.runes; + self.runes += 1; + self + .statistic_to_count + .insert(&Statistic::Runes.into(), self.runes)?; + self.id_to_entry.insert( + id.store(), + RuneEntry { + burned: 0, + divisibility, + etching: txid, + mints: 0, + number, + mint: mint.and_then(|mint| (!burn).then_some(mint)), + rune, + spacers, + supply: if let Some(mint) = mint { + if mint.end == Some(self.height) { + 0 + } else { + mint.limit.unwrap_or(runes::MAX_LIMIT) + } + } else { + u128::MAX + } - balance, + symbol, + timestamp: self.timestamp, + } + .store(), + )?; + + let inscription_id = InscriptionId { txid, index: 0 }; + + if let Some(sequence_number) = self + .inscription_id_to_sequence_number + .get(&inscription_id.store())? + { + self + .sequence_number_to_rune_id + .insert(sequence_number.value(), id.store())?; + } + + Ok(()) + } + + fn etched(&mut self, index: usize, runestone: &Runestone) -> Result> { + let Some(etching) = runestone.etching else { + return Ok(None); + }; + + if etching + .rune + .map(|rune| rune < self.minimum || rune.is_reserved()) + .unwrap_or_default() + || etching + .rune + .and_then(|rune| self.rune_to_id.get(rune.0).transpose()) + .transpose()? + .is_some() + { + return Ok(None); + } + + let rune = if let Some(rune) = etching.rune { + rune + } else { + let reserved_runes = self + .statistic_to_count + .get(&Statistic::ReservedRunes.into())? + .map(|entry| entry.value()) + .unwrap_or_default(); + + self + .statistic_to_count + .insert(&Statistic::ReservedRunes.into(), reserved_runes + 1)?; + + Rune::reserved(reserved_runes.into()) + }; + + // Nota bene: Because it would require constructing a block + // with 2**16 + 1 transactions, there is no test that checks that + // an eching in a transaction with an out-of-bounds index is + // ignored. + let Ok(index) = u16::try_from(index) else { + return Ok(None); + }; + + Ok(Some(Etched { + balance: if let Some(mint) = etching.mint { + if mint.term == Some(0) { + 0 + } else { + mint.limit.unwrap_or(runes::MAX_LIMIT) + } + } else { + u128::MAX + }, + divisibility: etching.divisibility, + id: u128::from(self.height) << 16 | u128::from(index), + rune, + spacers: etching.spacers, + symbol: etching.symbol, + mint: etching.mint.map(|mint| MintEntry { + deadline: mint.deadline, + end: mint.term.map(|term| term + self.height), + limit: mint.limit.map(|limit| limit.min(runes::MAX_LIMIT)), + }), + })) + } + + fn claim(&self, id: u128) -> Result> { + let Ok(key) = RuneId::try_from(id) else { + return Ok(None); + }; + + let Some(entry) = self.id_to_entry.get(&key.store())? else { + return Ok(None); + }; + + let entry = RuneEntry::load(entry.value()); + + let Some(mint) = entry.mint else { + return Ok(None); + }; + + if let Some(end) = mint.end { + if self.height >= end { + return Ok(None); + } + } + + if let Some(deadline) = mint.deadline { + if self.timestamp >= deadline { + return Ok(None); + } + } + + Ok(Some(Claim { + id, + limit: mint.limit.unwrap_or(runes::MAX_LIMIT), + })) + } + + fn unallocated(&mut self, tx: &Transaction) -> Result> { + // map of rune ID to un-allocated balance of that rune + let mut unallocated: HashMap = HashMap::new(); + + // increment unallocated runes with the runes in tx inputs + for input in &tx.input { + if let Some(guard) = self + .outpoint_to_balances + .remove(&input.previous_output.store())? + { + let buffer = guard.value(); + let mut i = 0; + while i < buffer.len() { + let (id, len) = varint::decode(&buffer[i..]); + i += len; + let (balance, len) = varint::decode(&buffer[i..]); + i += len; + *unallocated.entry(id).or_default() += balance; + } + } + } - #[test] - fn claim_from_id() { - assert_eq!(claim(1), None); - assert_eq!(claim(1 | CLAIM_BIT), Some(1)); + Ok(unallocated) } } diff --git a/src/inscriptions.rs b/src/inscriptions.rs index 913609a1a1..1c32ff8ae6 100644 --- a/src/inscriptions.rs +++ b/src/inscriptions.rs @@ -9,7 +9,7 @@ pub use self::{envelope::Envelope, inscription::Inscription, inscription_id::Ins mod charm; mod envelope; mod inscription; -mod inscription_id; +pub(crate) mod inscription_id; pub(crate) mod media; mod tag; pub(crate) mod teleburn; diff --git a/src/inscriptions/charm.rs b/src/inscriptions/charm.rs index d0770886d7..9aea73e774 100644 --- a/src/inscriptions/charm.rs +++ b/src/inscriptions/charm.rs @@ -40,6 +40,10 @@ impl Charm { charms & self.flag() != 0 } + pub(crate) fn unset(self, charms: u16) -> u16 { + charms & !self.flag() + } + pub(crate) fn icon(self) -> &'static str { match self { Self::Coin => "🪙", @@ -81,3 +85,31 @@ impl Charm { .collect() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn flag() { + assert_eq!(Charm::Coin.flag(), 0b1); + assert_eq!(Charm::Cursed.flag(), 0b10); + } + + #[test] + fn set() { + let mut flags = 0; + assert!(!Charm::Coin.is_set(flags)); + Charm::Coin.set(&mut flags); + assert!(Charm::Coin.is_set(flags)); + } + + #[test] + fn unset() { + let mut flags = 0; + Charm::Coin.set(&mut flags); + assert!(Charm::Coin.is_set(flags)); + let flags = Charm::Coin.unset(flags); + assert!(!Charm::Coin.is_set(flags)); + } +} diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index 76836f7733..0ef863c831 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -283,11 +283,11 @@ mod tests { #[test] fn ignore_key_path_spends() { assert_eq!( - parse(&[Witness::from_slice(&[bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) + parse(&[Witness::from_slice(&[script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) .push_slice(PROTOCOL_ID) - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) + .push_opcode(opcodes::all::OP_ENDIF) .into_script() .into_bytes()])]), Vec::new() @@ -298,11 +298,11 @@ mod tests { fn ignore_key_path_spends_with_annex() { assert_eq!( parse(&[Witness::from_slice(&[ - bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) + script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) .push_slice(PROTOCOL_ID) - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) + .push_opcode(opcodes::all::OP_ENDIF) .into_script() .into_bytes(), vec![0x50] @@ -315,11 +315,11 @@ mod tests { fn parse_from_tapscript() { assert_eq!( parse(&[Witness::from_slice(&[ - bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) + script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) .push_slice(PROTOCOL_ID) - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) + .push_opcode(opcodes::all::OP_ENDIF) .into_script() .into_bytes(), Vec::new() @@ -332,11 +332,11 @@ mod tests { #[test] fn ignore_unparsable_scripts() { - let mut script_bytes = bitcoin::script::Builder::new() - .push_opcode(bitcoin::opcodes::OP_FALSE) - .push_opcode(bitcoin::opcodes::all::OP_IF) + let mut script_bytes = script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) .push_slice(PROTOCOL_ID) - .push_opcode(bitcoin::opcodes::all::OP_ENDIF) + .push_opcode(opcodes::all::OP_ENDIF) .into_script() .into_bytes(); script_bytes.push(0x01); diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 42eaa2d2b1..057f34f76e 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -2,10 +2,7 @@ use { super::*, anyhow::ensure, bitcoin::{ - blockdata::{ - opcodes, - script::{self, PushBytesBuf}, - }, + blockdata::{opcodes, script}, ScriptBuf, }, brotli::enc::{writer::CompressorWriter, BrotliEncoderParams}, @@ -41,12 +38,13 @@ impl Inscription { pub(crate) fn from_file( chain: Chain, - path: impl AsRef, + compress: bool, + delegate: Option, + metadata: Option>, + metaprotocol: Option, parent: Option, + path: impl AsRef, pointer: Option, - metaprotocol: Option, - metadata: Option>, - compress: bool, ) -> Result { let path = path.as_ref(); @@ -99,11 +97,12 @@ impl Inscription { Ok(Self { body: Some(body), - content_type: Some(content_type.into()), content_encoding, + content_type: Some(content_type.into()), + delegate: delegate.map(|delegate| delegate.value()), metadata, metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()), - parent: parent.map(|id| id.value()), + parent: parent.map(|parent| parent.value()), pointer: pointer.map(Self::pointer_value), ..Default::default() }) @@ -139,7 +138,7 @@ impl Inscription { if let Some(body) = &self.body { builder = builder.push_slice(envelope::BODY_TAG); for chunk in body.chunks(MAX_SCRIPT_ELEMENT_SIZE) { - builder = builder.push_slice(PushBytesBuf::try_from(chunk.to_vec()).unwrap()); + builder = builder.push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); } } @@ -290,27 +289,19 @@ impl Inscription { pub(crate) fn hidden(&self) -> bool { use regex::bytes::Regex; + const BVM_NETWORK: &[u8] = b"\ +

          bvm.network

          "; + lazy_static! { - static ref CONTENT: Regex = Regex::new(r"^\s*/content/[[:xdigit:]]{64}i\d+\s*$").unwrap(); + static ref BRC_420: Regex = Regex::new(r"^\s*/content/[[:xdigit:]]{64}i\d+\s*$").unwrap(); } - if self + self .body() - .map(|body| CONTENT.is_match(body)) + .map(|body| BRC_420.is_match(body) || body.starts_with(BVM_NETWORK)) .unwrap_or_default() - { - return true; - } - - if self.metaprotocol.is_some() { - return true; - } - - if let Media::Code(_) | Media::Text | Media::Unknown = self.media() { - return true; - } - - false + || self.metaprotocol.is_some() + || matches!(self.media(), Media::Code(_) | Media::Text | Media::Unknown) } } @@ -735,19 +726,29 @@ mod tests { write!(file, "foo").unwrap(); - let inscription = - Inscription::from_file(Chain::Mainnet, file.path(), None, None, None, None, false).unwrap(); + let inscription = Inscription::from_file( + Chain::Mainnet, + false, + None, + None, + None, + None, + file.path(), + None, + ) + .unwrap(); assert_eq!(inscription.pointer, None); let inscription = Inscription::from_file( Chain::Mainnet, - file.path(), + false, None, - Some(0), None, None, - false, + None, + file.path(), + Some(0), ) .unwrap(); @@ -755,12 +756,13 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, - file.path(), + false, None, - Some(1), None, None, - false, + None, + file.path(), + Some(1), ) .unwrap(); @@ -768,12 +770,13 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, - file.path(), + false, None, - Some(256), None, None, - false, + None, + file.path(), + Some(256), ) .unwrap(); @@ -827,6 +830,20 @@ mod tests { Some(" /content/09a8d837ec0bcaec668ecf405e696a16bee5990863659c224ff888fb6f8f45e7i0 \n"), true, ); + case( + Some("text/html"), + Some( + r#"

          bvm.network

          "#, + ), + true, + ); + case( + Some("text/html"), + Some( + r#"

          bvm.network

          foo"#, + ), + true, + ); assert!(Inscription { content_type: Some("text/plain".as_bytes().into()), diff --git a/src/inscriptions/inscription_id.rs b/src/inscriptions/inscription_id.rs index 5f3f70e225..773b0ac715 100644 --- a/src/inscriptions/inscription_id.rs +++ b/src/inscriptions/inscription_id.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)] +#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, PartialOrd, Ord)] pub struct InscriptionId { pub txid: Txid, pub index: u32, @@ -39,7 +39,7 @@ impl<'de> Deserialize<'de> for InscriptionId { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } diff --git a/src/inscriptions/media.rs b/src/inscriptions/media.rs index c4859bd38b..de41de2571 100644 --- a/src/inscriptions/media.rs +++ b/src/inscriptions/media.rs @@ -1,7 +1,8 @@ use { + self::{ImageRendering::*, Language::*, Media::*}, super::*, brotli::enc::backward_references::BrotliEncoderMode::{ - self, BROTLI_MODE_FONT, BROTLI_MODE_GENERIC, BROTLI_MODE_TEXT, + self, BROTLI_MODE_FONT as FONT, BROTLI_MODE_GENERIC as GENERIC, BROTLI_MODE_TEXT as TEXT, }, mp4::{MediaType, Mp4Reader, TrackType}, std::{fs::File, io::BufReader}, @@ -13,7 +14,7 @@ pub(crate) enum Media { Code(Language), Font, Iframe, - Image, + Image(ImageRendering), Markdown, Model, Pdf, @@ -47,45 +48,65 @@ impl Display for Language { } } +#[derive(Debug, PartialEq, Copy, Clone)] +pub(crate) enum ImageRendering { + Auto, + Pixelated, +} + +impl Display for ImageRendering { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::Auto => "auto", + Self::Pixelated => "pixelated", + } + ) + } +} + impl Media { #[rustfmt::skip] const TABLE: &'static [(&'static str, BrotliEncoderMode, Media, &'static [&'static str])] = &[ - ("application/cbor", BROTLI_MODE_GENERIC, Media::Unknown, &["cbor"]), - ("application/json", BROTLI_MODE_TEXT, Media::Code(Language::Json), &["json"]), - ("application/octet-stream", BROTLI_MODE_GENERIC, Media::Unknown, &["bin"]), - ("application/pdf", BROTLI_MODE_GENERIC, Media::Pdf, &["pdf"]), - ("application/pgp-signature", BROTLI_MODE_TEXT, Media::Text, &["asc"]), - ("application/protobuf", BROTLI_MODE_GENERIC, Media::Unknown, &["binpb"]), - ("application/x-javascript", BROTLI_MODE_TEXT, Media::Code(Language::JavaScript), &[]), - ("application/yaml", BROTLI_MODE_TEXT, Media::Code(Language::Yaml), &["yaml", "yml"]), - ("audio/flac", BROTLI_MODE_GENERIC, Media::Audio, &["flac"]), - ("audio/mpeg", BROTLI_MODE_GENERIC, Media::Audio, &["mp3"]), - ("audio/wav", BROTLI_MODE_GENERIC, Media::Audio, &["wav"]), - ("font/otf", BROTLI_MODE_GENERIC, Media::Font, &["otf"]), - ("font/ttf", BROTLI_MODE_GENERIC, Media::Font, &["ttf"]), - ("font/woff", BROTLI_MODE_GENERIC, Media::Font, &["woff"]), - ("font/woff2", BROTLI_MODE_FONT, Media::Font, &["woff2"]), - ("image/apng", BROTLI_MODE_GENERIC, Media::Image, &["apng"]), - ("image/avif", BROTLI_MODE_GENERIC, Media::Image, &[]), - ("image/gif", BROTLI_MODE_GENERIC, Media::Image, &["gif"]), - ("image/jpeg", BROTLI_MODE_GENERIC, Media::Image, &["jpg", "jpeg"]), - ("image/png", BROTLI_MODE_GENERIC, Media::Image, &["png"]), - ("image/svg+xml", BROTLI_MODE_TEXT, Media::Iframe, &["svg"]), - ("image/webp", BROTLI_MODE_GENERIC, Media::Image, &["webp"]), - ("model/gltf+json", BROTLI_MODE_TEXT, Media::Model, &["gltf"]), - ("model/gltf-binary", BROTLI_MODE_GENERIC, Media::Model, &["glb"]), - ("model/stl", BROTLI_MODE_GENERIC, Media::Unknown, &["stl"]), - ("text/css", BROTLI_MODE_TEXT, Media::Code(Language::Css), &["css"]), - ("text/html", BROTLI_MODE_TEXT, Media::Iframe, &[]), - ("text/html;charset=utf-8", BROTLI_MODE_TEXT, Media::Iframe, &["html"]), - ("text/javascript", BROTLI_MODE_TEXT, Media::Code(Language::JavaScript), &["js"]), - ("text/markdown", BROTLI_MODE_TEXT, Media::Markdown, &[]), - ("text/markdown;charset=utf-8", BROTLI_MODE_TEXT, Media::Markdown, &["md"]), - ("text/plain", BROTLI_MODE_TEXT, Media::Text, &[]), - ("text/plain;charset=utf-8", BROTLI_MODE_TEXT, Media::Text, &["txt"]), - ("text/x-python", BROTLI_MODE_TEXT, Media::Code(Language::Python), &["py"]), - ("video/mp4", BROTLI_MODE_GENERIC, Media::Video, &["mp4"]), - ("video/webm", BROTLI_MODE_GENERIC, Media::Video, &["webm"]), + ("application/cbor", GENERIC, Unknown, &["cbor"]), + ("application/json", TEXT, Code(Json), &["json"]), + ("application/octet-stream", GENERIC, Unknown, &["bin"]), + ("application/pdf", GENERIC, Pdf, &["pdf"]), + ("application/pgp-signature", TEXT, Text, &["asc"]), + ("application/protobuf", GENERIC, Unknown, &["binpb"]), + ("application/x-javascript", TEXT, Code(JavaScript), &[]), + ("application/yaml", TEXT, Code(Yaml), &["yaml", "yml"]), + ("audio/flac", GENERIC, Audio, &["flac"]), + ("audio/mpeg", GENERIC, Audio, &["mp3"]), + ("audio/wav", GENERIC, Audio, &["wav"]), + ("font/otf", GENERIC, Font, &["otf"]), + ("font/ttf", GENERIC, Font, &["ttf"]), + ("font/woff", GENERIC, Font, &["woff"]), + ("font/woff2", FONT, Font, &["woff2"]), + ("image/apng", GENERIC, Image(Pixelated), &["apng"]), + ("image/avif", GENERIC, Image(Auto), &["avif"]), + ("image/gif", GENERIC, Image(Pixelated), &["gif"]), + ("image/jpeg", GENERIC, Image(Pixelated), &["jpg", "jpeg"]), + ("image/jxl", GENERIC, Image(Auto), &[]), + ("image/png", GENERIC, Image(Pixelated), &["png"]), + ("image/svg+xml", TEXT, Iframe, &["svg"]), + ("image/webp", GENERIC, Image(Pixelated), &["webp"]), + ("model/gltf+json", TEXT, Model, &["gltf"]), + ("model/gltf-binary", GENERIC, Model, &["glb"]), + ("model/stl", GENERIC, Unknown, &["stl"]), + ("text/css", TEXT, Code(Css), &["css"]), + ("text/html", TEXT, Iframe, &[]), + ("text/html;charset=utf-8", TEXT, Iframe, &["html"]), + ("text/javascript", TEXT, Code(JavaScript), &["js"]), + ("text/markdown", TEXT, Markdown, &[]), + ("text/markdown;charset=utf-8", TEXT, Markdown, &["md"]), + ("text/plain", TEXT, Text, &[]), + ("text/plain;charset=utf-8", TEXT, Text, &["txt"]), + ("text/x-python", TEXT, Code(Python), &["py"]), + ("video/mp4", GENERIC, Video, &["mp4"]), + ("video/webm", GENERIC, Video, &["webm"]), ]; pub(crate) fn content_type_for_path( diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index f6b95e4a48..6f186c13f2 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -13,6 +13,8 @@ pub(crate) enum Tag { ContentEncoding, Delegate, #[allow(unused)] + Note, + #[allow(unused)] Nop, } @@ -32,6 +34,7 @@ impl Tag { Self::Metaprotocol => &[7], Self::ContentEncoding => &[9], Self::Delegate => &[11], + Self::Note => &[15], Self::Nop => &[255], } } diff --git a/src/lib.rs b/src/lib.rs index fb49868858..8501bba765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ #![allow( + clippy::large_enum_variant, + clippy::result_large_err, clippy::too_many_arguments, - clippy::type_complexity, - clippy::result_large_err + clippy::type_complexity )] #![deny( clippy::cast_lossless, @@ -14,18 +15,15 @@ use { self::{ arguments::Arguments, blocktime::Blocktime, - config::Config, decimal::Decimal, - decimal_sat::DecimalSat, - degree::Degree, - deserialize_from_str::DeserializeFromStr, - epoch::Epoch, - height::Height, - index::List, - inscriptions::{media, teleburn, Charm, Media, ParsedEnvelope}, - outgoing::Outgoing, + inscriptions::{ + inscription_id, + media::{self, ImageRendering, Media}, + teleburn, Charm, ParsedEnvelope, + }, representation::Representation, runes::{Etching, Pile, SpacedRune}, + settings::Settings, subcommand::{Subcommand, SubcommandResult}, tally::Tally, }, @@ -34,13 +32,11 @@ use { bitcoin::{ address::{Address, NetworkUnchecked}, blockdata::{ - constants::{ - COIN_VALUE, DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL, - }, + constants::{DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL}, locktime::absolute::LockTime, }, consensus::{self, Decodable, Encodable}, - hash_types::BlockHash, + hash_types::{BlockHash, TxMerkleNode}, hashes::Hash, opcodes, script::{self, Instruction}, @@ -51,24 +47,23 @@ use { chrono::{DateTime, TimeZone, Utc}, ciborium::Value, clap::{ArgGroup, Parser}, - derive_more::{Display, FromStr}, html_escaper::{Escape, Trusted}, lazy_static::lazy_static, + ordinals::{DeserializeFromStr, Epoch, Height, Rarity, Sat, SatPoint}, regex::Regex, + reqwest::Url, serde::{Deserialize, Deserializer, Serialize, Serializer}, std::{ - cmp, + cmp::{self, Reverse}, collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}, env, - ffi::OsString, fmt::{self, Display, Formatter}, fs::{self, File}, - io::{self, Cursor}, + io::{self, Cursor, Read}, mem, - net::{TcpListener, ToSocketAddrs}, - ops::{Add, AddAssign, Sub}, + net::ToSocketAddrs, path::{Path, PathBuf}, - process::{self, Command}, + process::{self, Command, Stdio}, str::FromStr, sync::{ atomic::{self, AtomicBool}, @@ -78,22 +73,18 @@ use { time::{Duration, Instant, SystemTime}, }, sysinfo::System, - tempfile::TempDir, tokio::{runtime::Runtime, task}, }; pub use self::{ chain::Chain, fee_rate::FeeRate, - index::{Index, RuneEntry}, + index::{Index, MintEntry, RuneEntry}, inscriptions::{Envelope, Inscription, InscriptionId}, object::Object, options::Options, - rarity::Rarity, runes::{Edict, Rune, RuneId, Runestone}, - sat::Sat, - sat_point::SatPoint, - subcommand::wallet::transaction_builder::{Target, TransactionBuilder}, + wallet::transaction_builder::{Target, TransactionBuilder}, }; #[cfg(test)] @@ -104,49 +95,40 @@ mod test; use self::test::*; macro_rules! tprintln { - ($($arg:tt)*) => { - - if cfg!(test) { - eprint!("==> "); - eprintln!($($arg)*); - } - }; + ($($arg:tt)*) => { + if cfg!(test) { + eprint!("==> "); + eprintln!($($arg)*); + } + }; } -mod arguments; +pub mod api; +pub mod arguments; mod blocktime; pub mod chain; -mod config; mod decimal; -mod decimal_sat; -mod degree; -mod deserialize_from_str; -mod epoch; mod fee_rate; -mod height; -mod index; +pub mod index; mod inscriptions; mod object; mod options; mod ordzaar; -mod outgoing; -pub mod rarity; +pub mod outgoing; mod representation; pub mod runes; -pub mod sat; -mod sat_point; mod server_config; +mod settings; pub mod subcommand; mod tally; pub mod templates; +pub mod wallet; type Result = std::result::Result; -const CYCLE_EPOCHS: u32 = 6; - static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false); static LISTENERS: Mutex> = Mutex::new(Vec::new()); -static INDEXER: Mutex>> = Mutex::new(Option::None); +static INDEXER: Mutex>> = Mutex::new(None); const TARGET_POSTAGE: Amount = Amount::from_sat(10_000); @@ -185,16 +167,14 @@ fn fund_raw_transaction( ) } -fn integration_test() -> bool { - env::var_os("ORD_INTEGRATION_TEST") - .map(|value| value.len() > 0) - .unwrap_or(false) -} - pub fn timestamp(seconds: u32) -> DateTime { Utc.timestamp_opt(seconds.into(), 0).unwrap() } +fn target_as_block_hash(target: bitcoin::Target) -> BlockHash { + BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes())) +} + fn unbound_outpoint() -> OutPoint { OutPoint { txid: Hash::all_zeros(), @@ -202,6 +182,25 @@ fn unbound_outpoint() -> OutPoint { } } +pub fn parse_ord_server_args(args: &str) -> (Settings, subcommand::server::Server) { + match Arguments::try_parse_from(args.split_whitespace()) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Server(server) => ( + Settings::merge( + arguments.options, + vec![("INTEGRATION_TEST".into(), "1".into())] + .into_iter() + .collect(), + ) + .unwrap(), + server, + ), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + } +} + fn gracefully_shutdown_indexer() { if let Some(indexer) = INDEXER.lock().unwrap().take() { // We explicitly set this to true to notify the thread to not take on new work @@ -228,10 +227,16 @@ pub fn main() { .unwrap() .iter() .for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100)))); + + gracefully_shutdown_indexer(); }) .expect("Error setting handler"); - match Arguments::parse().run() { + let args = Arguments::parse(); + + let minify = args.options.minify; + + match args.run() { Err(err) => { eprintln!("error: {err}"); err @@ -249,8 +254,11 @@ pub fn main() { process::exit(1); } - Ok(output) => output.print_json(), + Ok(output) => { + if let Some(output) = output { + output.print_json(minify); + } + gracefully_shutdown_indexer(); + } } - - gracefully_shutdown_indexer(); } diff --git a/src/object.rs b/src/object.rs index 5ae7c2dc06..611f592934 100644 --- a/src/object.rs +++ b/src/object.rs @@ -67,7 +67,7 @@ impl<'de> Deserialize<'de> for Object { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } diff --git a/src/options.rs b/src/options.rs index ec6261f0f8..9a93442f4e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,4 @@ -use {super::*, bitcoincore_rpc::Auth}; +use super::*; #[derive(Clone, Default, Debug, Parser)] #[command(group( @@ -9,30 +9,33 @@ use {super::*, bitcoincore_rpc::Auth}; pub struct Options { #[arg(long, help = "Load Bitcoin Core data dir from .")] pub(crate) bitcoin_data_dir: Option, - #[arg(long, help = "Authenticate to Bitcoin Core RPC with .")] - pub(crate) bitcoin_rpc_pass: Option, - #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] - pub(crate) bitcoin_rpc_user: Option, #[arg( - long = "chain", - value_enum, - default_value = "mainnet", - help = "Use ." + long, + help = "Authenticate to Bitcoin Core RPC with ." + )] + pub(crate) bitcoin_rpc_password: Option, + #[arg(long, help = "Connect to Bitcoin Core RPC at .")] + pub(crate) bitcoin_rpc_url: Option, + #[arg( + long, + help = "Authenticate to Bitcoin Core RPC as ." + )] + pub(crate) bitcoin_rpc_username: Option, + #[arg(long = "chain", value_enum, help = "Use . [default: mainnet]")] + pub(crate) chain_argument: Option, + #[arg( + long, + help = "Commit to index every blocks. [default: 5000]" )] - pub(crate) chain_argument: Chain, + pub(crate) commit_interval: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config_dir: Option, #[arg(long, help = "Load Bitcoin Core RPC cookie file from .")] pub(crate) cookie_file: Option, - #[arg(long, help = "Store index in .", default_value_os_t = Options::default_data_dir())] - pub(crate) data_dir: PathBuf, - #[arg( - long, - help = "Set index cache to bytes. By default takes 1/4 of available RAM." - )] - pub(crate) db_cache_size: Option, + #[arg(long, help = "Store index in .")] + pub(crate) data_dir: Option, #[arg( long, help = "Don't look for inscriptions below ." @@ -42,6 +45,11 @@ pub struct Options { pub(crate) height_limit: Option, #[arg(long, help = "Use index at .")] pub(crate) index: Option, + #[arg( + long, + help = "Set index cache size to bytes. [default: 1/4 available RAM]" + )] + pub(crate) index_cache_size: Option, #[arg( long, help = "Track location of runes. RUNES ARE IN AN UNFINISHED PRE-ALPHA STATE AND SUBJECT TO CHANGE AT ANY TIME." @@ -49,8 +57,14 @@ pub struct Options { pub(crate) index_runes: bool, #[arg(long, help = "Track location of all satoshis.")] pub(crate) index_sats: bool, + #[arg(long, help = "Keep sat index entries of spent outputs.")] + pub(crate) index_spent_sats: bool, #[arg(long, help = "Store transactions in index.")] pub(crate) index_transactions: bool, + #[arg(long, help = "Run in integration test mode.")] + pub(crate) integration_test: bool, + #[arg(long, help = "Minify JSON output.")] + pub(crate) minify: bool, #[arg( long, short, @@ -58,754 +72,20 @@ pub struct Options { help = "Do not index inscriptions." )] pub(crate) no_index_inscriptions: bool, + #[arg( + long, + help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." + )] + pub(crate) server_password: Option, + #[arg( + long, + help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." + )] + pub(crate) server_username: Option, #[arg(long, short, help = "Use regtest. Equivalent to `--chain regtest`.")] pub(crate) regtest: bool, - #[arg(long, help = "Connect to Bitcoin Core RPC at .")] - pub(crate) rpc_url: Option, #[arg(long, short, help = "Use signet. Equivalent to `--chain signet`.")] pub(crate) signet: bool, #[arg(long, short, help = "Use testnet. Equivalent to `--chain testnet`.")] pub(crate) testnet: bool, } - -impl Options { - pub(crate) fn chain(&self) -> Chain { - if self.signet { - Chain::Signet - } else if self.regtest { - Chain::Regtest - } else if self.testnet { - Chain::Testnet - } else { - self.chain_argument - } - } - - pub(crate) fn first_inscription_height(&self) -> u32 { - if integration_test() { - 0 - } else { - self - .first_inscription_height - .unwrap_or_else(|| self.chain().first_inscription_height()) - } - } - - pub(crate) fn first_rune_height(&self) -> u32 { - if integration_test() { - 0 - } else { - self.chain().first_rune_height() - } - } - - pub(crate) fn index_runes(&self) -> bool { - self.index_runes && self.chain() != Chain::Mainnet - } - - pub(crate) fn rpc_url(&self, wallet_name: Option) -> String { - let base_url = self - .rpc_url - .clone() - .unwrap_or(format!("127.0.0.1:{}", self.chain().default_rpc_port())); - - match wallet_name { - Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), - None => format!("{base_url}/"), - } - } - - pub(crate) fn cookie_file(&self) -> Result { - if let Some(cookie_file) = &self.cookie_file { - return Ok(cookie_file.clone()); - } - - let path = if let Some(bitcoin_data_dir) = &self.bitcoin_data_dir { - bitcoin_data_dir.clone() - } else if cfg!(target_os = "linux") { - dirs::home_dir() - .ok_or_else(|| anyhow!("failed to get cookie file path: could not get home dir"))? - .join(".bitcoin") - } else { - dirs::data_dir() - .ok_or_else(|| anyhow!("failed to get cookie file path: could not get data dir"))? - .join("Bitcoin") - }; - - let path = self.chain().join_with_data_dir(&path); - - Ok(path.join(".cookie")) - } - - fn default_data_dir() -> PathBuf { - dirs::data_dir() - .map(|dir| dir.join("ord")) - .expect("failed to retrieve data dir") - } - - pub(crate) fn data_dir(&self) -> PathBuf { - self.chain().join_with_data_dir(&self.data_dir) - } - - pub(crate) fn load_config(&self) -> Result { - match &self.config { - Some(path) => Ok(serde_yaml::from_reader(File::open(path)?)?), - None => match &self.config_dir { - Some(dir) if dir.join("ord.yaml").exists() => { - Ok(serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)?) - } - Some(_) | None => Ok(Default::default()), - }, - } - } - - fn derive_var( - arg_value: Option<&str>, - env_key: Option<&str>, - config_value: Option<&str>, - default_value: Option<&str>, - ) -> Result> { - let env_value = match env_key { - Some(env_key) => match env::var(format!("ORD_{env_key}")) { - Ok(env_value) => Some(env_value), - Err(err @ env::VarError::NotUnicode(_)) => return Err(err.into()), - Err(env::VarError::NotPresent) => None, - }, - None => None, - }; - - Ok( - arg_value - .or(env_value.as_deref()) - .or(config_value) - .or(default_value) - .map(str::to_string), - ) - } - - pub(crate) fn auth(&self) -> Result { - let config = self.load_config()?; - - let rpc_user = Options::derive_var( - self.bitcoin_rpc_user.as_deref(), - Some("BITCOIN_RPC_USER"), - config.bitcoin_rpc_user.as_deref(), - None, - )?; - - let rpc_pass = Options::derive_var( - self.bitcoin_rpc_pass.as_deref(), - Some("BITCOIN_RPC_PASS"), - config.bitcoin_rpc_pass.as_deref(), - None, - )?; - - match (rpc_user, rpc_pass) { - (Some(rpc_user), Some(rpc_pass)) => Ok(Auth::UserPass(rpc_user, rpc_pass)), - (None, Some(_rpc_pass)) => Err(anyhow!("no bitcoind rpc user specified")), - (Some(_rpc_user), None) => Err(anyhow!("no bitcoind rpc password specified")), - _ => Ok(Auth::CookieFile(self.cookie_file()?)), - } - } - - pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { - let rpc_url = self.rpc_url(wallet); - - let auth = self.auth()?; - - log::info!("Connecting to Bitcoin Core at {}", self.rpc_url(None)); - - if let Auth::CookieFile(cookie_file) = &auth { - log::info!( - "Using credentials from cookie file at `{}`", - cookie_file.display() - ); - - ensure!( - cookie_file.is_file(), - "cookie file `{}` does not exist", - cookie_file.display() - ); - } - - let client = Client::new(&rpc_url, auth) - .with_context(|| format!("failed to connect to Bitcoin Core RPC at {rpc_url}"))?; - - let rpc_chain = match client.get_blockchain_info()?.chain.as_str() { - "main" => Chain::Mainnet, - "test" => Chain::Testnet, - "regtest" => Chain::Regtest, - "signet" => Chain::Signet, - other => bail!("Bitcoin RPC server on unknown chain: {other}"), - }; - - let ord_chain = self.chain(); - - if rpc_chain != ord_chain { - bail!("Bitcoin RPC server is on {rpc_chain} but ord is on {ord_chain}"); - } - - Ok(client) - } -} - -#[cfg(test)] -mod tests { - use {super::*, bitcoin::Network, std::path::Path}; - - #[test] - fn rpc_url_overrides_network() { - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--rpc-url=127.0.0.1:1234", - "--chain=signet", - "index", - "update" - ]) - .unwrap() - .options - .rpc_url(None), - "127.0.0.1:1234/" - ); - } - - #[test] - fn cookie_file_overrides_network() { - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--cookie-file=/foo/bar", - "--chain=signet", - "index", - "update" - ]) - .unwrap() - .options - .cookie_file() - .unwrap(), - Path::new("/foo/bar") - ); - } - - #[test] - fn use_default_network() { - let arguments = Arguments::try_parse_from(["ord", "index", "update"]).unwrap(); - - assert_eq!(arguments.options.rpc_url(None), "127.0.0.1:8332/"); - - assert!(arguments - .options - .cookie_file() - .unwrap() - .ends_with(".cookie")); - } - - #[test] - fn uses_network_defaults() { - let arguments = - Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]).unwrap(); - - assert_eq!(arguments.options.rpc_url(None), "127.0.0.1:38332/"); - - assert!(arguments - .options - .cookie_file() - .unwrap() - .display() - .to_string() - .ends_with(if cfg!(windows) { - r"\signet\.cookie" - } else { - "/signet/.cookie" - })); - } - - #[test] - fn mainnet_cookie_file_path() { - let cookie_file = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { - "/.bitcoin/.cookie" - } else if cfg!(windows) { - r"\Bitcoin\.cookie" - } else { - "/Bitcoin/.cookie" - })) - } - - #[test] - fn othernet_cookie_file_path() { - let arguments = - Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]).unwrap(); - - let cookie_file = arguments - .options - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { - "/.bitcoin/signet/.cookie" - } else if cfg!(windows) { - r"\Bitcoin\signet\.cookie" - } else { - "/Bitcoin/signet/.cookie" - })); - } - - #[test] - fn cookie_file_defaults_to_bitcoin_data_dir() { - let arguments = Arguments::try_parse_from([ - "ord", - "--bitcoin-data-dir=foo", - "--chain=signet", - "index", - "update", - ]) - .unwrap(); - - let cookie_file = arguments - .options - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(windows) { - r"foo\signet\.cookie" - } else { - "foo/signet/.cookie" - })); - } - - #[test] - fn mainnet_data_dir() { - let data_dir = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { r"\ord" } else { "/ord" }), - "{data_dir}" - ); - } - - #[test] - fn othernet_data_dir() { - let data_dir = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) - .unwrap() - .options - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { - r"\ord\signet" - } else { - "/ord/signet" - }), - "{data_dir}" - ); - } - - #[test] - fn network_is_joined_with_data_dir() { - let data_dir = Arguments::try_parse_from([ - "ord", - "--chain=signet", - "--data-dir", - "foo", - "index", - "update", - ]) - .unwrap() - .options - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { - r"foo\signet" - } else { - "foo/signet" - }), - "{data_dir}" - ); - } - - #[test] - fn network_accepts_aliases() { - fn check_network_alias(alias: &str, suffix: &str) { - let data_dir = Arguments::try_parse_from(["ord", "--chain", alias, "index", "update"]) - .unwrap() - .options - .data_dir() - .display() - .to_string(); - - assert!(data_dir.ends_with(suffix), "{data_dir}"); - } - - check_network_alias("main", "ord"); - check_network_alias("mainnet", "ord"); - check_network_alias( - "regtest", - if cfg!(windows) { - r"ord\regtest" - } else { - "ord/regtest" - }, - ); - check_network_alias( - "signet", - if cfg!(windows) { - r"ord\signet" - } else { - "ord/signet" - }, - ); - check_network_alias( - "test", - if cfg!(windows) { - r"ord\testnet3" - } else { - "ord/testnet3" - }, - ); - check_network_alias( - "testnet", - if cfg!(windows) { - r"ord\testnet3" - } else { - "ord/testnet3" - }, - ); - } - - #[test] - fn rpc_server_chain_must_match() { - let rpc_server = test_bitcoincore_rpc::builder() - .network(Network::Testnet) - .build(); - - let options = Options::try_parse_from([ - "ord", - "--cookie-file", - rpc_server.cookie_file().to_str().unwrap(), - "--rpc-url", - &rpc_server.url(), - ]) - .unwrap(); - - assert_eq!( - options.bitcoin_rpc_client(None).unwrap_err().to_string(), - "Bitcoin RPC server is on testnet but ord is on mainnet" - ); - } - - #[test] - fn chain_flags() { - Arguments::try_parse_from(["ord", "--signet", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--signet", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Signet - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-s", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Signet - ); - - Arguments::try_parse_from(["ord", "--regtest", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--regtest", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Regtest - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-r", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Regtest - ); - - Arguments::try_parse_from(["ord", "--testnet", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--testnet", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Testnet - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-t", "index", "update"]) - .unwrap() - .options - .chain(), - Chain::Testnet - ); - } - - fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::Wallet) { - match Arguments::try_parse_from(args.split_whitespace()) { - Ok(arguments) => match arguments.subcommand { - Subcommand::Wallet(wallet) => (arguments.options, wallet), - subcommand => panic!("unexpected subcommand: {subcommand:?}"), - }, - Err(err) => panic!("error parsing arguments: {err}"), - } - } - - #[test] - fn wallet_flag_overrides_default_name() { - let (_, wallet) = parse_wallet_args("ord wallet create"); - assert_eq!(wallet.name, "ord"); - - let (_, wallet) = parse_wallet_args("ord wallet --name foo create"); - assert_eq!(wallet.name, "foo") - } - - #[test] - fn default_config_is_returned_if_config_option_is_not_passed() { - assert_eq!( - Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .load_config() - .unwrap(), - Default::default() - ); - } - - #[test] - fn uses_wallet_rpc() { - let (options, _) = parse_wallet_args("ord wallet --name foo balance"); - - assert_eq!( - options.rpc_url(Some("foo".into())), - "127.0.0.1:8332/wallet/foo" - ); - } - - #[test] - fn config_is_loaded_from_config_option_path() { - let id = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let tempdir = TempDir::new().unwrap(); - let path = tempdir.path().join("ord.yaml"); - fs::write(&path, format!("hidden:\n- \"{id}\"")).unwrap(); - - assert_eq!( - Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) - .unwrap() - .options - .load_config() - .unwrap(), - Config { - hidden: iter::once(id).collect(), - ..Default::default() - } - ); - } - - #[test] - fn config_with_rpc_user_pass() { - let tempdir = TempDir::new().unwrap(); - let path = tempdir.path().join("ord.yaml"); - fs::write( - &path, - "hidden:\nbitcoin_rpc_user: foo\nbitcoin_rpc_pass: bar", - ) - .unwrap(); - - assert_eq!( - Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) - .unwrap() - .options - .load_config() - .unwrap(), - Config { - bitcoin_rpc_user: Some("foo".into()), - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - } - ); - } - - #[test] - fn config_is_loaded_from_config_dir_option_path() { - let id = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let tempdir = TempDir::new().unwrap(); - - fs::write( - tempdir.path().join("ord.yaml"), - format!("hidden:\n- \"{id}\""), - ) - .unwrap(); - - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--config-dir", - tempdir.path().to_str().unwrap(), - "index", - "update" - ]) - .unwrap() - .options - .load_config() - .unwrap(), - Config { - hidden: iter::once(id).collect(), - ..Default::default() - } - ); - } - - #[test] - fn test_derive_var() { - assert_eq!(Options::derive_var(None, None, None, None).unwrap(), None); - - assert_eq!( - Options::derive_var(None, None, None, Some("foo")).unwrap(), - Some("foo".into()) - ); - - assert_eq!( - Options::derive_var(None, None, Some("bar"), Some("foo")).unwrap(), - Some("bar".into()) - ); - - assert_eq!( - Options::derive_var(Some("qux"), None, Some("bar"), Some("foo")).unwrap(), - Some("qux".into()) - ); - - assert_eq!( - Options::derive_var(Some("qux"), None, None, Some("foo")).unwrap(), - Some("qux".into()), - ); - } - - #[test] - fn auth_missing_rpc_pass_is_an_error() { - let options = Options { - bitcoin_rpc_user: Some("foo".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap_err().to_string(), - "no bitcoind rpc password specified" - ); - } - - #[test] - fn auth_missing_rpc_user_is_an_error() { - let options = Options { - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap_err().to_string(), - "no bitcoind rpc user specified" - ); - } - - #[test] - fn auth_with_user_and_pass() { - let options = Options { - bitcoin_rpc_user: Some("foo".into()), - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap(), - Auth::UserPass("foo".into(), "bar".into()) - ); - } - - #[test] - fn auth_with_cookie_file() { - let options = Options { - cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap(), - Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) - ); - } - - #[test] - fn setting_db_cache_size() { - let arguments = - Arguments::try_parse_from(["ord", "--db-cache-size", "16000000000", "index", "update"]) - .unwrap(); - assert_eq!(arguments.options.db_cache_size, Some(16000000000)); - } - - #[test] - fn index_runes_only_returns_true_if_index_runes_flag_is_passed_and_not_on_mainnnet() { - assert!(Arguments::try_parse_from([ - "ord", - "--chain=signet", - "--index-runes", - "index", - "update" - ]) - .unwrap() - .options - .index_runes()); - assert!( - !Arguments::try_parse_from(["ord", "--index-runes", "index", "update"]) - .unwrap() - .options - .index_runes() - ); - assert!(!Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .index_runes()); - } - - #[test] - fn cookie_file_does_not_exist_error() { - assert_eq!( - Options { - cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), - ..Default::default() - } - .bitcoin_rpc_client(None) - .map(|_| "") - .unwrap_err() - .to_string(), - "cookie file `/foo/bar/baz/qux/.cookie` does not exist" - ); - } -} diff --git a/src/ordzaar/ordinals.rs b/src/ordzaar/ordinals.rs index 363b8acd15..6b02522f75 100644 --- a/src/ordzaar/ordinals.rs +++ b/src/ordzaar/ordinals.rs @@ -30,42 +30,41 @@ pub struct Output { } pub fn get_ordinals(index: &Index, outpoint: OutPoint) -> Result> { - match index.list(outpoint)? { - Some(crate::index::List::Unspent(ranges)) => { - let mut ordinals = Vec::new(); - for Output { + let index_list = index.list(outpoint)?; + if let Some(ranges) = index_list { + let mut ordinals = Vec::new(); + for Output { + output, + start, + end, + size, + offset, + rarity, + name, + } in list(outpoint, ranges) + { + let sat = Sat(start); + ordinals.push(OrdinalJson { + number: sat.n(), + decimal: sat.decimal().to_string(), + degree: sat.degree().to_string(), + name, + height: sat.height().0, + cycle: sat.cycle(), + epoch: sat.epoch().0, + period: sat.period(), + offset, + rarity, output, start, end, size, - offset, - rarity, - name, - } in list(outpoint, ranges) - { - let sat = Sat(start); - ordinals.push(OrdinalJson { - number: sat.n(), - decimal: sat.decimal().to_string(), - degree: sat.degree().to_string(), - name, - height: sat.height().0, - cycle: sat.cycle(), - epoch: sat.epoch().0, - period: sat.period(), - offset, - rarity, - output, - start, - end, - size, - }); - } - Ok(ordinals) + }); } - Some(crate::index::List::Spent) => Ok(Vec::new()), - None => Ok(Vec::new()), - } + return Ok(ordinals); + }; + + return Ok(Vec::new()); } fn list(outpoint: OutPoint, ranges: Vec<(u64, u64)>) -> Vec { diff --git a/src/outgoing.rs b/src/outgoing.rs index 21dcd83c01..72d00a1064 100644 --- a/src/outgoing.rs +++ b/src/outgoing.rs @@ -1,13 +1,24 @@ use super::*; #[derive(Debug, PartialEq, Clone)] -pub(crate) enum Outgoing { +pub enum Outgoing { Amount(Amount), InscriptionId(InscriptionId), SatPoint(SatPoint), Rune { decimal: Decimal, rune: SpacedRune }, } +impl Display for Outgoing { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Amount(amount) => write!(f, "{}", amount.to_string().to_lowercase()), + Self::InscriptionId(inscription_id) => inscription_id.fmt(f), + Self::SatPoint(satpoint) => satpoint.fmt(f), + Self::Rune { decimal, rune } => write!(f, "{decimal} {rune}"), + } + } +} + impl FromStr for Outgoing { type Err = Error; @@ -69,6 +80,24 @@ impl FromStr for Outgoing { } } +impl Serialize for Outgoing { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Outgoing { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + DeserializeFromStr::with(deserializer) + } +} + #[cfg(test)] mod tests { use super::*; @@ -153,4 +182,95 @@ mod tests { assert!("0".parse::().is_err()); } + + #[test] + fn roundtrip() { + #[track_caller] + fn case(s: &str, outgoing: Outgoing) { + assert_eq!(s.parse::().unwrap(), outgoing); + assert_eq!(s, outgoing.to_string()); + } + + case( + "0000000000000000000000000000000000000000000000000000000000000000i0", + Outgoing::InscriptionId( + "0000000000000000000000000000000000000000000000000000000000000000i0" + .parse() + .unwrap(), + ), + ); + + case( + "0000000000000000000000000000000000000000000000000000000000000000:0:0", + Outgoing::SatPoint( + "0000000000000000000000000000000000000000000000000000000000000000:0:0" + .parse() + .unwrap(), + ), + ); + + case("0 btc", Outgoing::Amount("0 btc".parse().unwrap())); + case("1.2 btc", Outgoing::Amount("1.2 btc".parse().unwrap())); + + case( + "0 XY•Z", + Outgoing::Rune { + rune: "XY•Z".parse().unwrap(), + decimal: "0".parse().unwrap(), + }, + ); + + case( + "1.1 XYZ", + Outgoing::Rune { + rune: "XYZ".parse().unwrap(), + decimal: "1.1".parse().unwrap(), + }, + ); + } + + #[test] + fn serde() { + #[track_caller] + fn case(s: &str, j: &str, o: Outgoing) { + assert_eq!(s.parse::().unwrap(), o); + assert_eq!(serde_json::to_string(&o).unwrap(), j); + assert_eq!(serde_json::from_str::(j).unwrap(), o); + } + + case( + "0000000000000000000000000000000000000000000000000000000000000000i0", + "\"0000000000000000000000000000000000000000000000000000000000000000i0\"", + Outgoing::InscriptionId( + "0000000000000000000000000000000000000000000000000000000000000000i0" + .parse() + .unwrap(), + ), + ); + + case( + "0000000000000000000000000000000000000000000000000000000000000000:0:0", + "\"0000000000000000000000000000000000000000000000000000000000000000:0:0\"", + Outgoing::SatPoint( + "0000000000000000000000000000000000000000000000000000000000000000:0:0" + .parse() + .unwrap(), + ), + ); + + case( + "3 btc", + "\"3 btc\"", + Outgoing::Amount(Amount::from_sat(3 * COIN_VALUE)), + ); + + case( + "6.66 HELL.MONEY", + "\"6.66 HELL•MONEY\"", + Outgoing::Rune { + rune: "HELL•MONEY".parse().unwrap(), + decimal: "6.66".parse().unwrap(), + }, + ); + } } diff --git a/src/runes.rs b/src/runes.rs index 0f58905b61..1de3fb8544 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -5,16 +5,16 @@ use { pub use {edict::Edict, rune::Rune, rune_id::RuneId, runestone::Runestone}; -pub(crate) use {etching::Etching, pile::Pile, spaced_rune::SpacedRune}; +pub(crate) use {etching::Etching, mint::Mint, pile::Pile, spaced_rune::SpacedRune}; pub const MAX_DIVISIBILITY: u8 = 38; -pub(crate) const CLAIM_BIT: u128 = 1 << 48; pub(crate) const MAX_LIMIT: u128 = 1 << 64; const RESERVED: u128 = 6402364363415443603228541259936211926; mod edict; mod etching; mod flag; +mod mint; mod pile; mod rune; mod rune_id; @@ -49,7 +49,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -139,7 +139,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -166,12 +166,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -193,7 +193,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -223,7 +223,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -250,12 +250,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(block_two_minimum), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } } @@ -273,7 +273,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -303,7 +303,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -330,12 +330,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RESERVED - 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } } @@ -353,7 +353,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -380,7 +380,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RESERVED), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -390,7 +390,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], )], ); @@ -402,7 +402,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -430,7 +430,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RESERVED), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -440,7 +440,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RESERVED + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 4, number: 1, ..Default::default() @@ -453,14 +453,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -478,7 +478,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -507,12 +507,12 @@ mod tests { rune: Rune(RUNE), etching: txid, divisibility: 1, - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -529,12 +529,12 @@ mod tests { edicts: vec![ Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -562,12 +562,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -584,12 +584,12 @@ mod tests { edicts: vec![ Edict { id: 0, - amount: u128::max_value() / 2, + amount: u128::MAX / 2, output: 0, }, Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -617,13 +617,13 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, symbol: None, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -798,7 +798,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -825,7 +825,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -835,7 +835,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -845,7 +845,7 @@ mod tests { Runestone { edicts: vec![Edict { id: id.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], ..Default::default() @@ -863,7 +863,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -873,7 +873,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -890,7 +890,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -899,6 +899,7 @@ mod tests { }), default_output: None, burn: true, + ..Default::default() } .encipher(), ), @@ -938,20 +939,22 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { rune: Some(Rune(RUNE)), - deadline: Some(1), + mint: Some(Mint { + deadline: Some(1), + limit: Some(1), + term: Some(1), + }), divisibility: 1, - limit: Some(1), symbol: Some('$'), - term: Some(1), spacers: 1, }), - default_output: None, burn: true, + ..Default::default() } .encipher(), ), @@ -970,11 +973,9 @@ mod tests { id, RuneEntry { burned: 0, - deadline: None, divisibility: 1, - end: None, etching: txid0, - limit: None, + mint: None, mints: 0, number: 0, rune: Rune(RUNE), @@ -1000,12 +1001,12 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching::default()), burn: true, - default_output: None, + ..Default::default() } .encipher(), ), @@ -1045,7 +1046,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1072,7 +1073,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1082,7 +1083,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1104,10 +1105,10 @@ mod tests { [( id, RuneEntry { - burned: u128::max_value(), + burned: u128::MAX, etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1128,7 +1129,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1155,7 +1156,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1165,7 +1166,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1183,7 +1184,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1193,7 +1194,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -1210,7 +1211,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1237,7 +1238,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1247,7 +1248,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1266,9 +1267,9 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, - burned: u128::max_value(), + burned: u128::MAX, ..Default::default() }, )], @@ -1288,7 +1289,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1315,7 +1316,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1325,7 +1326,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1350,7 +1351,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1360,7 +1361,7 @@ mod tests { txid: txid1, vout: 1, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -1377,7 +1378,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1404,7 +1405,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1414,7 +1415,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1439,7 +1440,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1449,7 +1450,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -1466,7 +1467,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1493,7 +1494,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1503,7 +1504,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1528,8 +1529,8 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), - burned: u128::max_value(), + supply: u128::MAX, + burned: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1551,7 +1552,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1578,7 +1579,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1588,7 +1589,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -1606,7 +1607,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1616,7 +1617,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -1633,7 +1634,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1660,12 +1661,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); context.rpc_server.broadcast_tx(TransactionTemplate { @@ -1674,7 +1675,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1696,12 +1697,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -1717,7 +1718,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1744,7 +1745,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1754,7 +1755,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], )], ); @@ -1764,7 +1765,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1792,7 +1793,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1802,7 +1803,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -1815,14 +1816,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -1841,7 +1842,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1851,7 +1852,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -1863,7 +1864,7 @@ mod tests { txid: txid2, vout: 0, }, - vec![(id0, u128::max_value()), (id1, u128::max_value())], + vec![(id0, u128::MAX), (id1, u128::MAX)], )], ); } @@ -1880,7 +1881,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1907,7 +1908,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1917,7 +1918,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], )], ); @@ -1927,7 +1928,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -1955,7 +1956,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -1965,7 +1966,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -1978,14 +1979,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -2004,7 +2005,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2014,7 +2015,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2026,7 +2027,7 @@ mod tests { txid: txid2, vout: 0, }, - vec![(id0, u128::max_value()), (id1, u128::max_value())], + vec![(id0, u128::MAX), (id1, u128::MAX)], )], ); @@ -2038,12 +2039,12 @@ mod tests { edicts: vec![ Edict { id: id0.into(), - amount: u128::max_value() / 2, + amount: u128::MAX / 2, output: 1, }, Edict { id: id1.into(), - amount: u128::max_value() / 2, + amount: u128::MAX / 2, output: 1, }, ], @@ -2063,7 +2064,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2073,7 +2074,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2086,17 +2087,14 @@ mod tests { txid: txid3, vout: 0, }, - vec![ - (id0, u128::max_value() / 2 + 1), - (id1, u128::max_value() / 2 + 1), - ], + vec![(id0, u128::MAX / 2 + 1), (id1, u128::MAX / 2 + 1)], ), ( OutPoint { txid: txid3, vout: 1, }, - vec![(id0, u128::max_value() / 2), (id1, u128::max_value() / 2)], + vec![(id0, u128::MAX / 2), (id1, u128::MAX / 2)], ), ], ); @@ -2114,7 +2112,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2141,7 +2139,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2151,7 +2149,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], )], ); @@ -2161,7 +2159,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2189,7 +2187,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2199,7 +2197,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2212,14 +2210,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -2231,12 +2229,12 @@ mod tests { edicts: vec![ Edict { id: id0.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, Edict { id: id1.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -2256,7 +2254,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2266,7 +2264,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2278,7 +2276,7 @@ mod tests { txid: txid2, vout: 0, }, - vec![(id0, u128::max_value()), (id1, u128::max_value())], + vec![(id0, u128::MAX), (id1, u128::MAX)], )], ); } @@ -2296,7 +2294,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2323,7 +2321,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2333,7 +2331,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -2356,12 +2354,12 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 1 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 1 }, vec![(id, u128::MAX)])], ); } @@ -2377,7 +2375,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2402,7 +2400,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2430,7 +2428,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, ..Default::default() }, @@ -2440,7 +2438,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2453,14 +2451,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -2478,7 +2476,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2505,7 +2503,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2515,7 +2513,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -2531,7 +2529,7 @@ mod tests { }, Edict { id: id.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -2550,7 +2548,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2560,7 +2558,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -2577,7 +2575,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2604,7 +2602,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2614,7 +2612,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], )], ); @@ -2624,7 +2622,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2652,7 +2650,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2662,7 +2660,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2675,14 +2673,14 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ( OutPoint { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ], ); @@ -2694,12 +2692,12 @@ mod tests { edicts: vec![ Edict { id: id0.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, Edict { id: id1.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -2719,7 +2717,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2729,7 +2727,7 @@ mod tests { RuneEntry { etching: txid1, rune: Rune(RUNE + 1), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 3, number: 1, ..Default::default() @@ -2742,14 +2740,14 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id1, u128::max_value())], + vec![(id1, u128::MAX)], ), ( OutPoint { txid: txid2, vout: 0, }, - vec![(id0, u128::max_value())], + vec![(id0, u128::MAX)], ), ], ); @@ -2767,7 +2765,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value() / 2, + amount: u128::MAX / 2, output: 0, }], etching: Some(Etching { @@ -2794,7 +2792,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value() / 2, + supply: u128::MAX / 2, timestamp: 2, ..Default::default() }, @@ -2804,7 +2802,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value() / 2)], + vec![(id, u128::MAX / 2)], )], ); @@ -2814,7 +2812,7 @@ mod tests { Runestone { edicts: vec![Edict { id: id.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], ..Default::default() @@ -2832,7 +2830,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value() / 2, + supply: u128::MAX / 2, timestamp: 2, ..Default::default() }, @@ -2842,7 +2840,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() / 2)], + vec![(id, u128::MAX / 2)], )], ); } @@ -2859,7 +2857,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 1, }], etching: Some(Etching { @@ -2884,10 +2882,10 @@ mod tests { [( id, RuneEntry { - burned: u128::max_value(), + burned: u128::MAX, etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -2909,7 +2907,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2936,12 +2934,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -2959,7 +2957,7 @@ mod tests { edicts: vec![ Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, Edict { @@ -2992,12 +2990,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -3041,28 +3039,16 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], [ - ( - OutPoint { txid, vout: 0 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 1 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 2 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 3 }, - vec![(id, u128::max_value() / 4)], - ), + (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 1 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 2 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 3 }, vec![(id, u128::MAX / 4)]), ], ); } @@ -3114,7 +3100,7 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3122,19 +3108,19 @@ mod tests { [ ( OutPoint { txid, vout: 0 }, - vec![(id, 1000 + (u128::max_value() - 1000) / 4 + 1)], + vec![(id, 1000 + (u128::MAX - 1000) / 4 + 1)], ), ( OutPoint { txid, vout: 1 }, - vec![(id, (u128::max_value() - 1000) / 4 + 1)], + vec![(id, (u128::MAX - 1000) / 4 + 1)], ), ( OutPoint { txid, vout: 2 }, - vec![(id, (u128::max_value() - 1000) / 4 + 1)], + vec![(id, (u128::MAX - 1000) / 4 + 1)], ), ( OutPoint { txid, vout: 3 }, - vec![(id, (u128::max_value() - 1000) / 4)], + vec![(id, (u128::MAX - 1000) / 4)], ), ], ); @@ -3187,28 +3173,16 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], [ - ( - OutPoint { txid, vout: 0 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 1 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 2 }, - vec![(id, u128::max_value() / 4 + 1)], - ), - ( - OutPoint { txid, vout: 3 }, - vec![(id, u128::max_value() / 4)], - ), + (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 1 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 2 }, vec![(id, u128::MAX / 4 + 1)]), + (OutPoint { txid, vout: 3 }, vec![(id, u128::MAX / 4)]), ], ); } @@ -3281,7 +3255,7 @@ mod tests { edicts: vec![ Edict { id: 0, - amount: u128::max_value() - 3000, + amount: u128::MAX - 3000, output: 0, }, Edict { @@ -3314,16 +3288,13 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], [ - ( - OutPoint { txid, vout: 0 }, - vec![(id, u128::max_value() - 2000)], - ), + (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX - 2000)]), (OutPoint { txid, vout: 1 }, vec![(id, 1000)]), (OutPoint { txid, vout: 2 }, vec![(id, 1000)]), ], @@ -3349,7 +3320,7 @@ mod tests { }, Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -3377,7 +3348,7 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3385,7 +3356,7 @@ mod tests { [ ( OutPoint { txid, vout: 0 }, - vec![(id, u128::max_value() - 4000 + 1000)], + vec![(id, u128::MAX - 4000 + 1000)], ), (OutPoint { txid, vout: 1 }, vec![(id, 1000)]), (OutPoint { txid, vout: 2 }, vec![(id, 1000)]), @@ -3406,7 +3377,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3433,7 +3404,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3443,7 +3414,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3472,7 +3443,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3483,14 +3454,14 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() / 2 + 1)], + vec![(id, u128::MAX / 2 + 1)], ), ( OutPoint { txid: txid1, vout: 1, }, - vec![(id, u128::max_value() / 2)], + vec![(id, u128::MAX / 2)], ), ], ); @@ -3508,7 +3479,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3535,7 +3506,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3545,7 +3516,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3581,7 +3552,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3592,14 +3563,14 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, 1000 + (u128::max_value() - 1000) / 2 + 1)], + vec![(id, 1000 + (u128::MAX - 1000) / 2 + 1)], ), ( OutPoint { txid: txid1, vout: 1, }, - vec![(id, (u128::max_value() - 1000) / 2)], + vec![(id, (u128::MAX - 1000) / 2)], ), ], ); @@ -3617,7 +3588,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3644,7 +3615,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3654,7 +3625,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3690,7 +3661,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3701,14 +3672,14 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() / 2 + 1)], + vec![(id, u128::MAX / 2 + 1)], ), ( OutPoint { txid: txid1, vout: 1, }, - vec![(id, u128::max_value() / 2)], + vec![(id, u128::MAX / 2)], ), ], ); @@ -3726,7 +3697,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3753,7 +3724,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3763,7 +3734,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3792,7 +3763,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3803,7 +3774,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() - 1000)], + vec![(id, u128::MAX - 1000)], ), ( OutPoint { @@ -3828,7 +3799,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3855,7 +3826,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3865,7 +3836,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3877,7 +3848,7 @@ mod tests { edicts: vec![ Edict { id: id.into(), - amount: u128::max_value() - 2000, + amount: u128::MAX - 2000, output: 0, }, Edict { @@ -3901,7 +3872,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3912,7 +3883,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() - 2000 + 1000)], + vec![(id, u128::MAX - 2000 + 1000)], ), ( OutPoint { @@ -3937,7 +3908,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -3964,7 +3935,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -3974,7 +3945,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -3991,7 +3962,7 @@ mod tests { }, Edict { id: id.into(), - amount: u128::max_value(), + amount: u128::MAX, output: 0, }, ], @@ -4010,7 +3981,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -4021,7 +3992,7 @@ mod tests { txid: txid1, vout: 0, }, - vec![(id, u128::max_value() - 4000 + 1000)], + vec![(id, u128::MAX - 4000 + 1000)], ), ( OutPoint { @@ -4060,7 +4031,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -4088,13 +4059,13 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, symbol: Some('$'), timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -4137,12 +4108,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, )], - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])], + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])], ); } @@ -4158,7 +4129,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -4185,7 +4156,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -4195,7 +4166,7 @@ mod tests { txid: txid0, vout: 0, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); @@ -4224,7 +4195,7 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() }, @@ -4234,7 +4205,7 @@ mod tests { txid: txid1, vout: 1, }, - vec![(id, u128::max_value())], + vec![(id, u128::MAX)], )], ); } @@ -4242,26 +4213,122 @@ mod tests { #[test] fn max_limit() { MAX_LIMIT - .checked_mul(u128::from(u16::max_value()) * 144 * 365 * 1_000_000_000) + .checked_mul(u128::from(u16::MAX) * 144 * 365 * 1_000_000_000) .unwrap(); } #[test] - fn etching_with_limit_can_be_minted() { + fn rune_can_be_minted_without_edict() { let context = Context::builder().arg("--index-runes").build(); context.mine_blocks(1); + // etch the rune let txid0 = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0, Witness::new())], op_return: Some( Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + } + .encipher(), + ), + ..Default::default() + }); + + context.mine_blocks(1); + + let id = RuneId { + height: 2, + index: 1, + }; + + context.assert_runes( + [( + id, + RuneEntry { + etching: txid0, + rune: Rune(RUNE), + timestamp: 2, + mints: 0, + mint: Some(MintEntry { limit: Some(1000), ..Default::default() }), ..Default::default() + }, + )], + [], + ); + + // claim the rune + let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, Witness::new())], + op_return: Some( + Runestone { + claim: Some(u128::from(id)), + ..Default::default() + } + .encipher(), + ), + ..Default::default() + }); + + context.mine_blocks(1); + + context.assert_runes( + [( + id, + RuneEntry { + etching: txid0, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), + mints: 1, + rune: Rune(RUNE), + supply: 1000, + timestamp: 2, + ..Default::default() + }, + )], + [( + OutPoint { + txid: txid1, + vout: 0, + }, + vec![(id, 1000)], + )], + ); + } + + #[test] + fn etching_with_limit_can_be_minted() { + let context = Context::builder().arg("--index-runes").build(); + + context.mine_blocks(1); + + // etch the rune + let txid0 = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Witness::new())], + op_return: Some( + Runestone { + etching: Some(Etching { + rune: Some(Rune(RUNE)), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() } .encipher(), ), @@ -4281,24 +4348,29 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), timestamp: 2, mints: 0, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), ..Default::default() }, )], [], ); + // claim the rune let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0, Witness::new())], op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4313,11 +4385,14 @@ mod tests { id, RuneEntry { etching: txid0, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), + mints: 1, rune: Rune(RUNE), - limit: Some(1000), supply: 1000, timestamp: 2, - mints: 1, ..Default::default() }, )], @@ -4330,15 +4405,17 @@ mod tests { )], ); + // claim the rune let txid2 = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(3, 0, 0, Witness::new())], op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4353,11 +4430,70 @@ mod tests { id, RuneEntry { etching: txid0, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), + mints: 2, rune: Rune(RUNE), - limit: Some(1000), supply: 2000, timestamp: 2, - mints: 2, + ..Default::default() + }, + )], + [ + ( + OutPoint { + txid: txid2, + vout: 0, + }, + vec![(id, 1000)], + ), + ( + OutPoint { + txid: txid1, + vout: 0, + }, + vec![(id, 1000)], + ), + ], + ); + + // claim the rune in a burn runestone + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(4, 0, 0, Witness::new())], + op_return: Some( + Runestone { + burn: true, + claim: Some(u128::from(id)), + edicts: vec![Edict { + id: u128::from(id), + amount: 1000, + output: 0, + }], + ..Default::default() + } + .encipher(), + ), + ..Default::default() + }); + + context.mine_blocks(1); + + context.assert_runes( + [( + id, + RuneEntry { + burned: 1000, + etching: txid0, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), + mints: 3, + rune: Rune(RUNE), + supply: 3000, + timestamp: 2, ..Default::default() }, )], @@ -4392,8 +4528,11 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), - term: Some(2), + mint: Some(Mint { + limit: Some(1000), + term: Some(2), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4416,8 +4555,11 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), - end: Some(4), + mint: Some(MintEntry { + limit: Some(1000), + end: Some(4), + ..Default::default() + }), timestamp: 2, ..Default::default() }, @@ -4430,10 +4572,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4449,9 +4592,12 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + end: Some(4), + ..Default::default() + }), supply: 1000, - end: Some(4), timestamp: 2, mints: 1, ..Default::default() @@ -4471,10 +4617,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4490,10 +4637,13 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), supply: 1000, - end: Some(4), timestamp: 2, + mint: Some(MintEntry { + limit: Some(1000), + end: Some(4), + ..Default::default() + }), mints: 1, ..Default::default() }, @@ -4525,8 +4675,11 @@ mod tests { }], etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), - term: Some(0), + mint: Some(Mint { + limit: Some(1000), + term: Some(0), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4549,8 +4702,11 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - limit: Some(1000), - end: Some(2), + mint: Some(MintEntry { + limit: Some(1000), + end: Some(2), + ..Default::default() + }), timestamp: 2, ..Default::default() }, @@ -4564,10 +4720,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1, output: 3, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4583,9 +4740,12 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - limit: Some(1000), - end: Some(2), timestamp: 2, + mint: Some(MintEntry { + limit: Some(1000), + end: Some(2), + ..Default::default() + }), ..Default::default() }, )], @@ -4605,8 +4765,11 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), - deadline: Some(4), + mint: Some(Mint { + limit: Some(1000), + deadline: Some(4), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4627,11 +4790,14 @@ mod tests { [( id, RuneEntry { - deadline: Some(4), etching: txid0, - limit: Some(1000), rune: Rune(RUNE), timestamp: 2, + mint: Some(MintEntry { + deadline: Some(4), + limit: Some(1000), + ..Default::default() + }), ..Default::default() }, )], @@ -4643,10 +4809,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4660,13 +4827,16 @@ mod tests { [( id, RuneEntry { - deadline: Some(4), - etching: txid0, - limit: Some(1000), rune: Rune(RUNE), supply: 1000, timestamp: 2, mints: 1, + etching: txid0, + mint: Some(MintEntry { + deadline: Some(4), + limit: Some(1000), + ..Default::default() + }), ..Default::default() }, )], @@ -4684,10 +4854,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 1000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4703,10 +4874,13 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), supply: 1000, - deadline: Some(4), timestamp: 2, + mint: Some(MintEntry { + limit: Some(1000), + deadline: Some(4), + ..Default::default() + }), mints: 1, ..Default::default() }, @@ -4733,7 +4907,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4756,7 +4933,10 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, ..Default::default() }, @@ -4770,10 +4950,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 0, output: 3, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4789,9 +4970,12 @@ mod tests { RuneEntry { etching: txid0, rune: Rune(RUNE), - limit: Some(1000), supply: 1000, timestamp: 2, + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), mints: 1, ..Default::default() }, @@ -4827,7 +5011,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), ..Default::default() }), edicts: vec![Edict { @@ -4855,7 +5042,10 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, supply: 1000, ..Default::default() @@ -4866,7 +5056,7 @@ mod tests { } #[test] - fn limit_over_max_limit_is_ignored() { + fn limit_over_max_is_clamped() { let context = Context::builder().arg("--index-runes").build(); context.mine_blocks(1); @@ -4877,7 +5067,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(MAX_LIMIT + 1), + mint: Some(Mint { + limit: Some(MAX_LIMIT + 1), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4901,21 +5094,27 @@ mod tests { etching, rune: Rune(RUNE), timestamp: 2, + mint: Some(MintEntry { + limit: Some(MAX_LIMIT), + deadline: None, + end: None, + }), ..Default::default() }, )], [], ); - context.rpc_server.broadcast_tx(TransactionTemplate { + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0, Witness::new())], op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: MAX_LIMIT + 1, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -4932,10 +5131,17 @@ mod tests { etching, rune: Rune(RUNE), timestamp: 2, + mints: 1, + supply: MAX_LIMIT, + mint: Some(MintEntry { + limit: Some(MAX_LIMIT), + deadline: None, + end: None, + }), ..Default::default() }, )], - [], + [(OutPoint { txid, vout: 0 }, vec![(id, MAX_LIMIT)])], ); } @@ -4951,7 +5157,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - term: Some(1), + mint: Some(Mint { + term: Some(1), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -4974,8 +5183,11 @@ mod tests { RuneEntry { etching, rune: Rune(RUNE), - limit: Some(MAX_LIMIT), - end: Some(3), + mint: Some(MintEntry { + limit: None, + end: Some(3), + ..Default::default() + }), timestamp: 2, ..Default::default() }, @@ -4996,7 +5208,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), ..Default::default() }), edicts: vec![Edict { @@ -5024,7 +5239,10 @@ mod tests { RuneEntry { etching, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, supply: 1000, ..Default::default() @@ -5044,10 +5262,11 @@ mod tests { op_return: Some( Runestone { edicts: vec![Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 2000, output: 0, }], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -5068,7 +5287,10 @@ mod tests { RuneEntry { etching, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, supply: 2000, mints: 1, @@ -5106,7 +5328,10 @@ mod tests { Runestone { etching: Some(Etching { rune: Some(Rune(RUNE)), - limit: Some(1000), + mint: Some(Mint { + limit: Some(1000), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -5129,7 +5354,10 @@ mod tests { RuneEntry { etching, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, ..Default::default() }, @@ -5143,21 +5371,22 @@ mod tests { Runestone { edicts: vec![ Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 500, output: 0, }, Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 500, output: 0, }, Edict { - id: u128::from(id) | CLAIM_BIT, + id: u128::from(id), amount: 500, output: 0, }, ], + claim: Some(u128::from(id)), ..Default::default() } .encipher(), @@ -5178,7 +5407,10 @@ mod tests { RuneEntry { etching, rune: Rune(RUNE), - limit: Some(1000), + mint: Some(MintEntry { + limit: Some(1000), + ..Default::default() + }), timestamp: 2, supply: 1000, mints: 1, diff --git a/src/runes/etching.rs b/src/runes/etching.rs index 75e1344f55..1f051148d8 100644 --- a/src/runes/etching.rs +++ b/src/runes/etching.rs @@ -2,11 +2,9 @@ use super::*; #[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] pub struct Etching { - pub deadline: Option, pub divisibility: u8, - pub limit: Option, + pub mint: Option, pub rune: Option, pub spacers: u32, pub symbol: Option, - pub term: Option, } diff --git a/src/runes/flag.rs b/src/runes/flag.rs index fcc39e93b9..e0c9f93160 100644 --- a/src/runes/flag.rs +++ b/src/runes/flag.rs @@ -1,5 +1,6 @@ pub(super) enum Flag { Etch = 0, + Mint = 1, #[allow(unused)] Burn = 127, } diff --git a/src/runes/mint.rs b/src/runes/mint.rs new file mode 100644 index 0000000000..5632d9b023 --- /dev/null +++ b/src/runes/mint.rs @@ -0,0 +1,8 @@ +use super::*; + +#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] +pub struct Mint { + pub deadline: Option, + pub limit: Option, + pub term: Option, +} diff --git a/src/runes/pile.rs b/src/runes/pile.rs index 4ba6822514..fbf429bec5 100644 --- a/src/runes/pile.rs +++ b/src/runes/pile.rs @@ -1,9 +1,10 @@ use super::*; -pub(crate) struct Pile { - pub(crate) amount: u128, - pub(crate) divisibility: u8, - pub(crate) symbol: Option, +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)] +pub struct Pile { + pub amount: u128, + pub divisibility: u8, + pub symbol: Option, } impl Display for Pile { @@ -122,7 +123,7 @@ mod tests { ); assert_eq!( Pile { - amount: u128::max_value(), + amount: u128::MAX, divisibility: 18, symbol: None, } @@ -131,7 +132,7 @@ mod tests { ); assert_eq!( Pile { - amount: u128::max_value(), + amount: u128::MAX, divisibility: MAX_DIVISIBILITY, symbol: None, } diff --git a/src/runes/rune.rs b/src/runes/rune.rs index 8321c81680..04bbc3e00e 100644 --- a/src/runes/rune.rs +++ b/src/runes/rune.rs @@ -88,14 +88,14 @@ impl<'de> Deserialize<'de> for Rune { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } impl Display for Rune { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut n = self.0; - if n == u128::max_value() { + if n == u128::MAX { return write!(f, "BCGDENLQRQWDSLRUGSNLBTMFIJAV"); } @@ -120,7 +120,7 @@ impl Display for Rune { } impl FromStr for Rune { - type Err = crate::Error; + type Err = Error; fn from_str(s: &str) -> crate::Result { let mut x = 0u128; @@ -183,9 +183,9 @@ mod tests { case(27, "AB"); case(51, "AZ"); case(52, "BA"); - case(u128::max_value() - 2, "BCGDENLQRQWDSLRUGSNLBTMFIJAT"); - case(u128::max_value() - 1, "BCGDENLQRQWDSLRUGSNLBTMFIJAU"); - case(u128::max_value(), "BCGDENLQRQWDSLRUGSNLBTMFIJAV"); + case(u128::MAX - 2, "BCGDENLQRQWDSLRUGSNLBTMFIJAT"); + case(u128::MAX - 1, "BCGDENLQRQWDSLRUGSNLBTMFIJAU"); + case(u128::MAX, "BCGDENLQRQWDSLRUGSNLBTMFIJAV"); } #[test] @@ -217,7 +217,7 @@ mod tests { case(END - 1, "A"); case(END, "A"); case(END + 1, "A"); - case(u32::max_value(), "A"); + case(u32::MAX, "A"); case(START + INTERVAL * 00 - 1, "AAAAAAAAAAAAA"); case(START + INTERVAL * 00 + 0, "ZZYZXBRKWXVA"); diff --git a/src/runes/rune_id.rs b/src/runes/rune_id.rs index d5a4e3760c..2ba260207f 100644 --- a/src/runes/rune_id.rs +++ b/src/runes/rune_id.rs @@ -25,16 +25,16 @@ impl From for u128 { impl Display for RuneId { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}/{}", self.height, self.index,) + write!(f, "{}:{}", self.height, self.index,) } } impl FromStr for RuneId { - type Err = crate::Error; + type Err = Error; fn from_str(s: &str) -> Result { let (height, index) = s - .split_once('/') + .split_once(':') .ok_or_else(|| anyhow!("invalid rune ID: {s}"))?; Ok(Self { @@ -58,7 +58,7 @@ impl<'de> Deserialize<'de> for RuneId { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } @@ -86,19 +86,19 @@ mod tests { index: 2 } .to_string(), - "1/2" + "1:2" ); } #[test] fn from_str() { - assert!("/".parse::().is_err()); - assert!("1/".parse::().is_err()); - assert!("/2".parse::().is_err()); - assert!("a/2".parse::().is_err()); - assert!("1/a".parse::().is_err()); + assert!(":".parse::().is_err()); + assert!("1:".parse::().is_err()); + assert!(":2".parse::().is_err()); + assert!("a:2".parse::().is_err()); + assert!("1:a".parse::().is_err()); assert_eq!( - "1/2".parse::().unwrap(), + "1:2".parse::().unwrap(), RuneId { height: 1, index: 2 @@ -125,7 +125,7 @@ mod tests { height: 1, index: 2, }; - let json = "\"1/2\""; + let json = "\"1:2\""; assert_eq!(serde_json::to_string(&rune_id).unwrap(), json); assert_eq!(serde_json::from_str::(json).unwrap(), rune_id); } diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs index c56a41d2dc..3c4f289772 100644 --- a/src/runes/runestone.rs +++ b/src/runes/runestone.rs @@ -4,10 +4,11 @@ const MAX_SPACERS: u32 = 0b00000111_11111111_11111111_11111111; #[derive(Default, Serialize, Debug, PartialEq)] pub struct Runestone { + pub burn: bool, + pub claim: Option, + pub default_output: Option, pub edicts: Vec, pub etching: Option, - pub default_output: Option, - pub burn: bool, } struct Message { @@ -61,6 +62,8 @@ impl Runestone { let Message { mut fields, edicts } = Message::from_integers(&integers); + let claim = Tag::Claim.take(&mut fields); + let deadline = Tag::Deadline .take(&mut fields) .and_then(|deadline| u32::try_from(deadline).ok()); @@ -77,7 +80,7 @@ impl Runestone { let limit = Tag::Limit .take(&mut fields) - .and_then(|limit| (limit <= MAX_LIMIT).then_some(limit)); + .map(|limit| limit.min(MAX_LIMIT)); let rune = Tag::Rune.take(&mut fields).map(Rune); @@ -100,15 +103,19 @@ impl Runestone { let etch = Flag::Etch.take(&mut flags); + let mint = Flag::Mint.take(&mut flags); + let etching = if etch { Some(Etching { - deadline, divisibility, - limit, rune, spacers, symbol, - term, + mint: mint.then_some(Mint { + deadline, + limit, + term, + }), }) } else { None @@ -116,6 +123,7 @@ impl Runestone { Ok(Some(Self { burn: flags != 0 || fields.keys().any(|tag| tag % 2 == 0), + claim, default_output, edicts, etching, @@ -129,16 +137,16 @@ impl Runestone { let mut flags = 0; Flag::Etch.set(&mut flags); + if etching.mint.is_some() { + Flag::Mint.set(&mut flags); + } + Tag::Flags.encode(flags, &mut payload); if let Some(rune) = etching.rune { Tag::Rune.encode(rune.0, &mut payload); } - if let Some(deadline) = etching.deadline { - Tag::Deadline.encode(deadline.into(), &mut payload); - } - if etching.divisibility != 0 { Tag::Divisibility.encode(etching.divisibility.into(), &mut payload); } @@ -151,15 +159,25 @@ impl Runestone { Tag::Symbol.encode(symbol.into(), &mut payload); } - if let Some(limit) = etching.limit { - Tag::Limit.encode(limit, &mut payload); - } + if let Some(mint) = etching.mint { + if let Some(deadline) = mint.deadline { + Tag::Deadline.encode(deadline.into(), &mut payload); + } + + if let Some(limit) = mint.limit { + Tag::Limit.encode(limit, &mut payload); + } - if let Some(term) = etching.term { - Tag::Term.encode(term.into(), &mut payload); + if let Some(term) = mint.term { + Tag::Term.encode(term.into(), &mut payload); + } } } + if let Some(claim) = self.claim { + Tag::Claim.encode(claim, &mut payload); + } + if let Some(default_output) = self.default_output { Tag::DefaultOutput.encode(default_output.into(), &mut payload); } @@ -187,8 +205,8 @@ impl Runestone { .push_opcode(opcodes::all::OP_RETURN) .push_slice(b"RUNE_TEST"); - for chunk in payload.chunks(bitcoin::blockdata::constants::MAX_SCRIPT_ELEMENT_SIZE) { - let push: &bitcoin::script::PushBytes = chunk.try_into().unwrap(); + for chunk in payload.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + let push: &script::PushBytes = chunk.try_into().unwrap(); builder = builder.push_slice(push); } @@ -557,12 +575,36 @@ mod tests { ); } + #[test] + fn etch_flag_is_required_to_etch_rune_even_if_mint_is_set() { + assert_eq!( + decipher(&[ + Tag::Flags.into(), + Flag::Mint.mask(), + Tag::Term.into(), + 4, + Tag::Body.into(), + 1, + 2, + 3 + ]), + Runestone { + edicts: vec![Edict { + id: 1, + amount: 2, + output: 3, + }], + ..Default::default() + }, + ); + } + #[test] fn decipher_etching_with_term() { assert_eq!( decipher(&[ Tag::Flags.into(), - Flag::Etch.mask(), + Flag::Etch.mask() | Flag::Mint.mask(), Tag::Term.into(), 4, Tag::Body.into(), @@ -577,7 +619,10 @@ mod tests { output: 3, }], etching: Some(Etching { - term: Some(4), + mint: Some(Mint { + term: Some(4), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -590,7 +635,7 @@ mod tests { assert_eq!( decipher(&[ Tag::Flags.into(), - Flag::Etch.mask(), + Flag::Etch.mask() | Flag::Mint.mask(), Tag::Limit.into(), 4, Tag::Body.into(), @@ -605,7 +650,10 @@ mod tests { output: 3, }], etching: Some(Etching { - limit: Some(4), + mint: Some(Mint { + limit: Some(4), + ..Default::default() + }), ..Default::default() }), ..Default::default() @@ -860,7 +908,7 @@ mod tests { assert_eq!( decipher(&[ Tag::Flags.into(), - Flag::Etch.mask(), + Flag::Etch.mask() | Flag::Mint.mask(), Tag::Rune.into(), 4, Tag::Deadline.into(), @@ -888,11 +936,13 @@ mod tests { }], etching: Some(Etching { rune: Some(Rune(4)), - deadline: Some(7), + mint: Some(Mint { + deadline: Some(7), + term: Some(2), + limit: Some(3), + }), divisibility: 1, symbol: Some('a'), - term: Some(2), - limit: Some(3), spacers: 5, }), ..Default::default() @@ -925,9 +975,7 @@ mod tests { amount: 2, output: 3, }], - etching: None, - default_output: None, - burn: false, + ..Default::default() }, ); } @@ -1016,7 +1064,7 @@ mod tests { #[test] fn id_deltas_saturate_to_max() { assert_eq!( - decipher(&[Tag::Body.into(), 1, 2, 3, u128::max_value(), 5, 6]), + decipher(&[Tag::Body.into(), 1, 2, 3, u128::MAX, 5, 6]), Runestone { edicts: vec![ Edict { @@ -1025,7 +1073,7 @@ mod tests { output: 3, }, Edict { - id: u128::max_value(), + id: u128::MAX, amount: 5, output: 6, }, @@ -1213,12 +1261,14 @@ mod tests { Vec::new(), Some(Etching { divisibility: MAX_DIVISIBILITY, - deadline: Some(10000), + mint: Some(Mint { + deadline: Some(10000), + limit: Some(1), + term: Some(1), + }), rune: Some(Rune(0)), symbol: Some('$'), - limit: Some(1), spacers: 1, - term: Some(1), }), 19, ); @@ -1226,7 +1276,7 @@ mod tests { case( Vec::new(), Some(Etching { - rune: Some(Rune(u128::max_value())), + rune: Some(Rune(u128::MAX)), ..Default::default() }), 24, @@ -1244,7 +1294,7 @@ mod tests { }], Some(Etching { divisibility: MAX_DIVISIBILITY, - rune: Some(Rune(u128::max_value())), + rune: Some(Rune(u128::MAX)), ..Default::default() }), 30, @@ -1252,7 +1302,7 @@ mod tests { case( vec![Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 0, index: 0, @@ -1262,7 +1312,7 @@ mod tests { }], Some(Etching { divisibility: MAX_DIVISIBILITY, - rune: Some(Rune(u128::max_value())), + rune: Some(Rune(u128::MAX)), ..Default::default() }), 48, @@ -1273,7 +1323,7 @@ mod tests { amount: 0, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1284,20 +1334,10 @@ mod tests { case( vec![Edict { - amount: 0, - id: CLAIM_BIT, - output: 0, - }], - None, - 12, - ); - - case( - vec![Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1309,19 +1349,19 @@ mod tests { case( vec![ Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, }, Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1334,28 +1374,28 @@ mod tests { case( vec![ Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, }, Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, }, Edict { - amount: u128::max_value(), + amount: u128::MAX, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1368,10 +1408,10 @@ mod tests { case( vec![ Edict { - amount: u64::max_value().into(), + amount: u64::MAX.into(), id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1385,10 +1425,10 @@ mod tests { case( vec![ Edict { - amount: u64::max_value().into(), + amount: u64::MAX.into(), id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1402,10 +1442,10 @@ mod tests { case( vec![ Edict { - amount: u64::max_value().into(), + amount: u64::MAX.into(), id: RuneId { height: 0, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1422,7 +1462,7 @@ mod tests { amount: 1_000_000_000_000_000_000, id: RuneId { height: 1_000_000, - index: u16::max_value(), + index: u16::MAX, } .into(), output: 0, @@ -1441,7 +1481,7 @@ mod tests { Tag::Flags.into(), Flag::Etch.mask(), Tag::Term.into(), - u128::from(u64::max_value()) + 1, + u128::from(u64::MAX) + 1, ]), Runestone { etching: Some(Etching::default()), @@ -1491,11 +1531,13 @@ mod tests { Runestone { etching: Some(Etching { divisibility: 1, - deadline: Some(2), - limit: Some(3), + mint: Some(Mint { + deadline: Some(2), + limit: Some(3), + term: Some(5), + }), symbol: Some('@'), rune: Some(Rune(4)), - term: Some(5), spacers: 6, }), edicts: vec![ @@ -1511,27 +1553,32 @@ mod tests { }, ], default_output: Some(11), - burn: false, + burn: true, + claim: Some(12), }, &[ Tag::Flags.into(), - Flag::Etch.mask(), + Flag::Etch.mask() | Flag::Mint.mask(), Tag::Rune.into(), 4, - Tag::Deadline.into(), - 2, Tag::Divisibility.into(), 1, Tag::Spacers.into(), 6, Tag::Symbol.into(), '@'.into(), + Tag::Deadline.into(), + 2, Tag::Limit.into(), 3, Tag::Term.into(), 5, + Tag::Claim.into(), + 12, Tag::DefaultOutput.into(), 11, + Tag::Burn.into(), + 0, Tag::Body.into(), 6, 5, @@ -1546,11 +1593,9 @@ mod tests { Runestone { etching: Some(Etching { divisibility: 0, - deadline: None, - limit: None, + mint: None, symbol: None, rune: Some(Rune(3)), - term: None, spacers: 0, }), burn: false, @@ -1563,11 +1608,9 @@ mod tests { Runestone { etching: Some(Etching { divisibility: 0, - deadline: None, - limit: None, + mint: None, symbol: None, rune: None, - term: None, spacers: 0, }), burn: false, diff --git a/src/runes/spaced_rune.rs b/src/runes/spaced_rune.rs index 73e2c7c6f1..d1dedafe80 100644 --- a/src/runes/spaced_rune.rs +++ b/src/runes/spaced_rune.rs @@ -68,7 +68,7 @@ impl<'de> Deserialize<'de> for SpacedRune { where D: Deserializer<'de>, { - Ok(DeserializeFromStr::deserialize(deserializer)?.0) + DeserializeFromStr::with(deserializer) } } diff --git a/src/runes/tag.rs b/src/runes/tag.rs index f33ab00caa..9cb95bd9a5 100644 --- a/src/runes/tag.rs +++ b/src/runes/tag.rs @@ -9,14 +9,15 @@ pub(super) enum Tag { Term = 8, Deadline = 10, DefaultOutput = 12, + Claim = 14, #[allow(unused)] - Burn = 254, + Burn = 126, Divisibility = 1, Spacers = 3, Symbol = 5, #[allow(unused)] - Nop = 255, + Nop = 127, } impl Tag { @@ -81,4 +82,15 @@ mod tests { assert_eq!(payload, [2, 3, 4, 5]); } + + #[test] + fn burn_and_nop_are_one_byte() { + let mut payload = Vec::new(); + Tag::Burn.encode(0, &mut payload); + assert_eq!(payload.len(), 2); + + let mut payload = Vec::new(); + Tag::Nop.encode(0, &mut payload); + assert_eq!(payload.len(), 2); + } } diff --git a/src/runes/varint.rs b/src/runes/varint.rs index 0a9ff7a90e..438673a836 100644 --- a/src/runes/varint.rs +++ b/src/runes/varint.rs @@ -48,7 +48,7 @@ mod tests { #[test] fn u128_max_round_trips_successfully() { - let n = u128::max_value(); + let n = u128::MAX; let encoded = encode(n); let (decoded, length) = decode(&encoded); assert_eq!(decoded, n); diff --git a/src/server_config.rs b/src/server_config.rs index 86b363dc5b..57cd3e0dd1 100644 --- a/src/server_config.rs +++ b/src/server_config.rs @@ -3,9 +3,10 @@ use super::*; #[derive(Default)] pub(crate) struct ServerConfig { pub(crate) chain: Chain, + pub(crate) content_proxy: Option, pub(crate) csp_origin: Option, pub(crate) decompress: bool, pub(crate) domain: Option, pub(crate) index_sats: bool, - pub(crate) is_json_api_enabled: bool, + pub(crate) json_api_enabled: bool, } diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000000..34d8e41309 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,1159 @@ +use {super::*, bitcoincore_rpc::Auth}; + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(default, deny_unknown_fields)] +pub struct Settings { + bitcoin_data_dir: Option, + bitcoin_rpc_password: Option, + bitcoin_rpc_url: Option, + bitcoin_rpc_username: Option, + chain: Option, + commit_interval: Option, + config: Option, + config_dir: Option, + cookie_file: Option, + data_dir: Option, + first_inscription_height: Option, + height_limit: Option, + hidden: Option>, + index: Option, + index_cache_size: Option, + index_runes: bool, + index_sats: bool, + index_spent_sats: bool, + index_transactions: bool, + integration_test: bool, + no_index_inscriptions: bool, + server_password: Option, + server_url: Option, + server_username: Option, +} + +impl Settings { + pub(crate) fn load(options: Options) -> Result { + let mut env = BTreeMap::::new(); + + for (var, value) in env::vars_os() { + let Some(var) = var.to_str() else { + continue; + }; + + let Some(key) = var.strip_prefix("ORD_") else { + continue; + }; + + env.insert( + key.into(), + value.into_string().map_err(|value| { + anyhow!( + "environment variable `{var}` not valid unicode: `{}`", + value.to_string_lossy() + ) + })?, + ); + } + + Self::merge(options, env) + } + + pub(crate) fn merge(options: Options, env: BTreeMap) -> Result { + let settings = Settings::from_options(options).or(Settings::from_env(env)?); + + let config_path = if let Some(path) = &settings.config { + Some(path.into()) + } else { + let path = if let Some(dir) = settings.config_dir.clone().or(settings.data_dir.clone()) { + dir + } else { + Self::default_data_dir()? + } + .join("ord.yaml"); + + path.exists().then_some(path) + }; + + let config = if let Some(config_path) = config_path { + serde_yaml::from_reader(File::open(&config_path).context(anyhow!( + "failed to open config file `{}`", + config_path.display() + ))?) + .context(anyhow!( + "failed to deserialize config file `{}`", + config_path.display() + ))? + } else { + Settings::default() + }; + + let settings = settings.or(config).or_defaults()?; + + match ( + &settings.bitcoin_rpc_username, + &settings.bitcoin_rpc_password, + ) { + (None, Some(_rpc_pass)) => bail!("no bitcoin RPC username specified"), + (Some(_rpc_user), None) => bail!("no bitcoin RPC password specified"), + _ => {} + }; + + match (&settings.server_username, &settings.server_password) { + (None, Some(_rpc_pass)) => bail!("no username specified"), + (Some(_rpc_user), None) => bail!("no password specified"), + _ => {} + }; + + Ok(settings) + } + + pub(crate) fn or(self, source: Settings) -> Self { + Self { + bitcoin_data_dir: self.bitcoin_data_dir.or(source.bitcoin_data_dir), + bitcoin_rpc_password: self.bitcoin_rpc_password.or(source.bitcoin_rpc_password), + bitcoin_rpc_url: self.bitcoin_rpc_url.or(source.bitcoin_rpc_url), + bitcoin_rpc_username: self.bitcoin_rpc_username.or(source.bitcoin_rpc_username), + chain: self.chain.or(source.chain), + commit_interval: self.commit_interval.or(source.commit_interval), + config: self.config.or(source.config), + config_dir: self.config_dir.or(source.config_dir), + cookie_file: self.cookie_file.or(source.cookie_file), + data_dir: self.data_dir.or(source.data_dir), + first_inscription_height: self + .first_inscription_height + .or(source.first_inscription_height), + height_limit: self.height_limit.or(source.height_limit), + hidden: Some( + self + .hidden + .iter() + .flatten() + .chain(source.hidden.iter().flatten()) + .cloned() + .collect(), + ), + index: self.index.or(source.index), + index_cache_size: self.index_cache_size.or(source.index_cache_size), + index_runes: self.index_runes || source.index_runes, + index_sats: self.index_sats || source.index_sats, + index_spent_sats: self.index_spent_sats || source.index_spent_sats, + index_transactions: self.index_transactions || source.index_transactions, + integration_test: self.integration_test || source.integration_test, + no_index_inscriptions: self.no_index_inscriptions || source.no_index_inscriptions, + server_password: self.server_password.or(source.server_password), + server_url: self.server_url.or(source.server_url), + server_username: self.server_username.or(source.server_username), + } + } + + pub(crate) fn from_options(options: Options) -> Self { + Self { + bitcoin_data_dir: options.bitcoin_data_dir, + bitcoin_rpc_password: options.bitcoin_rpc_password, + bitcoin_rpc_url: options.bitcoin_rpc_url, + bitcoin_rpc_username: options.bitcoin_rpc_username, + chain: options + .signet + .then_some(Chain::Signet) + .or(options.regtest.then_some(Chain::Regtest)) + .or(options.testnet.then_some(Chain::Testnet)) + .or(options.chain_argument), + commit_interval: options.commit_interval, + config: options.config, + config_dir: options.config_dir, + cookie_file: options.cookie_file, + data_dir: options.data_dir, + first_inscription_height: options.first_inscription_height, + height_limit: options.height_limit, + hidden: None, + index: options.index, + index_cache_size: options.index_cache_size, + index_runes: options.index_runes, + index_sats: options.index_sats, + index_spent_sats: options.index_spent_sats, + index_transactions: options.index_transactions, + integration_test: options.integration_test, + no_index_inscriptions: options.no_index_inscriptions, + server_password: options.server_password, + server_url: None, + server_username: options.server_username, + } + } + + pub(crate) fn from_env(env: BTreeMap) -> Result { + let get_bool = |key| { + env + .get(key) + .map(|value| !value.is_empty()) + .unwrap_or_default() + }; + + let get_string = |key| env.get(key).cloned(); + + let get_path = |key| env.get(key).map(PathBuf::from); + + let get_chain = |key| { + env + .get(key) + .map(|chain| chain.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as chain")) + }; + + let inscriptions = |key| { + env + .get(key) + .map(|inscriptions| { + inscriptions + .split_whitespace() + .map(|inscription_id| inscription_id.parse::()) + .collect::, inscription_id::ParseError>>() + }) + .transpose() + .with_context(|| { + format!("failed to parse environment variable ORD_{key} as inscription list") + }) + }; + + let get_u32 = |key| { + env + .get(key) + .map(|int| int.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as u32")) + }; + let get_usize = |key| { + env + .get(key) + .map(|int| int.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as usize")) + }; + + Ok(Self { + bitcoin_data_dir: get_path("BITCOIN_DATA_DIR"), + bitcoin_rpc_password: get_string("BITCOIN_RPC_PASSWORD"), + bitcoin_rpc_url: get_string("BITCOIN_RPC_URL"), + bitcoin_rpc_username: get_string("BITCOIN_RPC_USERNAME"), + chain: get_chain("CHAIN")?, + commit_interval: get_usize("COMMIT_INTERVAL")?, + config: get_path("CONFIG"), + config_dir: get_path("CONFIG_DIR"), + cookie_file: get_path("COOKIE_FILE"), + data_dir: get_path("DATA_DIR"), + first_inscription_height: get_u32("FIRST_INSCRIPTION_HEIGHT")?, + height_limit: get_u32("HEIGHT_LIMIT")?, + hidden: inscriptions("HIDDEN")?, + index: get_path("INDEX"), + index_cache_size: get_usize("INDEX_CACHE_SIZE")?, + index_runes: get_bool("INDEX_RUNES"), + index_sats: get_bool("INDEX_SATS"), + index_spent_sats: get_bool("INDEX_SPENT_SATS"), + index_transactions: get_bool("INDEX_TRANSACTIONS"), + integration_test: get_bool("INTEGRATION_TEST"), + no_index_inscriptions: get_bool("NO_INDEX_INSCRIPTIONS"), + server_password: get_string("SERVER_PASSWORD"), + server_url: get_string("SERVER_URL"), + server_username: get_string("SERVER_USERNAME"), + }) + } + + pub(crate) fn for_env(dir: &Path, rpc_url: &str, server_url: &str) -> Self { + Self { + bitcoin_data_dir: Some(dir.into()), + bitcoin_rpc_password: None, + bitcoin_rpc_url: Some(rpc_url.into()), + bitcoin_rpc_username: None, + chain: Some(Chain::Regtest), + commit_interval: None, + config: None, + config_dir: None, + cookie_file: None, + data_dir: Some(dir.into()), + first_inscription_height: None, + height_limit: None, + hidden: None, + index: None, + index_cache_size: None, + index_runes: true, + index_sats: true, + index_spent_sats: false, + index_transactions: false, + integration_test: false, + no_index_inscriptions: false, + server_password: None, + server_url: Some(server_url.into()), + server_username: None, + } + } + + pub(crate) fn or_defaults(self) -> Result { + let chain = self.chain.unwrap_or_default(); + + let bitcoin_data_dir = match &self.bitcoin_data_dir { + Some(bitcoin_data_dir) => bitcoin_data_dir.clone(), + None => { + if cfg!(target_os = "linux") { + dirs::home_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get home dir"))? + .join(".bitcoin") + } else { + dirs::data_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get data dir"))? + .join("Bitcoin") + } + } + }; + + let cookie_file = match self.cookie_file { + Some(cookie_file) => cookie_file, + None => chain.join_with_data_dir(&bitcoin_data_dir).join(".cookie"), + }; + + let data_dir = chain.join_with_data_dir(match &self.data_dir { + Some(data_dir) => data_dir.clone(), + None => Self::default_data_dir()?, + }); + + let index = match &self.index { + Some(path) => path.clone(), + None => data_dir.join("index.redb"), + }; + + Ok(Self { + bitcoin_data_dir: Some(bitcoin_data_dir), + bitcoin_rpc_password: self.bitcoin_rpc_password, + bitcoin_rpc_url: Some( + self + .bitcoin_rpc_url + .clone() + .unwrap_or_else(|| format!("127.0.0.1:{}", chain.default_rpc_port())), + ), + bitcoin_rpc_username: self.bitcoin_rpc_username, + chain: Some(chain), + commit_interval: Some(self.commit_interval.unwrap_or(5000)), + config: None, + config_dir: None, + cookie_file: Some(cookie_file), + data_dir: Some(data_dir), + first_inscription_height: Some(if self.integration_test { + 0 + } else { + self + .first_inscription_height + .unwrap_or_else(|| chain.first_inscription_height()) + }), + height_limit: self.height_limit, + hidden: self.hidden, + index: Some(index), + index_cache_size: Some(match self.index_cache_size { + Some(index_cache_size) => index_cache_size, + None => { + let mut sys = System::new(); + sys.refresh_memory(); + usize::try_from(sys.total_memory() / 4)? + } + }), + index_runes: self.index_runes, + index_sats: self.index_sats, + index_spent_sats: self.index_spent_sats, + index_transactions: self.index_transactions, + integration_test: self.integration_test, + no_index_inscriptions: self.no_index_inscriptions, + server_password: self.server_password, + server_url: self.server_url, + server_username: self.server_username, + }) + } + + pub(crate) fn default_data_dir() -> Result { + Ok( + dirs::data_dir() + .context("could not get data dir")? + .join("ord"), + ) + } + + pub(crate) fn bitcoin_credentials(&self) -> Result { + if let Some((user, pass)) = &self + .bitcoin_rpc_username + .as_ref() + .zip(self.bitcoin_rpc_password.as_ref()) + { + Ok(Auth::UserPass((*user).clone(), (*pass).clone())) + } else { + Ok(Auth::CookieFile(self.cookie_file()?)) + } + } + + pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { + let rpc_url = self.bitcoin_rpc_url(wallet); + + let bitcoin_credentials = self.bitcoin_credentials()?; + + log::info!( + "Connecting to Bitcoin Core at {}", + self.bitcoin_rpc_url(None) + ); + + if let Auth::CookieFile(cookie_file) = &bitcoin_credentials { + log::info!( + "Using credentials from cookie file at `{}`", + cookie_file.display() + ); + + ensure!( + cookie_file.is_file(), + "cookie file `{}` does not exist", + cookie_file.display() + ); + } + + let client = Client::new(&rpc_url, bitcoin_credentials) + .with_context(|| format!("failed to connect to Bitcoin Core RPC at `{rpc_url}`"))?; + + let mut checks = 0; + let rpc_chain = loop { + match client.get_blockchain_info() { + Ok(blockchain_info) => { + break match blockchain_info.chain.as_str() { + "main" => Chain::Mainnet, + "test" => Chain::Testnet, + "regtest" => Chain::Regtest, + "signet" => Chain::Signet, + other => bail!("Bitcoin RPC server on unknown chain: {other}"), + } + } + Err(bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(err))) + if err.code == -28 => {} + Err(err) => bail!("Failed to connect to Bitcoin Core RPC at `{rpc_url}`: {err}"), + } + + ensure! { + checks < 100, + "Failed to connect to Bitcoin Core RPC at `{rpc_url}`", + } + + checks += 1; + thread::sleep(Duration::from_millis(100)); + }; + + let ord_chain = self.chain(); + + if rpc_chain != ord_chain { + bail!("Bitcoin RPC server is on {rpc_chain} but ord is on {ord_chain}"); + } + + Ok(client) + } + + pub(crate) fn chain(&self) -> Chain { + self.chain.unwrap() + } + + pub(crate) fn commit_interval(&self) -> usize { + self.commit_interval.unwrap() + } + + pub(crate) fn cookie_file(&self) -> Result { + if let Some(cookie_file) = &self.cookie_file { + return Ok(cookie_file.clone()); + } + + let path = if let Some(bitcoin_data_dir) = &self.bitcoin_data_dir { + bitcoin_data_dir.clone() + } else if cfg!(target_os = "linux") { + dirs::home_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get home dir"))? + .join(".bitcoin") + } else { + dirs::data_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get data dir"))? + .join("Bitcoin") + }; + + let path = self.chain().join_with_data_dir(path); + + Ok(path.join(".cookie")) + } + + pub(crate) fn credentials(&self) -> Option<(&str, &str)> { + self + .server_username + .as_deref() + .zip(self.server_password.as_deref()) + } + + pub(crate) fn data_dir(&self) -> PathBuf { + self.data_dir.as_ref().unwrap().into() + } + + pub(crate) fn first_inscription_height(&self) -> u32 { + self.first_inscription_height.unwrap() + } + + pub(crate) fn first_rune_height(&self) -> u32 { + if self.integration_test { + 0 + } else { + self.chain.unwrap().first_rune_height() + } + } + + pub(crate) fn height_limit(&self) -> Option { + self.height_limit + } + + pub(crate) fn index(&self) -> &Path { + self.index.as_ref().unwrap() + } + + pub(crate) fn index_inscriptions(&self) -> bool { + !self.no_index_inscriptions + } + + pub(crate) fn index_runes(&self) -> bool { + self.index_runes + } + + pub(crate) fn index_cache_size(&self) -> usize { + self.index_cache_size.unwrap() + } + + pub(crate) fn index_sats(&self) -> bool { + self.index_sats + } + + pub(crate) fn index_spent_sats(&self) -> bool { + self.index_spent_sats + } + + pub(crate) fn index_transactions(&self) -> bool { + self.index_transactions + } + + pub(crate) fn integration_test(&self) -> bool { + self.integration_test + } + + pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { + self + .hidden + .as_ref() + .map(|hidden| hidden.contains(&inscription_id)) + .unwrap_or_default() + } + + pub(crate) fn bitcoin_rpc_url(&self, wallet_name: Option) -> String { + let base_url = self.bitcoin_rpc_url.as_ref().unwrap(); + match wallet_name { + Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), + None => format!("{base_url}/"), + } + } + + pub(crate) fn server_url(&self) -> Option<&str> { + self.server_url.as_deref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(args: &[&str]) -> Settings { + let args = iter::once("ord") + .chain(args.iter().copied()) + .collect::>(); + Settings::from_options(Options::try_parse_from(args).unwrap()) + .or_defaults() + .unwrap() + } + + fn wallet(args: &str) -> (Settings, subcommand::wallet::WalletCommand) { + match Arguments::try_parse_from(args.split_whitespace()) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Wallet(wallet) => ( + Settings::from_options(arguments.options) + .or_defaults() + .unwrap(), + wallet, + ), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + } + } + + #[test] + fn auth_missing_rpc_pass_is_an_error() { + assert_eq!( + Settings::merge( + Options { + bitcoin_rpc_username: Some("foo".into()), + ..Default::default() + }, + Default::default(), + ) + .unwrap_err() + .to_string(), + "no bitcoin RPC password specified" + ); + } + + #[test] + fn auth_missing_rpc_user_is_an_error() { + assert_eq!( + Settings::merge( + Options { + bitcoin_rpc_password: Some("foo".into()), + ..Default::default() + }, + Default::default(), + ) + .unwrap_err() + .to_string(), + "no bitcoin RPC username specified" + ); + } + + #[test] + fn auth_with_user_and_pass() { + assert_eq!( + parse(&["--bitcoin-rpc-username=foo", "--bitcoin-rpc-password=bar"]) + .bitcoin_credentials() + .unwrap(), + Auth::UserPass("foo".into(), "bar".into()) + ); + } + + #[test] + fn auth_with_cookie_file() { + assert_eq!( + parse(&["--cookie-file=/var/lib/Bitcoin/.cookie"]) + .bitcoin_credentials() + .unwrap(), + Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) + ); + } + + #[test] + fn cookie_file_does_not_exist_error() { + assert_eq!( + parse(&["--cookie-file=/foo/bar/baz/qux/.cookie"]) + .bitcoin_rpc_client(None) + .err() + .unwrap() + .to_string(), + "cookie file `/foo/bar/baz/qux/.cookie` does not exist" + ); + } + + #[test] + fn rpc_server_chain_must_match() { + let rpc_server = test_bitcoincore_rpc::builder() + .network(Network::Testnet) + .build(); + + let settings = parse(&[ + "--cookie-file", + rpc_server.cookie_file().to_str().unwrap(), + "--bitcoin-rpc-url", + &rpc_server.url(), + ]); + + assert_eq!( + settings.bitcoin_rpc_client(None).unwrap_err().to_string(), + "Bitcoin RPC server is on testnet but ord is on mainnet" + ); + } + + #[test] + fn rpc_url_overrides_network() { + assert_eq!( + parse(&["--bitcoin-rpc-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_rpc_url(None), + "127.0.0.1:1234/" + ); + } + + #[test] + fn cookie_file_overrides_network() { + assert_eq!( + parse(&["--cookie-file=/foo/bar", "--chain=signet"]) + .cookie_file() + .unwrap(), + Path::new("/foo/bar") + ); + } + + #[test] + fn use_default_network() { + let settings = parse(&[]); + + assert_eq!(settings.bitcoin_rpc_url(None), "127.0.0.1:8332/"); + + assert!(settings.cookie_file().unwrap().ends_with(".cookie")); + } + + #[test] + fn uses_network_defaults() { + let settings = parse(&["--chain=signet"]); + + assert_eq!(settings.bitcoin_rpc_url(None), "127.0.0.1:38332/"); + + assert!(settings + .cookie_file() + .unwrap() + .display() + .to_string() + .ends_with(if cfg!(windows) { + r"\signet\.cookie" + } else { + "/signet/.cookie" + })); + } + + #[test] + fn mainnet_cookie_file_path() { + let cookie_file = parse(&[]).cookie_file().unwrap().display().to_string(); + + assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { + "/.bitcoin/.cookie" + } else if cfg!(windows) { + r"\Bitcoin\.cookie" + } else { + "/Bitcoin/.cookie" + })) + } + + #[test] + fn othernet_cookie_file_path() { + let cookie_file = parse(&["--chain=signet"]) + .cookie_file() + .unwrap() + .display() + .to_string(); + + assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { + "/.bitcoin/signet/.cookie" + } else if cfg!(windows) { + r"\Bitcoin\signet\.cookie" + } else { + "/Bitcoin/signet/.cookie" + })); + } + + #[test] + fn cookie_file_defaults_to_bitcoin_data_dir() { + let cookie_file = parse(&["--bitcoin-data-dir=foo", "--chain=signet"]) + .cookie_file() + .unwrap() + .display() + .to_string(); + + assert!(cookie_file.ends_with(if cfg!(windows) { + r"foo\signet\.cookie" + } else { + "foo/signet/.cookie" + })); + } + + #[test] + fn mainnet_data_dir() { + let data_dir = parse(&[]).data_dir().display().to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { r"\ord" } else { "/ord" }), + "{data_dir}" + ); + } + + #[test] + fn othernet_data_dir() { + let data_dir = parse(&["--chain=signet"]).data_dir().display().to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { + r"\ord\signet" + } else { + "/ord/signet" + }), + "{data_dir}" + ); + } + + #[test] + fn network_is_joined_with_data_dir() { + let data_dir = parse(&["--chain=signet", "--data-dir=foo"]) + .data_dir() + .display() + .to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { + r"foo\signet" + } else { + "foo/signet" + }), + "{data_dir}" + ); + } + + #[test] + fn network_accepts_aliases() { + fn check_network_alias(alias: &str, suffix: &str) { + let data_dir = parse(&["--chain", alias]).data_dir().display().to_string(); + + assert!(data_dir.ends_with(suffix), "{data_dir}"); + } + + check_network_alias("main", "ord"); + check_network_alias("mainnet", "ord"); + check_network_alias( + "regtest", + if cfg!(windows) { + r"ord\regtest" + } else { + "ord/regtest" + }, + ); + check_network_alias( + "signet", + if cfg!(windows) { + r"ord\signet" + } else { + "ord/signet" + }, + ); + check_network_alias( + "test", + if cfg!(windows) { + r"ord\testnet3" + } else { + "ord/testnet3" + }, + ); + check_network_alias( + "testnet", + if cfg!(windows) { + r"ord\testnet3" + } else { + "ord/testnet3" + }, + ); + } + + #[test] + fn chain_flags() { + Arguments::try_parse_from(["ord", "--signet", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!(parse(&["--signet"]).chain(), Chain::Signet); + assert_eq!(parse(&["-s"]).chain(), Chain::Signet); + + Arguments::try_parse_from(["ord", "--regtest", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!(parse(&["--regtest"]).chain(), Chain::Regtest); + assert_eq!(parse(&["-r"]).chain(), Chain::Regtest); + + Arguments::try_parse_from(["ord", "--testnet", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!(parse(&["--testnet"]).chain(), Chain::Testnet); + assert_eq!(parse(&["-t"]).chain(), Chain::Testnet); + } + + #[test] + fn wallet_flag_overrides_default_name() { + assert_eq!(wallet("ord wallet create").1.name, "ord"); + assert_eq!(wallet("ord wallet --name foo create").1.name, "foo") + } + + #[test] + fn uses_wallet_rpc() { + let (settings, _) = wallet("ord wallet --name foo balance"); + + assert_eq!( + settings.bitcoin_rpc_url(Some("foo".into())), + "127.0.0.1:8332/wallet/foo" + ); + } + + #[test] + fn setting_index_cache_size() { + assert_eq!( + parse(&["--index-cache-size=16000000000",]).index_cache_size(), + 16000000000 + ); + } + + #[test] + fn setting_commit_interval() { + let arguments = + Arguments::try_parse_from(["ord", "--commit-interval", "500", "index", "update"]).unwrap(); + assert_eq!(arguments.options.commit_interval, Some(500)); + } + + #[test] + fn index_runes() { + assert!(parse(&["--chain=signet", "--index-runes"]).index_runes()); + assert!(parse(&["--index-runes"]).index_runes()); + assert!(!parse(&[]).index_runes()); + } + + #[test] + fn bitcoin_rpc_and_pass_setting() { + let config = Settings { + bitcoin_rpc_username: Some("config_user".into()), + bitcoin_rpc_password: Some("config_pass".into()), + ..Default::default() + }; + + let tempdir = TempDir::new().unwrap(); + + let config_path = tempdir.path().join("ord.yaml"); + + fs::write(&config_path, serde_yaml::to_string(&config).unwrap()).unwrap(); + + assert_eq!( + Settings::merge( + Options { + bitcoin_rpc_username: Some("option_user".into()), + bitcoin_rpc_password: Some("option_pass".into()), + config: Some(config_path.clone()), + ..Default::default() + }, + vec![ + ("BITCOIN_RPC_USERNAME".into(), "env_user".into()), + ("BITCOIN_RPC_PASSWORD".into(), "env_pass".into()), + ] + .into_iter() + .collect(), + ) + .unwrap() + .bitcoin_credentials() + .unwrap(), + Auth::UserPass("option_user".into(), "option_pass".into()), + ); + + assert_eq!( + Settings::merge( + Options { + config: Some(config_path.clone()), + ..Default::default() + }, + vec![ + ("BITCOIN_RPC_USERNAME".into(), "env_user".into()), + ("BITCOIN_RPC_PASSWORD".into(), "env_pass".into()), + ] + .into_iter() + .collect(), + ) + .unwrap() + .bitcoin_credentials() + .unwrap(), + Auth::UserPass("env_user".into(), "env_pass".into()), + ); + + assert_eq!( + Settings::merge( + Options { + config: Some(config_path), + ..Default::default() + }, + Default::default(), + ) + .unwrap() + .bitcoin_credentials() + .unwrap(), + Auth::UserPass("config_user".into(), "config_pass".into()), + ); + + assert_matches!( + Settings::merge(Default::default(), Default::default()) + .unwrap() + .bitcoin_credentials() + .unwrap(), + Auth::CookieFile(_), + ); + } + + #[test] + fn example_config_file_is_valid() { + let _: Settings = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); + } + + #[test] + fn from_env() { + let env = vec![ + ("BITCOIN_DATA_DIR", "/bitcoin/data/dir"), + ("BITCOIN_RPC_PASSWORD", "bitcoin password"), + ("BITCOIN_RPC_URL", "url"), + ("BITCOIN_RPC_USERNAME", "bitcoin username"), + ("CHAIN", "signet"), + ("COMMIT_INTERVAL", "1"), + ("CONFIG", "config"), + ("CONFIG_DIR", "config dir"), + ("COOKIE_FILE", "cookie file"), + ("DATA_DIR", "/data/dir"), + ("FIRST_INSCRIPTION_HEIGHT", "2"), + ("HEIGHT_LIMIT", "3"), + ("HIDDEN", "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0"), + ("INDEX", "index"), + ("INDEX_CACHE_SIZE", "4"), + ("INDEX_RUNES", "1"), + ("INDEX_SATS", "1"), + ("INDEX_SPENT_SATS", "1"), + ("INDEX_TRANSACTIONS", "1"), + ("INTEGRATION_TEST", "1"), + ("NO_INDEX_INSCRIPTIONS", "1"), + ("SERVER_PASSWORD", "server password"), + ("SERVER_URL", "server url"), + ("SERVER_USERNAME", "server username"), + ] + .into_iter() + .map(|(key, value)| (key.into(), value.into())) + .collect::>(); + + pretty_assert_eq!( + Settings::from_env(env).unwrap(), + Settings { + bitcoin_data_dir: Some("/bitcoin/data/dir".into()), + bitcoin_rpc_password: Some("bitcoin password".into()), + bitcoin_rpc_url: Some("url".into()), + bitcoin_rpc_username: Some("bitcoin username".into()), + chain: Some(Chain::Signet), + commit_interval: Some(1), + config: Some("config".into()), + config_dir: Some("config dir".into()), + cookie_file: Some("cookie file".into()), + data_dir: Some("/data/dir".into()), + first_inscription_height: Some(2), + height_limit: Some(3), + hidden: Some( + vec![ + "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0" + .parse() + .unwrap(), + "703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0" + .parse() + .unwrap() + ] + .into_iter() + .collect() + ), + index: Some("index".into()), + index_cache_size: Some(4), + index_runes: true, + index_sats: true, + index_spent_sats: true, + index_transactions: true, + integration_test: true, + no_index_inscriptions: true, + server_password: Some("server password".into()), + server_url: Some("server url".into()), + server_username: Some("server username".into()), + } + ); + } + + #[test] + fn from_options() { + pretty_assert_eq!( + Settings::from_options( + Options::try_parse_from([ + "ord", + "--bitcoin-data-dir=/bitcoin/data/dir", + "--bitcoin-rpc-password=bitcoin password", + "--bitcoin-rpc-url=url", + "--bitcoin-rpc-username=bitcoin username", + "--chain=signet", + "--commit-interval=1", + "--config=config", + "--config-dir=config dir", + "--cookie-file=cookie file", + "--data-dir=/data/dir", + "--first-inscription-height=2", + "--height-limit=3", + "--index-cache-size=4", + "--index-runes", + "--index-sats", + "--index-spent-sats", + "--index-transactions", + "--index=index", + "--integration-test", + "--no-index-inscriptions", + "--server-password=server password", + "--server-username=server username", + ]) + .unwrap() + ), + Settings { + bitcoin_data_dir: Some("/bitcoin/data/dir".into()), + bitcoin_rpc_password: Some("bitcoin password".into()), + bitcoin_rpc_url: Some("url".into()), + bitcoin_rpc_username: Some("bitcoin username".into()), + chain: Some(Chain::Signet), + commit_interval: Some(1), + config: Some("config".into()), + config_dir: Some("config dir".into()), + cookie_file: Some("cookie file".into()), + data_dir: Some("/data/dir".into()), + first_inscription_height: Some(2), + height_limit: Some(3), + hidden: None, + index: Some("index".into()), + index_cache_size: Some(4), + index_runes: true, + index_sats: true, + index_spent_sats: true, + index_transactions: true, + integration_test: true, + no_index_inscriptions: true, + server_password: Some("server password".into()), + server_url: None, + server_username: Some("server username".into()), + } + ); + } + + #[test] + fn merge() { + let env = vec![("INDEX", "env")] + .into_iter() + .map(|(key, value)| (key.into(), value.into())) + .collect::>(); + + let config = Settings { + index: Some("config".into()), + ..Default::default() + }; + + let tempdir = TempDir::new().unwrap(); + + let config_path = tempdir.path().join("ord.yaml"); + + fs::write(&config_path, serde_yaml::to_string(&config).unwrap()).unwrap(); + + let options = + Options::try_parse_from(["ord", "--config", config_path.to_str().unwrap()]).unwrap(); + + pretty_assert_eq!( + Settings::merge(options.clone(), Default::default()) + .unwrap() + .index, + Some("config".into()), + ); + + pretty_assert_eq!( + Settings::merge(options, env.clone()).unwrap().index, + Some("env".into()), + ); + + let options = Options::try_parse_from([ + "ord", + "--index=option", + "--config", + config_path.to_str().unwrap(), + ]) + .unwrap(); + + pretty_assert_eq!( + Settings::merge(options, env).unwrap().index, + Some("option".into()), + ); + } +} diff --git a/src/subcommand.rs b/src/subcommand.rs index ff7b406079..2da87735db 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -2,14 +2,15 @@ use super::*; pub mod balances; pub mod decode; +pub mod env; pub mod epochs; pub mod find; pub mod index; pub mod list; pub mod parse; -mod preview; pub mod runes; pub(crate) mod server; +mod settings; pub mod subsidy; pub mod supply; pub mod teleburn; @@ -22,6 +23,8 @@ pub(crate) enum Subcommand { Balances, #[command(about = "Decode a transaction")] Decode(decode::Decode), + #[command(about = "Start a regtest ord and bitcoind instance")] + Env(env::Env), #[command(about = "List the first satoshis of each reward epoch")] Epochs, #[command(about = "Find a satoshi's current location")] @@ -32,12 +35,12 @@ pub(crate) enum Subcommand { List(list::List), #[command(about = "Parse a satoshi from ordinal notation")] Parse(parse::Parse), - #[command(about = "Run an explorer server populated with inscriptions")] - Preview(preview::Preview), #[command(about = "List all runes")] Runes, #[command(about = "Run the explorer server")] Server(server::Server), + #[command(about = "Display settings")] + Settings, #[command(about = "Display information about a block's subsidy")] Subsidy(subsidy::Subsidy), #[command(about = "Display Bitcoin supply information")] @@ -47,51 +50,53 @@ pub(crate) enum Subcommand { #[command(about = "Display satoshi traits")] Traits(traits::Traits), #[command(about = "Wallet commands")] - Wallet(wallet::Wallet), + Wallet(wallet::WalletCommand), } impl Subcommand { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { match self { - Self::Balances => balances::run(options), - Self::Decode(decode) => decode.run(options), + Self::Balances => balances::run(settings), + Self::Decode(decode) => decode.run(settings), + Self::Env(env) => env.run(), Self::Epochs => epochs::run(), - Self::Find(find) => find.run(options), - Self::Index(index) => index.run(options), - Self::List(list) => list.run(options), + Self::Find(find) => find.run(settings), + Self::Index(index) => index.run(settings), + Self::List(list) => list.run(settings), Self::Parse(parse) => parse.run(), - Self::Preview(preview) => preview.run(), - Self::Runes => runes::run(options), + Self::Runes => runes::run(settings), Self::Server(server) => { - let index = Arc::new(Index::open(&options)?); + let index = Arc::new(Index::open(&settings)?); let handle = axum_server::Handle::new(); LISTENERS.lock().unwrap().push(handle.clone()); - server.run(options, index, handle) + server.run(settings, index, handle) } + Self::Settings => settings::run(settings), Self::Subsidy(subsidy) => subsidy.run(), Self::Supply => supply::run(), Self::Teleburn(teleburn) => teleburn.run(), Self::Traits(traits) => traits.run(), - Self::Wallet(wallet) => wallet.run(options), + Self::Wallet(wallet) => wallet.run(settings), } } } -#[derive(Serialize, Deserialize)] -pub struct Empty {} - -pub(crate) trait Output: Send { - fn print_json(&self); +pub trait Output: Send { + fn print_json(&self, minify: bool); } impl Output for T where T: Serialize + Send, { - fn print_json(&self) { - serde_json::to_writer_pretty(io::stdout(), self).ok(); + fn print_json(&self, minify: bool) { + if minify { + serde_json::to_writer(io::stdout(), self).ok(); + } else { + serde_json::to_writer_pretty(io::stdout(), self).ok(); + } println!(); } } -pub(crate) type SubcommandResult = Result>; +pub(crate) type SubcommandResult = Result>>; diff --git a/src/subcommand/balances.rs b/src/subcommand/balances.rs index cc94155bbf..8643c926ac 100644 --- a/src/subcommand/balances.rs +++ b/src/subcommand/balances.rs @@ -5,8 +5,8 @@ pub struct Output { pub runes: BTreeMap>, } -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; ensure!( index.has_rune_index(), @@ -15,7 +15,7 @@ pub(crate) fn run(options: Options) -> SubcommandResult { index.update()?; - Ok(Box::new(Output { + Ok(Some(Box::new(Output { runes: index.get_rune_balance_map()?, - })) + }))) } diff --git a/src/subcommand/decode.rs b/src/subcommand/decode.rs index 38d2275524..f0562a126a 100644 --- a/src/subcommand/decode.rs +++ b/src/subcommand/decode.rs @@ -74,9 +74,9 @@ pub(crate) struct Decode { } impl Decode { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { let transaction = if let Some(txid) = self.txid { - options + settings .bitcoin_rpc_client(None)? .get_raw_transaction(&txid, None)? } else if let Some(file) = self.file { @@ -88,15 +88,15 @@ impl Decode { let inscriptions = ParsedEnvelope::from_transaction(&transaction); if self.compact { - Ok(Box::new(CompactOutput { + Ok(Some(Box::new(CompactOutput { inscriptions: inscriptions .clone() .into_iter() .map(|inscription| inscription.payload.try_into()) .collect::>>()?, - })) + }))) } else { - Ok(Box::new(RawOutput { inscriptions })) + Ok(Some(Box::new(RawOutput { inscriptions }))) } } } diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs new file mode 100644 index 0000000000..293a11295e --- /dev/null +++ b/src/subcommand/env.rs @@ -0,0 +1,178 @@ +use {super::*, colored::Colorize, std::net::TcpListener}; + +struct KillOnDrop(process::Child); + +impl Drop for KillOnDrop { + fn drop(&mut self) { + assert!(Command::new("kill") + .arg(self.0.id().to_string()) + .status() + .unwrap() + .success()); + self.0.wait().unwrap(); + } +} + +#[derive(Debug, Parser)] +pub(crate) struct Env { + #[arg(default_value = "env", help = "Create env in .")] + directory: PathBuf, +} + +#[derive(Serialize)] +struct Info { + bitcoin_cli_command: Vec, + bitcoind_port: u16, + ord_port: u16, + ord_wallet_command: Vec, +} + +impl Env { + pub(crate) fn run(self) -> SubcommandResult { + let bitcoind_port = TcpListener::bind("127.0.0.1:9000") + .ok() + .map(|listener| listener.local_addr().unwrap().port()); + + let ord_port = TcpListener::bind("127.0.0.1:9001") + .ok() + .map(|listener| listener.local_addr().unwrap().port()); + + let (bitcoind_port, ord_port) = ( + bitcoind_port.unwrap_or(TcpListener::bind("127.0.0.1:0")?.local_addr()?.port()), + ord_port.unwrap_or(TcpListener::bind("127.0.0.1:0")?.local_addr()?.port()), + ); + + let relative = self.directory.to_str().unwrap().to_string(); + let absolute = std::env::current_dir()?.join(&self.directory); + let absolute_str = absolute + .to_str() + .with_context(|| format!("directory `{}` is not valid unicode", absolute.display()))?; + + fs::create_dir_all(&absolute)?; + + fs::write( + absolute.join("bitcoin.conf"), + format!( + "regtest=1 +datadir={absolute_str} +listen=0 +txindex=1 +[regtest] +rpcport={bitcoind_port} +", + ), + )?; + + let _bitcoind = KillOnDrop( + Command::new("bitcoind") + .arg(format!("-conf={}", absolute.join("bitcoin.conf").display())) + .stdout(Stdio::null()) + .spawn()?, + ); + + loop { + if absolute.join("regtest/.cookie").try_exists()? { + break; + } + } + + let rpc_url = format!("http://localhost:{bitcoind_port}"); + + let server_url = format!("http://127.0.0.1:{ord_port}"); + + let config = absolute.join("ord.yaml"); + + fs::write( + config, + serde_yaml::to_string(&Settings::for_env(&absolute, &rpc_url, &server_url))?, + )?; + + let ord = std::env::current_exe()?; + + let _ord = KillOnDrop( + Command::new(&ord) + .arg("--data-dir") + .arg(&absolute) + .arg("server") + .arg("--polling-interval=100ms") + .arg("--http-port") + .arg(ord_port.to_string()) + .spawn()?, + ); + + thread::sleep(Duration::from_millis(250)); + + if !absolute.join("regtest/wallets/ord").try_exists()? { + let status = Command::new(&ord) + .arg("--data-dir") + .arg(&absolute) + .arg("wallet") + .arg("create") + .status()?; + + ensure!(status.success(), "failed to create wallet: {status}"); + + let output = Command::new(&ord) + .arg("--data-dir") + .arg(&absolute) + .arg("wallet") + .arg("receive") + .output()?; + + ensure!( + output.status.success(), + "failed to generate receive address: {status}" + ); + + let receive = + serde_json::from_slice::(&output.stdout)?; + + let address = receive.address.require_network(Network::Regtest)?; + + let status = Command::new("bitcoin-cli") + .arg(format!("-datadir={relative}")) + .arg("generatetoaddress") + .arg("200") + .arg(address.to_string()) + .status()?; + + ensure!(status.success(), "failed to create wallet: {status}"); + } + + serde_json::to_writer_pretty( + File::create(self.directory.join("env.json"))?, + &Info { + bitcoind_port, + ord_port, + bitcoin_cli_command: vec!["bitcoin-cli".into(), format!("-datadir={relative}")], + ord_wallet_command: vec![ + ord.to_str().unwrap().into(), + "--data-dir".into(), + absolute.to_str().unwrap().into(), + "wallet".into(), + ], + }, + )?; + + eprintln!( + "{} +{server_url} +{} +bitcoin-cli -datadir='{relative}' getblockchaininfo +{} +{} --data-dir '{relative}' wallet balance", + "`ord` server URL:".blue().bold(), + "Example `bitcoin-cli` command:".blue().bold(), + "Example `ord` command:".blue().bold(), + ord.display(), + ); + + loop { + if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { + break Ok(None); + } + + thread::sleep(Duration::from_millis(100)); + } + } +} diff --git a/src/subcommand/epochs.rs b/src/subcommand/epochs.rs index 39534a65ca..b5971f49f2 100644 --- a/src/subcommand/epochs.rs +++ b/src/subcommand/epochs.rs @@ -11,5 +11,5 @@ pub(crate) fn run() -> SubcommandResult { starting_sats.push(sat); } - Ok(Box::new(Output { starting_sats })) + Ok(Some(Box::new(Output { starting_sats }))) } diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index 68884679df..602710f656 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -21,8 +21,8 @@ pub struct FindRangeOutput { } impl Find { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; if !index.has_sat_index() { bail!("find requires index created with `--index-sats` flag"); @@ -32,11 +32,11 @@ impl Find { match self.end { Some(end) => match index.find_range(self.sat, end)? { - Some(result) => Ok(Box::new(result)), + Some(result) => Ok(Some(Box::new(result))), None => Err(anyhow!("range has not been mined as of index height")), }, None => match index.find(self.sat)? { - Some(satpoint) => Ok(Box::new(Output { satpoint })), + Some(satpoint) => Ok(Some(Box::new(Output { satpoint }))), None => Err(anyhow!("sat has not been mined as of index height")), }, } diff --git a/src/subcommand/index.rs b/src/subcommand/index.rs index a76b9dca42..cf24863ed0 100644 --- a/src/subcommand/index.rs +++ b/src/subcommand/index.rs @@ -15,11 +15,11 @@ pub(crate) enum IndexSubcommand { } impl IndexSubcommand { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { match self { - Self::Export(export) => export.run(options), - Self::Info(info) => info.run(options), - Self::Update => update::run(options), + Self::Export(export) => export.run(settings), + Self::Info(info) => info.run(settings), + Self::Update => update::run(settings), } } } diff --git a/src/subcommand/index/export.rs b/src/subcommand/index/export.rs index cddebe59bb..5e93eaec18 100644 --- a/src/subcommand/index/export.rs +++ b/src/subcommand/index/export.rs @@ -9,12 +9,12 @@ pub(crate) struct Export { } impl Export { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; index.export(&self.tsv, self.include_addresses)?; - Ok(Box::new(Empty {})) + Ok(None) } } diff --git a/src/subcommand/index/info.rs b/src/subcommand/index/info.rs index 8b9a2deb07..11143e8e36 100644 --- a/src/subcommand/index/info.rs +++ b/src/subcommand/index/info.rs @@ -15,8 +15,8 @@ pub struct TransactionsOutput { } impl Info { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; @@ -34,9 +34,9 @@ impl Info { elapsed: (end.starting_timestamp - start.starting_timestamp) as f64 / 1000.0 / 60.0, }); } - Ok(Box::new(output)) + Ok(Some(Box::new(output))) } else { - Ok(Box::new(info)) + Ok(Some(Box::new(info))) } } } diff --git a/src/subcommand/index/update.rs b/src/subcommand/index/update.rs index cd3d7d45e8..ffdbce09a2 100644 --- a/src/subcommand/index/update.rs +++ b/src/subcommand/index/update.rs @@ -1,9 +1,9 @@ use super::*; -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; - Ok(Box::new(Empty {})) + Ok(None) } diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index c0477407c0..290dcf4db1 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -8,18 +8,23 @@ pub(crate) struct List { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Output { - pub output: OutPoint, - pub start: u64, + pub ranges: Option>, + pub spent: bool, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Range { pub end: u64, - pub size: u64, + pub name: String, pub offset: u64, pub rarity: Rarity, - pub name: String, + pub size: u64, + pub start: u64, } impl List { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; if !index.has_sat_index() { bail!("list requires index created with `--index-sats` flag"); @@ -27,52 +32,35 @@ impl List { index.update()?; - match index.list(self.outpoint)? { - Some(crate::index::List::Unspent(ranges)) => { - let mut outputs = Vec::new(); - for Output { - output, - start, - end, - size, - offset, - rarity, - name, - } in list(self.outpoint, ranges) - { - outputs.push(Output { - output, - start, - end, - size, - offset, - rarity, - name, - }); - } - - Ok(Box::new(outputs)) - } - Some(crate::index::List::Spent) => Err(anyhow!("output spent.")), - None => Err(anyhow!("output not found")), + ensure! { + index.is_output_in_active_chain(self.outpoint)?, + "output not found" } + + let ranges = index.list(self.outpoint)?; + + let spent = index.is_output_spent(self.outpoint)?; + + Ok(Some(Box::new(Output { + spent, + ranges: ranges.map(output_ranges), + }))) } } -fn list(outpoint: OutPoint, ranges: Vec<(u64, u64)>) -> Vec { +fn output_ranges(ranges: Vec<(u64, u64)>) -> Vec { let mut offset = 0; ranges .into_iter() .map(|(start, end)| { let size = end - start; - let output = Output { - output: outpoint, - start, + let output = Range { end, - size, - offset, name: Sat(start).name(), + offset, rarity: Sat(start).rarity(), + size, + start, }; offset += size; @@ -86,69 +74,39 @@ fn list(outpoint: OutPoint, ranges: Vec<(u64, u64)>) -> Vec { mod tests { use super::*; - fn output( - output: OutPoint, - start: u64, - end: u64, - size: u64, - offset: u64, - rarity: Rarity, - name: String, - ) -> super::Output { - super::Output { - output, - start, - end, - size, - offset, - name, - rarity, - } - } - #[test] fn list_ranges() { - let outpoint = - OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5") - .unwrap(); - let ranges = vec![ - (50 * COIN_VALUE, 55 * COIN_VALUE), - (10, 100), - (1050000000000000, 1150000000000000), - ]; assert_eq!( - list(outpoint, ranges), + output_ranges(vec![ + (50 * COIN_VALUE, 55 * COIN_VALUE), + (10, 100), + (1050000000000000, 1150000000000000), + ]), vec![ - output( - OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5") - .unwrap(), - 50 * COIN_VALUE, - 55 * COIN_VALUE, - 5 * COIN_VALUE, - 0, - Rarity::Uncommon, - "nvtcsezkbth".to_string() - ), - output( - OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5") - .unwrap(), - 10, - 100, - 90, - 5 * COIN_VALUE, - Rarity::Common, - "nvtdijuwxlf".to_string() - ), - output( - OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5") - .unwrap(), - 1050000000000000, - 1150000000000000, - 100000000000000, - 5 * COIN_VALUE + 90, - Rarity::Epic, - "gkjbdrhkfqf".to_string() - ) + Range { + end: 55 * COIN_VALUE, + name: "nvtcsezkbth".to_string(), + offset: 0, + rarity: Rarity::Uncommon, + size: 5 * COIN_VALUE, + start: 50 * COIN_VALUE, + }, + Range { + end: 100, + name: "nvtdijuwxlf".to_string(), + offset: 5 * COIN_VALUE, + rarity: Rarity::Common, + size: 90, + start: 10, + }, + Range { + end: 1150000000000000, + name: "gkjbdrhkfqf".to_string(), + offset: 5 * COIN_VALUE + 90, + rarity: Rarity::Epic, + size: 100000000000000, + start: 1050000000000000, + } ] ) } diff --git a/src/subcommand/parse.rs b/src/subcommand/parse.rs index 2430abd185..b3c1067a05 100644 --- a/src/subcommand/parse.rs +++ b/src/subcommand/parse.rs @@ -13,8 +13,8 @@ pub struct Output { impl Parse { pub(crate) fn run(self) -> SubcommandResult { - Ok(Box::new(Output { + Ok(Some(Box::new(Output { object: self.object, - })) + }))) } } diff --git a/src/subcommand/preview.rs b/src/subcommand/preview.rs deleted file mode 100644 index 845c0c90d6..0000000000 --- a/src/subcommand/preview.rs +++ /dev/null @@ -1,205 +0,0 @@ -use {super::*, fee_rate::FeeRate, std::sync::atomic}; - -#[derive(Debug, Parser)] -pub(crate) struct Preview { - #[command(flatten)] - server: super::server::Server, - #[arg( - num_args = 0.., - long, - help = "Inscribe inscriptions defined in ." - )] - batches: Option>, - #[arg(long, help = "Automatically mine a block every seconds.")] - blocktime: Option, - #[arg(num_args = 0.., long, help = "Inscribe contents of .")] - files: Option>, -} - -#[derive(Debug, Parser)] -pub(crate) struct Batch { - batch_files: Vec, -} - -#[derive(Debug, Parser)] -pub(crate) struct File { - files: Vec, -} - -struct KillOnDrop(process::Child); - -impl Drop for KillOnDrop { - fn drop(&mut self) { - self.0.kill().unwrap() - } -} - -impl Preview { - pub(crate) fn run(self) -> SubcommandResult { - let tmpdir = TempDir::new()?; - - let rpc_port = TcpListener::bind("127.0.0.1:0")?.local_addr()?.port(); - - let bitcoin_data_dir = tmpdir.path().join("bitcoin"); - - fs::create_dir(&bitcoin_data_dir)?; - - eprintln!("Spawning bitcoind…"); - - let _bitcoind = KillOnDrop( - Command::new("bitcoind") - .arg({ - let mut arg = OsString::from("-datadir="); - arg.push(&bitcoin_data_dir); - arg - }) - .arg("-listen=0") - .arg("-printtoconsole=0") - .arg("-regtest") - .arg("-txindex") - .arg(format!("-rpcport={rpc_port}")) - .spawn() - .context("failed to spawn `bitcoind`")?, - ); - - let options = Options { - chain_argument: Chain::Regtest, - bitcoin_data_dir: Some(bitcoin_data_dir), - data_dir: tmpdir.path().into(), - rpc_url: Some(format!("127.0.0.1:{rpc_port}")), - index_sats: true, - ..Options::default() - }; - - for attempt in 0.. { - if options.bitcoin_rpc_client(None).is_ok() { - break; - } - - if attempt == 100 { - panic!("Bitcoin Core RPC did not respond"); - } - - thread::sleep(Duration::from_millis(50)); - } - - super::wallet::create::Create { - passphrase: "".into(), - } - .run("ord".into(), options.clone())?; - - let rpc_client = options.bitcoin_rpc_client(None)?; - - let address = rpc_client - .get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m))? - .require_network(Network::Regtest)?; - - eprintln!("Mining blocks…"); - - rpc_client.generate_to_address(101, &address)?; - - if let Some(files) = self.files { - for file in files { - Arguments { - options: options.clone(), - subcommand: Subcommand::Wallet(super::wallet::Wallet { - name: "ord".into(), - subcommand: super::wallet::Subcommand::Inscribe(super::wallet::inscribe::Inscribe { - batch: None, - cbor_metadata: None, - commit_fee_rate: None, - compress: false, - destination: None, - dry_run: false, - fee_rate: FeeRate::try_from(1.0).unwrap(), - file: Some(file), - json_metadata: None, - metaprotocol: None, - no_backup: true, - no_limit: false, - parent: None, - postage: Some(TARGET_POSTAGE), - reinscribe: false, - satpoint: None, - sat: None, - }), - }), - } - .run()?; - - rpc_client.generate_to_address(1, &address)?; - } - } - - if let Some(batches) = self.batches { - for batch in batches { - Arguments { - options: options.clone(), - subcommand: Subcommand::Wallet(super::wallet::Wallet { - name: "ord".into(), - subcommand: super::wallet::Subcommand::Inscribe(super::wallet::inscribe::Inscribe { - batch: Some(batch), - cbor_metadata: None, - commit_fee_rate: None, - compress: false, - destination: None, - dry_run: false, - fee_rate: FeeRate::try_from(1.0).unwrap(), - file: None, - json_metadata: None, - metaprotocol: None, - no_backup: true, - no_limit: false, - parent: None, - postage: Some(TARGET_POSTAGE), - reinscribe: false, - satpoint: None, - sat: None, - }), - }), - } - .run()?; - - rpc_client.generate_to_address(1, &address)?; - } - } - - if let Some(blocktime) = self.blocktime { - eprintln!( - "Mining blocks every {}...", - "second".tally(blocktime.try_into().unwrap()) - ); - - let running = Arc::new(AtomicBool::new(true)); - - let handle = { - let running = running.clone(); - - std::thread::spawn(move || { - while running.load(atomic::Ordering::SeqCst) { - rpc_client.generate_to_address(1, &address).unwrap(); - thread::sleep(Duration::from_secs(blocktime)); - } - }) - }; - - Arguments { - options, - subcommand: Subcommand::Server(self.server), - } - .run()?; - - running.store(false, atomic::Ordering::SeqCst); - - handle.join().unwrap(); - } else { - Arguments { - options, - subcommand: Subcommand::Server(self.server), - } - .run()?; - } - - Ok(Box::new(Empty {})) - } -} diff --git a/src/subcommand/runes.rs b/src/subcommand/runes.rs index ed28f68bd8..dd99e79eab 100644 --- a/src/subcommand/runes.rs +++ b/src/subcommand/runes.rs @@ -8,14 +8,12 @@ pub struct Output { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct RuneInfo { pub burned: u128, - pub deadline: Option, pub divisibility: u8, - pub end: Option, pub etching: Txid, pub height: u32, pub id: RuneId, pub index: u16, - pub limit: Option, + pub mint: Option, pub mints: u64, pub number: u64, pub rune: Rune, @@ -25,8 +23,8 @@ pub struct RuneInfo { pub timestamp: DateTime, } -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; ensure!( index.has_rune_index(), @@ -35,7 +33,7 @@ pub(crate) fn run(options: Options) -> SubcommandResult { index.update()?; - Ok(Box::new(Output { + Ok(Some(Box::new(Output { runes: index .runes()? .into_iter() @@ -44,11 +42,9 @@ pub(crate) fn run(options: Options) -> SubcommandResult { id, RuneEntry { burned, - deadline, divisibility, - end, etching, - limit, + mint, mints, number, rune, @@ -62,14 +58,12 @@ pub(crate) fn run(options: Options) -> SubcommandResult { rune, RuneInfo { burned, - deadline, divisibility, - end, etching, height: id.height, id, index: id.index, - limit, + mint, mints, number, rune, @@ -82,5 +76,5 @@ pub(crate) fn run(options: Options) -> SubcommandResult { }, ) .collect::>(), - })) + }))) } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 888275bbcd..ca53ba3fec 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2,7 +2,6 @@ use { self::{ accept_encoding::AcceptEncoding, accept_json::AcceptJson, - deserialize_from_str::DeserializeFromStr, error::{OptionExt, ServerError, ServerResult}, }, super::*, @@ -11,23 +10,20 @@ use { ordzaar::ordinals::get_ordinals, server_config::ServerConfig, templates::{ - BlockHtml, BlockJson, BlocksHtml, ChildrenHtml, ChildrenJson, ClockSvg, CollectionsHtml, - HomeHtml, InputHtml, InscriptionHtml, InscriptionJson, InscriptionsBlockHtml, - InscriptionsHtml, InscriptionsJson, OutputHtml, OutputJson, PageContent, PageHtml, + BlockHtml, BlocksHtml, ChildrenHtml, ClockSvg, CollectionsHtml, HomeHtml, InputHtml, + InscriptionHtml, InscriptionsBlockHtml, InscriptionsHtml, OutputHtml, PageContent, PageHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, - RangeHtml, RareTxt, RuneHtml, RuneJson, RunesHtml, RunesJson, SatHtml, SatInscriptionJson, - SatInscriptionsJson, SatJson, TransactionHtml, + RangeHtml, RareTxt, RuneBalancesHtml, RuneHtml, RunesHtml, SatHtml, TransactionHtml, }, }, axum::{ body, extract::{Extension, Json, Path, Query}, - headers::UserAgent, http::{header, HeaderMap, HeaderValue, StatusCode, Uri}, response::{IntoResponse, Redirect, Response}, routing::{get, post}, - Router, TypedHeader, + Router, }, axum_server::Handle, brotli::Decompressor, @@ -44,56 +40,14 @@ use { compression::CompressionLayer, cors::{Any, CorsLayer}, set_header::SetResponseHeaderLayer, + validate_request::ValidateRequestHeaderLayer, }, }; mod accept_encoding; mod accept_json; mod error; - -#[derive(Copy, Clone)] -pub(crate) enum InscriptionQuery { - Id(InscriptionId), - Number(i32), -} - -impl FromStr for InscriptionQuery { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(if s.contains('i') { - Self::Id(s.parse()?) - } else { - Self::Number(s.parse()?) - }) - } -} - -impl Display for InscriptionQuery { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Id(id) => write!(f, "{id}"), - Self::Number(number) => write!(f, "{number}"), - } - } -} - -enum BlockQuery { - Height(u32), - Hash(BlockHash), -} - -impl FromStr for BlockQuery { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(if s.len() == 64 { - BlockQuery::Hash(s.parse()?) - } else { - BlockQuery::Height(s.parse()?) - }) - } -} +pub(crate) mod query; enum SpawnConfig { Https(AxumAcceptor), @@ -127,83 +81,103 @@ impl Display for StaticHtml { } } -#[derive(Debug, Parser)] -pub(crate) struct Server { +#[derive(Debug, Parser, Clone)] +pub struct Server { #[arg( long, help = "Listen on
          for incoming requests. [default: 0.0.0.0]" )] - address: Option, + pub(crate) address: Option, #[arg( long, help = "Request ACME TLS certificate for . This ord instance must be reachable at :443 to respond to Let's Encrypt ACME challenges." )] - acme_domain: Vec, + pub(crate) acme_domain: Vec, #[arg( long, help = "Use in Content-Security-Policy header. Set this to the public-facing URL of your ord instance." )] - csp_origin: Option, + pub(crate) csp_origin: Option, + #[arg( + long, + help = "Decompress encoded content. Currently only supports brotli. Be careful using this on production instances. A decompressed inscription may be arbitrarily large, making decompression a DoS vector." + )] + pub(crate) decompress: bool, + #[arg(long, help = "Disable JSON API.")] + pub(crate) disable_json_api: bool, #[arg( long, help = "Listen on for incoming HTTP requests. [default: 80]" )] - http_port: Option, + pub(crate) http_port: Option, #[arg( long, group = "port", help = "Listen on for incoming HTTPS requests. [default: 443]" )] - https_port: Option, + pub(crate) https_port: Option, #[arg(long, help = "Store ACME TLS certificates in .")] - acme_cache: Option, + pub(crate) acme_cache: Option, #[arg(long, help = "Provide ACME contact .")] - acme_contact: Vec, + pub(crate) acme_contact: Vec, #[arg(long, help = "Serve HTTP traffic on .")] - http: bool, + pub(crate) http: bool, #[arg(long, help = "Serve HTTPS traffic on .")] - https: bool, + pub(crate) https: bool, #[arg(long, help = "Redirect HTTP traffic to HTTPS.")] - redirect_http_to_https: bool, - #[arg(long, short = 'j', help = "Enable JSON API.")] - pub(crate) enable_json_api: bool, + pub(crate) redirect_http_to_https: bool, + #[arg(long, alias = "nosync", help = "Do not update the index.")] + pub(crate) no_sync: bool, #[arg( long, - help = "Decompress encoded content. Currently only supports brotli. Be careful using this on production instances. A decompressed inscription may be arbitrarily large, making decompression a DoS vector." + help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." )] - pub(crate) decompress: bool, - #[arg(long, alias = "nosync", help = "Do not update the index.")] - no_sync: bool, + pub(crate) content_proxy: Option, + #[arg( + long, + default_value = "5s", + help = "Poll Bitcoin Core every ." + )] + pub(crate) polling_interval: humantime::Duration, } impl Server { - pub(crate) fn run(self, options: Options, index: Arc, handle: Handle) -> SubcommandResult { + pub fn run(self, settings: Settings, index: Arc, handle: Handle) -> SubcommandResult { Runtime::new()?.block_on(async { let index_clone = index.clone(); + let integration_test = settings.integration_test(); let index_thread = thread::spawn(move || loop { if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { break; } + if !self.no_sync { if let Err(error) = index_clone.update() { log::warn!("Updating index: {error}"); } } - thread::sleep(Duration::from_millis(5000)); + + thread::sleep(if integration_test { + Duration::from_millis(100) + } else { + self.polling_interval.into() + }); }); + INDEXER.lock().unwrap().replace(index_thread); - let config = Arc::new(options.load_config()?); + let settings = Arc::new(settings); let acme_domains = self.acme_domains()?; let server_config = Arc::new(ServerConfig { - chain: options.chain(), + chain: settings.chain(), + content_proxy: self.content_proxy.clone(), csp_origin: self.csp_origin.clone(), + decompress: self.decompress, domain: acme_domains.first().cloned(), index_sats: index.has_sat_index(), - is_json_api_enabled: self.enable_json_api, - decompress: self.decompress, + json_api_enabled: !self.disable_json_api, }); let router = Router::new() @@ -251,6 +225,11 @@ impl Server { ) .route("/r/blockheight", get(Self::block_height)) .route("/r/blocktime", get(Self::block_time)) + .route("/r/blockinfo/:query", get(Self::block_info)) + .route( + "/r/inscription/:inscription_id", + get(Self::inscription_recursive), + ) .route("/r/children/:inscription_id", get(Self::children_recursive)) .route( "/r/children/:inscription_id/:page", @@ -270,6 +249,7 @@ impl Server { .route("/rare.txt", get(Self::rare_txt)) .route("/rune/:rune", get(Self::rune)) .route("/runes", get(Self::runes)) + .route("/runes/balances", get(Self::runes_balances)) .route("/sat/:sat", get(Self::sat)) .route("/search", get(Self::search_by_query)) .route("/search/*query", get(Self::search_by_path)) @@ -282,7 +262,7 @@ impl Server { // ---- Ordzaar routes ---- .layer(Extension(index)) .layer(Extension(server_config.clone())) - .layer(Extension(config)) + .layer(Extension(settings.clone())) .layer(SetResponseHeaderLayer::if_not_present( header::CONTENT_SECURITY_POLICY, HeaderValue::from_static("default-src 'self'"), @@ -299,19 +279,26 @@ impl Server { .layer(CompressionLayer::new()) .with_state(server_config); + let router = if let Some((username, password)) = settings.credentials() { + router.layer(ValidateRequestHeaderLayer::basic(username, password)) + } else { + router + }; + match (self.http_port(), self.https_port()) { (Some(http_port), None) => { self - .spawn(router, handle, http_port, SpawnConfig::Http)? + .spawn(&settings, router, handle, http_port, SpawnConfig::Http)? .await?? } (None, Some(https_port)) => { self .spawn( + &settings, router, handle, https_port, - SpawnConfig::Https(self.acceptor(&options)?), + SpawnConfig::Https(self.acceptor(&settings)?), )? .await?? } @@ -327,12 +314,19 @@ impl Server { }; let (http_result, https_result) = tokio::join!( - self.spawn(router.clone(), handle.clone(), http_port, http_spawn_config)?, self.spawn( + &settings, + router.clone(), + handle.clone(), + http_port, + http_spawn_config + )?, + self.spawn( + &settings, router, handle, https_port, - SpawnConfig::Https(self.acceptor(&options)?), + SpawnConfig::Https(self.acceptor(&settings)?), )? ); http_result.and(https_result)??; @@ -340,7 +334,7 @@ impl Server { (None, None) => unreachable!(), } - Ok(Box::new(Empty {}) as Box) + Ok(None) }) } @@ -392,6 +386,7 @@ impl Server { fn spawn( &self, + settings: &Settings, router: Router, handle: Handle, port: u16, @@ -400,7 +395,7 @@ impl Server { let address = match &self.address { Some(address) => address.as_str(), None => { - if cfg!(test) || integration_test() { + if cfg!(test) || settings.integration_test() { "127.0.0.1" } else { "0.0.0.0" @@ -413,7 +408,7 @@ impl Server { .next() .ok_or_else(|| anyhow!("failed to get socket addrs"))?; - if !integration_test() { + if !settings.integration_test() && !cfg!(test) { eprintln!( "Listening on {}://{addr}", match config { @@ -453,10 +448,11 @@ impl Server { })) } - fn acme_cache(acme_cache: Option<&PathBuf>, options: &Options) -> PathBuf { - acme_cache - .unwrap_or(&options.data_dir().join("acme-cache")) - .to_path_buf() + fn acme_cache(acme_cache: Option<&PathBuf>, settings: &Settings) -> PathBuf { + match acme_cache { + Some(acme_cache) => acme_cache.clone(), + None => settings.data_dir().join("acme-cache"), + } } fn acme_domains(&self) -> Result> { @@ -485,12 +481,12 @@ impl Server { } } - fn acceptor(&self, options: &Options) -> Result { + fn acceptor(&self, settings: &Settings) -> Result { let config = AcmeConfig::new(self.acme_domains()?) .contact(&self.acme_contact) .cache_option(Some(DirCache::new(Self::acme_cache( self.acme_cache.as_ref(), - options, + settings, )))) .directory(if cfg!(test) { LETS_ENCRYPT_STAGING_DIRECTORY @@ -557,7 +553,7 @@ impl Server { }); let blocktime = index.block_time(sat.height())?; Ok(if accept_json { - Json(SatJson { + Json(api::Sat { number: sat.0, decimal: sat.decimal().to_string(), degree: sat.degree().to_string(), @@ -598,22 +594,28 @@ impl Server { AcceptJson(accept_json): AcceptJson, ) -> ServerResult { task::block_in_place(|| { - let list = index.list(outpoint)?; + let sat_ranges = index.list(outpoint)?; + + let indexed; let output = if outpoint == OutPoint::null() || outpoint == unbound_outpoint() { let mut value = 0; - if let Some(List::Unspent(ranges)) = &list { + if let Some(ranges) = &sat_ranges { for (start, end) in ranges { value += end - start; } } + indexed = true; + TxOut { value, script_pubkey: ScriptBuf::new(), } } else { + indexed = index.contains_output(&outpoint)?; + index .get_transaction(outpoint.txid)? .ok_or_not_found(|| format!("output {outpoint}"))? @@ -627,27 +629,29 @@ impl Server { let runes = index.get_rune_balances_for_outpoint(outpoint)?; + let spent = index.is_output_spent(outpoint)?; + Ok(if accept_json { - Json(OutputJson::new( - outpoint, - list, + Json(api::Output::new( server_config.chain, - output, inscriptions, - runes - .into_iter() - .map(|(spaced_rune, pile)| (spaced_rune.rune, pile.amount)) - .collect(), + outpoint, + output, + indexed, + runes, + sat_ranges, + spent, )) .into_response() } else { OutputHtml { - outpoint, - inscriptions, - list, chain: server_config.chain, + inscriptions, + outpoint, output, runes, + sat_ranges, + spent, } .page(server_config) .into_response() @@ -678,7 +682,7 @@ impl Server { async fn rune( Extension(server_config): Extension>, Extension(index): Extension>, - Path(DeserializeFromStr(spaced_rune)): Path>, + Path(DeserializeFromStr(rune_query)): Path>, AcceptJson(accept_json): AcceptJson, ) -> ServerResult { task::block_in_place(|| { @@ -688,12 +692,19 @@ impl Server { )); } + let rune = match rune_query { + query::Rune::SpacedRune(spaced_rune) => spaced_rune.rune, + query::Rune::RuneId(rune_id) => index + .get_rune_by_id(rune_id)? + .ok_or_not_found(|| format!("rune {rune_id}"))?, + }; + let (id, entry, parent) = index - .rune(spaced_rune.rune)? - .ok_or_not_found(|| format!("rune {spaced_rune}"))?; + .rune(rune)? + .ok_or_not_found(|| format!("rune {rune}"))?; Ok(if accept_json { - Json(RuneJson { entry, id, parent }).into_response() + Json(api::Rune { entry, id, parent }).into_response() } else { RuneHtml { entry, id, parent } .page(server_config) @@ -709,7 +720,7 @@ impl Server { ) -> ServerResult { task::block_in_place(|| { Ok(if accept_json { - Json(RunesJson { + Json(api::Runes { entries: index.runes()?, }) .into_response() @@ -723,6 +734,23 @@ impl Server { }) } + async fn runes_balances( + Extension(server_config): Extension>, + Extension(index): Extension>, + AcceptJson(accept_json): AcceptJson, + ) -> ServerResult { + task::block_in_place(|| { + let balances = index.get_rune_balance_map()?; + Ok(if accept_json { + Json(balances).into_response() + } else { + RuneBalancesHtml { balances } + .page(server_config) + .into_response() + }) + }) + } + async fn home( Extension(server_config): Extension>, Extension(index): Extension>, @@ -740,7 +768,8 @@ impl Server { async fn blocks( Extension(server_config): Extension>, Extension(index): Extension>, - ) -> ServerResult> { + AcceptJson(accept_json): AcceptJson, + ) -> ServerResult { task::block_in_place(|| { let blocks = index.blocks(100)?; let mut featured_blocks = BTreeMap::new(); @@ -751,7 +780,13 @@ impl Server { featured_blocks.insert(*hash, inscriptions); } - Ok(BlocksHtml::new(blocks, featured_blocks).page(server_config)) + Ok(if accept_json { + Json(api::Blocks::new(blocks, featured_blocks)).into_response() + } else { + BlocksHtml::new(blocks, featured_blocks) + .page(server_config) + .into_response() + }) }) } @@ -762,19 +797,19 @@ impl Server { async fn block( Extension(server_config): Extension>, Extension(index): Extension>, - Path(DeserializeFromStr(query)): Path>, + Path(DeserializeFromStr(query)): Path>, AcceptJson(accept_json): AcceptJson, ) -> ServerResult { task::block_in_place(|| { let (block, height) = match query { - BlockQuery::Height(height) => { + query::Block::Height(height) => { let block = index .get_block_by_height(height)? .ok_or_not_found(|| format!("block {height}"))?; (block, height) } - BlockQuery::Hash(hash) => { + query::Block::Hash(hash) => { let info = index .block_header_info(hash)? .ok_or_not_found(|| format!("block {hash}"))?; @@ -789,7 +824,7 @@ impl Server { Ok(if accept_json { let inscriptions = index.get_inscriptions_in_block(height)?; - Json(BlockJson::new( + Json(api::Block::new( block, Height(height), Self::index_height(&index)?, @@ -816,7 +851,8 @@ impl Server { Extension(server_config): Extension>, Extension(index): Extension>, Path(txid): Path, - ) -> ServerResult> { + AcceptJson(accept_json): AcceptJson, + ) -> ServerResult { task::block_in_place(|| { let transaction = index .get_transaction(txid)? @@ -824,19 +860,26 @@ impl Server { let inscription_count = index.inscription_count(txid)?; - let blockhash = index.get_transaction_blockhash(txid)?; - - Ok( - TransactionHtml { - blockhash, + Ok(if accept_json { + Json(api::Transaction { + chain: server_config.chain, + etching: index.get_etching(txid)?, + inscription_count, transaction, txid, - inscription_count, + }) + .into_response() + } else { + TransactionHtml { chain: server_config.chain, etching: index.get_etching(txid)?, + inscription_count, + transaction, + txid, } - .page(server_config), - ) + .page(server_config) + .into_response() + }) }) } @@ -855,6 +898,66 @@ impl Server { }) } + async fn inscription_recursive( + Extension(index): Extension>, + Path(inscription_id): Path, + ) -> ServerResult { + task::block_in_place(|| { + let inscription = index + .get_inscription_by_id(inscription_id)? + .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + + let entry = index + .get_inscription_entry(inscription_id) + .unwrap() + .unwrap(); + + let satpoint = index + .get_inscription_satpoint_by_id(inscription_id) + .ok() + .flatten() + .unwrap(); + + let output = if satpoint.outpoint == unbound_outpoint() { + None + } else { + Some( + index + .get_transaction(satpoint.outpoint.txid)? + .ok_or_not_found(|| format!("inscription {inscription_id} current transaction"))? + .output + .into_iter() + .nth(satpoint.outpoint.vout.try_into().unwrap()) + .ok_or_not_found(|| { + format!("inscription {inscription_id} current transaction output") + })?, + ) + }; + + Ok( + Json(api::InscriptionRecursive { + charms: Charm::ALL + .iter() + .filter(|charm| charm.is_set(entry.charms)) + .map(|charm| charm.title().into()) + .collect(), + content_type: inscription.content_type().map(|s| s.to_string()), + content_length: inscription.content_length(), + fee: entry.fee, + height: entry.height, + id: inscription_id, + number: entry.inscription_number, + output: satpoint.outpoint, + value: output.as_ref().map(|o| o.value), + sat: entry.sat, + satpoint, + timestamp: timestamp(entry.timestamp).timestamp(), + }) + .into_response(), + ) + }) + } + async fn status( Extension(server_config): Extension>, Extension(index): Extension>, @@ -894,7 +997,7 @@ impl Server { static ref INSCRIPTION_ID: Regex = Regex::new(r"^[[:xdigit:]]{64}i\d+$").unwrap(); static ref OUTPOINT: Regex = Regex::new(r"^[[:xdigit:]]{64}:\d+$").unwrap(); static ref RUNE: Regex = Regex::new(r"^[A-Z•.]+$").unwrap(); - static ref RUNE_ID: Regex = Regex::new(r"^[0-9]+/[0-9]+$").unwrap(); + static ref RUNE_ID: Regex = Regex::new(r"^[0-9]+:[0-9]+$").unwrap(); } let query = query.trim(); @@ -925,32 +1028,12 @@ impl Server { }) } - async fn favicon(user_agent: Option>) -> ServerResult { - if user_agent - .map(|user_agent| { - user_agent.as_str().contains("Safari/") - && !user_agent.as_str().contains("Chrome/") - && !user_agent.as_str().contains("Chromium/") - }) - .unwrap_or_default() - { - Ok( - Self::static_asset(Path("/favicon.png".to_string())) - .await - .into_response(), - ) - } else { - Ok( - ( - [( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static("default-src 'unsafe-inline'"), - )], - Self::static_asset(Path("/favicon.svg".to_string())).await?, - ) - .into_response(), - ) - } + async fn favicon() -> ServerResult { + Ok( + Self::static_asset(Path("/favicon.png".to_string())) + .await + .into_response(), + ) } async fn feed( @@ -1079,6 +1162,65 @@ impl Server { }) } + async fn block_info( + Extension(index): Extension>, + Path(DeserializeFromStr(query)): Path>, + ) -> ServerResult> { + task::block_in_place(|| { + let hash = match query { + query::Block::Hash(hash) => hash, + query::Block::Height(height) => index + .block_hash(Some(height))? + .ok_or_not_found(|| format!("block {height}"))?, + }; + + let header = index + .block_header(hash)? + .ok_or_not_found(|| format!("block {hash}"))?; + + let info = index + .block_header_info(hash)? + .ok_or_not_found(|| format!("block {hash}"))?; + + let stats = index + .block_stats(info.height.try_into().unwrap())? + .ok_or_not_found(|| format!("block {hash}"))?; + + Ok(Json(api::BlockInfo { + average_fee: stats.avg_fee.to_sat(), + average_fee_rate: stats.avg_fee_rate.to_sat(), + bits: header.bits.to_consensus(), + chainwork: info.chainwork.try_into().unwrap(), + confirmations: info.confirmations, + difficulty: info.difficulty, + hash, + height: info.height.try_into().unwrap(), + max_fee: stats.max_fee.to_sat(), + max_fee_rate: stats.max_fee_rate.to_sat(), + max_tx_size: stats.max_tx_size, + median_fee: stats.median_fee.to_sat(), + median_time: info + .median_time + .map(|median_time| median_time.try_into().unwrap()), + merkle_root: info.merkle_root, + min_fee: stats.min_fee.to_sat(), + min_fee_rate: stats.min_fee_rate.to_sat(), + next_block: info.next_block_hash, + nonce: info.nonce, + previous_block: info.previous_block_hash, + subsidy: stats.subsidy.to_sat(), + target: target_as_block_hash(header.target()), + timestamp: info.time.try_into().unwrap(), + total_fee: stats.total_fee.to_sat(), + total_size: stats.total_size, + total_weight: stats.total_weight, + transaction_count: info.n_tx.try_into().unwrap(), + #[allow(clippy::cast_sign_loss)] + version: info.version.to_consensus() as u32, + })) + }) + } + async fn block_time(Extension(index): Extension>) -> ServerResult { task::block_in_place(|| { Ok( @@ -1126,21 +1268,54 @@ impl Server { Redirect::to("https://docs.ordinals.com/bounty/") } + fn proxy_content(proxy: &Url, inscription_id: InscriptionId) -> ServerResult { + let response = reqwest::blocking::Client::new() + .get(format!("{}content/{}", proxy, inscription_id)) + .send() + .map_err(|err| anyhow!(err))?; + + let mut headers = response.headers().clone(); + + headers.insert( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_str(&format!( + "default-src 'self' {proxy} 'unsafe-eval' 'unsafe-inline' data: blob:" + )) + .map_err(|err| ServerError::Internal(Error::from(err)))?, + ); + + Ok( + ( + response.status(), + headers, + response.bytes().map_err(|err| anyhow!(err))?, + ) + .into_response(), + ) + } + async fn content( Extension(index): Extension>, - Extension(config): Extension>, + Extension(settings): Extension>, Extension(server_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { task::block_in_place(|| { - if config.is_hidden(inscription_id) { + if settings.is_hidden(inscription_id) { return Ok(PreviewUnknownHtml.into_response()); } - let mut inscription = index - .get_inscription_by_id(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + let Some(mut inscription) = index.get_inscription_by_id(inscription_id)? else { + return if let Some(proxy) = server_config.content_proxy.as_ref() { + Self::proxy_content(proxy, inscription_id) + } else { + Err(ServerError::NotFound(format!( + "{} not found", + inscription_id + ))) + }; + }; if let Some(delegate) = inscription.delegate() { inscription = index @@ -1185,7 +1360,7 @@ impl Server { headers.insert( header::CACHE_CONTROL, - HeaderValue::from_static("public, max-age=31536000, immutable"), + HeaderValue::from_static("public, max-age=1209600, immutable"), ); headers.insert( @@ -1228,13 +1403,13 @@ impl Server { async fn preview( Extension(index): Extension>, - Extension(config): Extension>, + Extension(settings): Extension>, Extension(server_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { task::block_in_place(|| { - if config.is_hidden(inscription_id) { + if settings.is_hidden(inscription_id) { return Ok(PreviewUnknownHtml.into_response()); } @@ -1278,13 +1453,16 @@ impl Server { .ok_or_not_found(|| format!("inscription {inscription_id} content"))? .into_response(), ), - Media::Image => Ok( + Media::Image(image_rendering) => Ok( ( [( header::CONTENT_SECURITY_POLICY, "default-src 'self' 'unsafe-inline'", )], - PreviewImageHtml { inscription_id }, + PreviewImageHtml { + image_rendering, + inscription_id, + }, ) .into_response(), ), @@ -1328,7 +1506,7 @@ impl Server { async fn inscription( Extension(server_config): Extension>, Extension(index): Extension>, - Path(DeserializeFromStr(query)): Path>, + Path(DeserializeFromStr(query)): Path>, AcceptJson(accept_json): AcceptJson, ) -> ServerResult { task::block_in_place(|| { @@ -1336,19 +1514,7 @@ impl Server { .ok_or_not_found(|| format!("inscription {query}"))?; Ok(if accept_json { - Json(InscriptionJson { - inscription_id: info.entry.id, - charms: Charm::ALL - .iter() - .filter(|charm| charm.is_set(info.charms)) - .map(|charm| charm.title().into()) - .collect(), - children: info.children, - inscription_number: info.entry.inscription_number, - genesis_height: info.entry.height, - parent: info.parent, - genesis_fee: info.entry.fee, - output_value: info.output.as_ref().map(|o| o.value), + Json(api::Inscription { address: info .output .as_ref() @@ -1359,14 +1525,26 @@ impl Server { .ok() }) .map(|address| address.to_string()), + charms: Charm::ALL + .iter() + .filter(|charm| charm.is_set(info.charms)) + .map(|charm| charm.title().into()) + .collect(), + children: info.children, + content_length: info.inscription.content_length(), + content_type: info.inscription.content_type().map(|s| s.to_string()), + fee: info.entry.fee, + height: info.entry.height, + id: info.entry.id, + number: info.entry.inscription_number, + parent: info.parent, sat: info.entry.sat, satpoint: info.satpoint, - content_type: info.inscription.content_type().map(|s| s.to_string()), - content_length: info.inscription.content_length(), timestamp: timestamp(info.entry.timestamp).timestamp(), previous: info.previous, next: info.next, rune: info.rune, + value: info.output.as_ref().map(|o| o.value), // ---- Ordzaar ---- inscription_sequence: info.entry.sequence_number, @@ -1376,13 +1554,13 @@ impl Server { } else { InscriptionHtml { chain: server_config.chain, - charms: info.charms, + charms: Charm::Vindicated.unset(info.charms), children: info.children, - genesis_fee: info.entry.fee, - genesis_height: info.entry.height, + fee: info.entry.fee, + height: info.entry.height, inscription: info.inscription, - inscription_id: info.entry.id, - inscription_number: info.entry.inscription_number, + id: info.entry.id, + number: info.entry.inscription_number, next: info.next, output: info.output, parent: info.parent, @@ -1495,7 +1673,7 @@ impl Server { let (ids, more) = index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?; - Ok(Json(ChildrenJson { ids, more, page }).into_response()) + Ok(Json(api::Children { ids, more, page }).into_response()) }) } @@ -1527,8 +1705,8 @@ impl Server { let next = more.then_some(page_index + 1); Ok(if accept_json { - Json(InscriptionsJson { - inscriptions, + Json(api::Inscriptions { + ids: inscriptions, page_index, more, }) @@ -1586,8 +1764,8 @@ impl Server { } Ok(if accept_json { - Json(InscriptionsJson { - inscriptions, + Json(api::Inscriptions { + ids: inscriptions, page_index, more, }) @@ -1609,14 +1787,14 @@ impl Server { async fn sat_inscriptions( Extension(index): Extension>, Path(sat): Path, - ) -> ServerResult> { + ) -> ServerResult> { Self::sat_inscriptions_paginated(Extension(index), Path((sat, 0))).await } async fn sat_inscriptions_paginated( Extension(index): Extension>, Path((sat, page)): Path<(u64, u64)>, - ) -> ServerResult> { + ) -> ServerResult> { task::block_in_place(|| { if !index.has_sat_index() { return Err(ServerError::NotFound( @@ -1626,14 +1804,14 @@ impl Server { let (ids, more) = index.get_inscription_ids_by_sat_paginated(Sat(sat), 100, page)?; - Ok(Json(SatInscriptionsJson { ids, more, page })) + Ok(Json(api::SatInscriptions { ids, more, page })) }) } async fn sat_inscription_at_index( Extension(index): Extension>, Path((DeserializeFromStr(sat), inscription_index)): Path<(DeserializeFromStr, isize)>, - ) -> ServerResult> { + ) -> ServerResult> { task::block_in_place(|| { if !index.has_sat_index() { return Err(ServerError::NotFound( @@ -1643,7 +1821,7 @@ impl Server { let id = index.get_inscription_id_by_sat_indexed(sat, inscription_index)?; - Ok(Json(SatInscriptionJson { id })) + Ok(Json(api::SatInscription { id })) }) } @@ -1667,89 +1845,72 @@ mod tests { reqwest::Url, serde::de::DeserializeOwned, std::net::TcpListener, + tempfile::TempDir, }; const RUNE: u128 = 99246114928149462; - struct TestServer { - bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - index: Arc, - ord_server_handle: Handle, - url: Url, - #[allow(unused)] - tempdir: TempDir, + #[derive(Default)] + struct Builder { + bitcoin_rpc_server: Option, + config: String, + ord_args: BTreeMap>, + server_args: BTreeMap>, } - impl TestServer { - fn new() -> Self { - Self::new_with_args(&[], &[]) + impl Builder { + fn bitcoin_rpc_server(self, bitcoin_rpc_server: test_bitcoincore_rpc::Handle) -> Self { + Self { + bitcoin_rpc_server: Some(bitcoin_rpc_server), + ..self + } } - fn new_with_sat_index() -> Self { - Self::new_with_args(&["--index-sats"], &[]) + fn ord_option(mut self, option: &str, value: &str) -> Self { + self.ord_args.insert(option.into(), Some(value.into())); + self } - fn new_with_args(ord_args: &[&str], server_args: &[&str]) -> Self { - Self::new_server(test_bitcoincore_rpc::spawn(), None, ord_args, server_args) + fn ord_flag(mut self, flag: &str) -> Self { + self.ord_args.insert(flag.into(), None); + self } - fn new_with_regtest() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::network::constants::Network::Regtest) - .build(), - None, - &["--chain", "regtest"], - &[], - ) + fn server_option(mut self, option: &str, value: &str) -> Self { + self.server_args.insert(option.into(), Some(value.into())); + self } - fn new_with_regtest_with_json_api() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::network::constants::Network::Regtest) - .build(), - None, - &["--chain", "regtest"], - &["--enable-json-api"], - ) + fn server_flag(mut self, flag: &str) -> Self { + self.server_args.insert(flag.into(), None); + self } - fn new_with_regtest_with_index_sats() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::Network::Regtest) - .build(), - None, - &["--chain", "regtest", "--index-sats"], - &[], - ) + fn chain(self, chain: Chain) -> Self { + self.ord_option("--chain", &chain.to_string()) } - fn new_with_regtest_with_index_runes() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::Network::Regtest) - .build(), - None, - &["--chain", "regtest", "--index-runes"], - &["--enable-json-api"], - ) + fn config(self, config: &str) -> Self { + Self { + config: config.into(), + ..self + } } - fn new_with_bitcoin_rpc_server_and_config( - bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - config: String, - ) -> Self { - Self::new_server(bitcoin_rpc_server, Some(config), &[], &[]) - } + fn build(self) -> TestServer { + let bitcoin_rpc_server = self.bitcoin_rpc_server.unwrap_or_else(|| { + test_bitcoincore_rpc::builder() + .network( + self + .ord_args + .get("--chain") + .map(|chain| chain.as_ref().unwrap().parse::().unwrap()) + .unwrap_or_default() + .network(), + ) + .build() + }); - fn new_server( - bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - config: Option, - ord_args: &[&str], - server_args: &[&str], - ) -> Self { let tempdir = TempDir::new().unwrap(); let cookiefile = tempdir.path().join("cookie"); @@ -1762,38 +1923,71 @@ mod tests { .unwrap() .port(); - let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); + let mut args = vec!["ord".to_string()]; + + args.push("--bitcoin-rpc-url".into()); + args.push(bitcoin_rpc_server.url()); - let config_args = match config { - Some(config) => { - let config_path = tempdir.path().join("ord.yaml"); - fs::write(&config_path, config).unwrap(); - format!("--config {}", config_path.display()) + args.push("--cookie-file".into()); + args.push(cookiefile.to_str().unwrap().into()); + + args.push("--data-dir".into()); + args.push(tempdir.path().to_str().unwrap().into()); + + if !self.ord_args.contains_key("--chain") { + args.push("--chain".into()); + args.push(bitcoin_rpc_server.network()); + } + + for (arg, value) in self.ord_args { + args.push(arg); + + if let Some(value) = value { + args.push(value); } - None => "".to_string(), + } + + args.push("server".into()); + + args.push("--address".into()); + args.push("127.0.0.1".into()); + + args.push("--http-port".into()); + args.push(port.to_string()); + + args.push("--polling-interval".into()); + args.push("100ms".into()); + + for (arg, value) in self.server_args { + args.push(arg); + + if let Some(value) = value { + args.push(value); + } + } + + let arguments = Arguments::try_parse_from(args).unwrap(); + + let Subcommand::Server(server) = arguments.subcommand else { + panic!("unexpected subcommand: {:?}", arguments.subcommand); }; - let (options, server) = parse_server_args(&format!( - "ord --rpc-url {} --cookie-file {} --data-dir {} {config_args} {} server --http-port {} --address 127.0.0.1 {}", - bitcoin_rpc_server.url(), - cookiefile.to_str().unwrap(), - tempdir.path().to_str().unwrap(), - ord_args.join(" "), - port, - server_args.join(" "), - )); + let settings = Settings::from_options(arguments.options) + .or(serde_yaml::from_str::(&self.config).unwrap()) + .or_defaults() + .unwrap(); - let index = Arc::new(Index::open(&options).unwrap()); + let index = Arc::new(Index::open(&settings).unwrap()); let ord_server_handle = Handle::new(); { let index = index.clone(); let ord_server_handle = ord_server_handle.clone(); - thread::spawn(|| server.run(options, index, ord_server_handle).unwrap()); + thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); } while index.statistic(crate::index::Statistic::Commits) == 0 { - thread::sleep(Duration::from_millis(25)); + thread::sleep(Duration::from_millis(50)); } let client = reqwest::blocking::Client::builder() @@ -1806,23 +2000,59 @@ mod tests { Ok(_) => break, Err(err) => { if i == 400 { - panic!("server failed to start: {err}"); + panic!("ord server failed to start: {err}"); } } } - thread::sleep(Duration::from_millis(25)); + thread::sleep(Duration::from_millis(50)); } - Self { + TestServer { bitcoin_rpc_server, index, ord_server_handle, tempdir, - url, + url: Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(), } } + fn https(self) -> Self { + self.server_flag("--https") + } + + fn index_runes(self) -> Self { + self.ord_flag("--index-runes") + } + + fn index_sats(self) -> Self { + self.ord_flag("--index-sats") + } + + fn redirect_http_to_https(self) -> Self { + self.server_flag("--redirect-http-to-https") + } + } + + struct TestServer { + bitcoin_rpc_server: test_bitcoincore_rpc::Handle, + index: Arc, + ord_server_handle: Handle, + #[allow(unused)] + tempdir: TempDir, + url: Url, + } + + impl TestServer { + fn builder() -> Builder { + Default::default() + } + + fn new() -> Self { + Builder::default().build() + } + + #[track_caller] fn get(&self, path: impl AsRef) -> reqwest::blocking::Response { if let Err(error) = self.index.update() { log::error!("{error}"); @@ -1830,6 +2060,7 @@ mod tests { reqwest::blocking::get(self.join_url(path.as_ref())).unwrap() } + #[track_caller] pub(crate) fn get_json(&self, path: impl AsRef) -> T { if let Err(error) = self.index.update() { log::error!("{error}"); @@ -1839,7 +2070,7 @@ mod tests { let response = client .get(self.join_url(path.as_ref())) - .header(reqwest::header::ACCEPT, "application/json") + .header(header::ACCEPT, "application/json") .send() .unwrap(); @@ -1852,6 +2083,7 @@ mod tests { self.url.join(url).unwrap() } + #[track_caller] fn assert_response(&self, path: impl AsRef, status: StatusCode, expected_response: &str) { let response = self.get(path); assert_eq!(response.status(), status, "{}", response.text().unwrap()); @@ -1903,7 +2135,7 @@ mod tests { assert_eq!(response.headers().get(header::LOCATION).unwrap(), location); } - fn mine_blocks(&self, n: u64) -> Vec { + fn mine_blocks(&self, n: u64) -> Vec { let blocks = self.bitcoin_rpc_server.mine_blocks(n); self.index.update().unwrap(); blocks @@ -1922,10 +2154,15 @@ mod tests { } } - fn parse_server_args(args: &str) -> (Options, Server) { + fn parse_server_args(args: &str) -> (Settings, Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (arguments.options, server), + Subcommand::Server(server) => ( + Settings::from_options(arguments.options) + .or_defaults() + .unwrap(), + server, + ), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), @@ -2054,9 +2291,12 @@ mod tests { #[test] fn acme_cache_defaults_to_data_dir() { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server"]).unwrap(); - let acme_cache = Server::acme_cache(None, &arguments.options) - .display() - .to_string(); + + let settings = Settings::from_options(arguments.options) + .or_defaults() + .unwrap(); + + let acme_cache = Server::acme_cache(None, &settings).display().to_string(); assert!( acme_cache.contains(if cfg!(windows) { r"foo\acme-cache" @@ -2072,7 +2312,12 @@ mod tests { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server", "--acme-cache", "bar"]) .unwrap(); - let acme_cache = Server::acme_cache(Some(&"bar".into()), &arguments.options) + + let settings = Settings::from_options(arguments.options) + .or_defaults() + .unwrap(); + + let acme_cache = Server::acme_cache(Some(&"bar".into()), &settings) .display() .to_string(); assert_eq!(acme_cache, "bar") @@ -2193,7 +2438,10 @@ mod tests { #[test] fn search_by_rune_id_returns_rune() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2207,7 +2455,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2223,21 +2471,64 @@ mod tests { server.mine_blocks(1); - server.assert_redirect("/search/2/1", "/rune/AAAAAAAAAAAAA"); - server.assert_redirect("/search?query=2/1", "/rune/AAAAAAAAAAAAA"); - - server.assert_response_regex("/rune/100/200", StatusCode::NOT_FOUND, ".*"); + server.assert_redirect("/search/2:1", "/rune/AAAAAAAAAAAAA"); + server.assert_redirect("/search?query=2:1", "/rune/AAAAAAAAAAAAA"); server.assert_response_regex( - "/search/100000000000000000000/200000000000000000", + "/search/100000000000000000000:200000000000000000", StatusCode::BAD_REQUEST, ".*", ); } + #[test] + fn runes_can_be_queried_by_rune_id() { + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); + + server.mine_blocks(1); + + let rune = Rune(RUNE); + + server.assert_response_regex("/rune/2:1", StatusCode::NOT_FOUND, ".*"); + + server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + op_return: Some( + Runestone { + edicts: vec![Edict { + id: 0, + amount: u128::MAX, + output: 0, + }], + etching: Some(Etching { + rune: Some(rune), + ..Default::default() + }), + ..Default::default() + } + .encipher(), + ), + ..Default::default() + }); + + server.mine_blocks(1); + + server.assert_response_regex( + "/rune/2:1", + StatusCode::OK, + ".*Rune AAAAAAAAAAAAA.*", + ); + } + #[test] fn runes_are_displayed_on_runes_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2253,7 +2544,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2281,7 +2572,7 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() } @@ -2290,7 +2581,7 @@ mod tests { assert_eq!( server.index.get_rune_balances().unwrap(), - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])] ); server.assert_response_regex( @@ -2306,7 +2597,10 @@ mod tests { #[test] fn runes_are_displayed_on_rune_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2320,7 +2614,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2349,7 +2643,7 @@ mod tests { RuneEntry { etching: txid, rune, - supply: u128::max_value(), + supply: u128::MAX, symbol: Some('%'), timestamp: 2, ..Default::default() @@ -2359,7 +2653,7 @@ mod tests { assert_eq!( server.index.get_rune_balances().unwrap(), - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])] ); server.assert_response_regex( @@ -2375,13 +2669,13 @@ mod tests {
          timestamp
          id
          -
          2/1
          +
          2:1
          etching block height
          2
          etching transaction index
          1
          -
          mints
          -
          0
          +
          mint
          +
          no
          supply
          340282366920938463463374607431768211455\u{00A0}%
          burned
          @@ -2414,7 +2708,10 @@ mod tests { #[test] fn runes_are_spaced() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2428,7 +2725,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2458,7 +2755,7 @@ mod tests { RuneEntry { etching: txid, rune, - supply: u128::max_value(), + supply: u128::MAX, symbol: Some('%'), timestamp: 2, spacers: 1, @@ -2469,7 +2766,7 @@ mod tests { assert_eq!( server.index.get_rune_balances().unwrap(), - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])] ); server.assert_response_regex( @@ -2511,7 +2808,10 @@ mod tests { #[test] fn transactions_link_to_etching() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2527,7 +2827,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2555,7 +2855,7 @@ mod tests { RuneEntry { etching: txid, rune: Rune(RUNE), - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() } @@ -2564,7 +2864,7 @@ mod tests { assert_eq!( server.index.get_rune_balances().unwrap(), - [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] + [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])] ); server.assert_response_regex( @@ -2579,7 +2879,10 @@ mod tests { #[test] fn runes_are_displayed_on_output_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2593,7 +2896,7 @@ mod tests { Runestone { edicts: vec![Edict { id: 0, - amount: u128::max_value(), + amount: u128::MAX, output: 0, }], etching: Some(Etching { @@ -2623,7 +2926,7 @@ mod tests { divisibility: 1, etching: txid, rune, - supply: u128::max_value(), + supply: u128::MAX, timestamp: 2, ..Default::default() } @@ -2634,7 +2937,7 @@ mod tests { assert_eq!( server.index.get_rune_balances().unwrap(), - [(output, vec![(id, u128::max_value())])] + [(output, vec![(id, u128::MAX)])] ); server.assert_response_regex( @@ -2660,52 +2963,103 @@ mod tests { ); assert_eq!( - server.get_json::(format!("/output/{output}")), - OutputJson { + server.get_json::(format!("/output/{output}")), + api::Output { value: 5000000000, script_pubkey: String::new(), address: None, transaction: txid.to_string(), sat_ranges: None, + indexed: true, inscriptions: Vec::new(), - runes: vec![(Rune(RUNE), 340282366920938463463374607431768211455)] - .into_iter() - .collect(), + runes: vec![( + SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, + Pile { + amount: 340282366920938463463374607431768211455, + divisibility: 1, + symbol: None, + } + )], + spent: false, } ); } #[test] fn http_to_https_redirect_with_path() { - TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"]).assert_redirect( - "/sat/0", - &format!("https://{}/sat/0", System::host_name().unwrap()), - ); + TestServer::builder() + .redirect_http_to_https() + .https() + .build() + .assert_redirect( + "/sat/0", + &format!("https://{}/sat/0", System::host_name().unwrap()), + ); } #[test] fn http_to_https_redirect_with_empty() { - TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"]) + TestServer::builder() + .redirect_http_to_https() + .https() + .build() .assert_redirect("/", &format!("https://{}/", System::host_name().unwrap())); } #[test] fn status() { - let test_server = TestServer::new(); + let server = TestServer::builder().chain(Chain::Regtest).build(); - test_server.assert_response_regex( + server.mine_blocks(3); + + server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[( + 1, + 0, + 0, + inscription("text/plain;charset=utf-8", "hello").to_witness(), + )], + ..Default::default() + }); + + server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[( + 2, + 0, + 0, + inscription("text/plain;charset=utf-8", "hello").to_witness(), + )], + ..Default::default() + }); + + server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[( + 3, + 0, + 0, + Inscription::new(None, Some("hello".as_bytes().into())).to_witness(), + )], + ..Default::default() + }); + + server.mine_blocks(1); + + server.assert_response_regex( "/status", StatusCode::OK, ".*

          Status

          chain
          -
          mainnet
          +
          regtest
          height
          -
          0
          +
          4
          inscriptions
          -
          0
          +
          3
          blessed inscriptions
          -
          0
          +
          3
          cursed inscriptions
          0
          runes
          @@ -2717,7 +3071,7 @@ mod tests {
          uptime
          .*
          minimum rune for next block
          -
          AAAAAAAAAAAAA
          +
          .*
          version
          .*
          unrecoverably reorged
          @@ -2736,6 +3090,15 @@ mod tests { [[:xdigit:]]{40} +
          inscription content types
          +
          +
          +
          text/plain;charset=utf-8
          +
          2 +
          none
          +
          1 +
          +
          .*", ); @@ -2825,7 +3188,7 @@ mod tests { TestServer::new().assert_response( "/range/=/0", StatusCode::BAD_REQUEST, - "Invalid URL: invalid digit found in string", + "Invalid URL: failed to parse sat `=`: invalid integer: invalid digit found in string", ); } @@ -2834,7 +3197,7 @@ mod tests { TestServer::new().assert_response( "/range/0/=", StatusCode::BAD_REQUEST, - "Invalid URL: invalid digit found in string", + "Invalid URL: failed to parse sat `=`: invalid integer: invalid digit found in string", ); } @@ -2848,7 +3211,7 @@ mod tests { TestServer::new().assert_response_regex( "/range/0/1", StatusCode::OK, - r".*Sat range 0–1.*

          Sat range 0–1

          + r".*Sat Range 0–1.*

          Sat Range 0–1

          value
          1
          first
          0
          @@ -2902,7 +3265,7 @@ mod tests { TestServer::new().assert_response( "/sat/2099999997690000", StatusCode::BAD_REQUEST, - "Invalid URL: invalid sat", + "Invalid URL: failed to parse sat `2099999997690000`: invalid integer range", ); } @@ -2918,22 +3281,26 @@ mod tests { #[test] fn output_with_sat_index() { let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; - TestServer::new_with_sat_index().assert_response_regex( - format!("/output/{txid}:0"), - StatusCode::OK, - format!( - ".*Output {txid}:0.*

          Output {txid}:0

          + TestServer::builder() + .index_sats() + .build() + .assert_response_regex( + format!("/output/{txid}:0"), + StatusCode::OK, + format!( + ".*Output {txid}:0.*

          Output {txid}:0

          value
          5000000000
          script pubkey
          OP_PUSHBYTES_65 [[:xdigit:]]{{130}} OP_CHECKSIG
          transaction
          {txid}
          +
          spent
          false

          1 Sat Range

          .*" - ), - ); + ), + ); } #[test] @@ -2948,6 +3315,7 @@ mod tests {
          value
          5000000000
          script pubkey
          OP_PUSHBYTES_65 [[:xdigit:]]{{130}} OP_CHECKSIG
          transaction
          {txid}
          +
          spent
          false
          .*" ), ); @@ -2956,7 +3324,7 @@ mod tests { #[test] fn null_output_is_initially_empty() { let txid = "0000000000000000000000000000000000000000000000000000000000000000"; - TestServer::new_with_sat_index().assert_response_regex( + TestServer::builder().index_sats().build().assert_response_regex( format!("/output/{txid}:4294967295"), StatusCode::OK, format!( @@ -2965,6 +3333,7 @@ mod tests {
          value
          0
          script pubkey
          transaction
          {txid}
          +
          spent
          false

          0 Sat Ranges

.*Inscription 0.*
location
\s*
{reveal}:0:0
.*", @@ -172,15 +179,15 @@ fn inscription_page_after_send() { let txid = CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {inscription}" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stdout_regex(".*") .run_and_deserialize_output::() - .transaction; + .txid; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{inscription}"), format!( r".*

Inscription 0

.*
address
\s*
bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv
.*
location
\s*
{txid}:0:0
.*", @@ -190,17 +197,16 @@ fn inscription_page_after_send() { #[test] fn inscription_content() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - rpc_server.mine_blocks(1); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (inscription, _) = inscribe(&rpc_server); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let response = - TestServer::spawn_with_args(&rpc_server, &[]).request(format!("/content/{inscription}")); + let response = ord_rpc_server.request(format!("/content/{inscription}")); assert_eq!(response.status(), StatusCode::OK); assert_eq!( @@ -237,27 +243,29 @@ fn inscription_metadata() { ]); ciborium::ser::into_writer(&cbor_map, &mut encoded_metadata).unwrap(); - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - rpc_server.mine_blocks(1); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let inscription_id = CommandBuilder::new( "wallet inscribe --fee-rate 1 --json-metadata metadata.json --file foo.txt", ) .write("foo.txt", "FOO") .write("metadata.json", metadata) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .inscriptions .first() .unwrap() .id; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let response = - TestServer::spawn_with_args(&rpc_server, &[]).request(format!("/r/metadata/{inscription_id}")); + let response = ord_rpc_server.request(format!("/r/metadata/{inscription_id}")); assert_eq!(response.status(), StatusCode::OK); assert_eq!( @@ -270,14 +278,68 @@ fn inscription_metadata() { ); } +#[test] +fn recursive_inscription_endpoint() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --file foo.txt") + .write("foo.txt", "FOO") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + let inscription = output.inscriptions.first().unwrap(); + let response = ord_rpc_server.request(format!("/r/inscription/{}", inscription.id)); + + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response.headers().get("content-type").unwrap(), + "application/json" + ); + + let inscription_recursive_json: api::InscriptionRecursive = + serde_json::from_str(&response.text().unwrap()).unwrap(); + + pretty_assert_eq!( + inscription_recursive_json, + api::InscriptionRecursive { + charms: vec!["coin".into(), "uncommon".into()], + content_type: Some("text/plain;charset=utf-8".to_string()), + content_length: Some(3), + fee: 138, + height: 2, + id: inscription.id, + number: 0, + output: inscription.location.outpoint, + sat: Some(Sat(50 * COIN_VALUE)), + satpoint: SatPoint { + outpoint: inscription.location.outpoint, + offset: 0, + }, + timestamp: 2, + value: Some(10000), + } + ) +} + #[test] fn inscriptions_page() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - let (inscription, _) = inscribe(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + ord_rpc_server.assert_response_regex( "/inscriptions", format!( ".*

All Inscriptions

@@ -291,29 +353,33 @@ fn inscriptions_page() { #[test] fn inscriptions_page_is_sorted() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); let mut regex = String::new(); for _ in 0..8 { - let (inscription, _) = inscribe(&rpc_server); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); regex.insert_str(0, &format!(".*
.*")); } - TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex("/inscriptions", ®ex); + ord_rpc_server.assert_response_regex("/inscriptions", ®ex); } #[test] fn inscriptions_page_has_next_and_previous() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - let (a, _) = inscribe(&rpc_server); - let (b, _) = inscribe(&rpc_server); - let (c, _) = inscribe(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( + let (a, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + let (b, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + let (c, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + ord_rpc_server.assert_response_regex( format!("/inscription/{b}"), format!( ".*

Inscription 1

.* @@ -336,118 +402,63 @@ fn expected_sat_time_is_rounded() { ); } -#[test] -#[ignore] -fn server_runs_with_rpc_user_and_pass_as_env_vars() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); - - let tempdir = TempDir::new().unwrap(); - let port = TcpListener::bind("127.0.0.1:0") - .unwrap() - .local_addr() - .unwrap() - .port(); - - let mut child = Command::new(executable_path("ord")) - .args(format!( - "--rpc-url {} --bitcoin-data-dir {} --data-dir {} server --http-port {port} --address 127.0.0.1", - rpc_server.url(), - tempdir.path().display(), - tempdir.path().display()).to_args() - ) - .env("ORD_BITCOIN_RPC_PASS", "bar") - .env("ORD_BITCOIN_RPC_USER", "foo") - .env("ORD_INTEGRATION_TEST", "1") - .current_dir(&tempdir) - .spawn().unwrap(); - - for i in 0.. { - match reqwest::blocking::get(format!("http://127.0.0.1:{port}/status")) { - Ok(_) => break, - Err(err) => { - if i == 400 { - panic!("Server failed to start: {err}"); - } - } - } - - thread::sleep(Duration::from_millis(25)); - } - - rpc_server.mine_blocks(1); - - for i in 0.. { - let response = reqwest::blocking::get(format!("http://127.0.0.1:{port}/blockcount")).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - if response.text().unwrap() == "2" { - break; - } - - if i == 400 { - panic!("server failed to sync"); - } - - thread::sleep(Duration::from_millis(25)); - } - - child.kill().unwrap(); -} - #[test] fn missing_credentials() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new("--bitcoin-rpc-user foo server") - .rpc_server(&rpc_server) + CommandBuilder::new("--bitcoin-rpc-username foo server") + .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) - .expected_stderr("error: no bitcoind rpc password specified\n") + .expected_stderr("error: no bitcoin RPC password specified\n") .run_and_extract_stdout(); - CommandBuilder::new("--bitcoin-rpc-pass bar server") - .rpc_server(&rpc_server) + CommandBuilder::new("--bitcoin-rpc-password bar server") + .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) - .expected_stderr("error: no bitcoind rpc user specified\n") + .expected_stderr("error: no bitcoin RPC username specified\n") .run_and_extract_stdout(); } #[test] fn all_endpoints_in_recursive_directory_return_json() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(2); + bitcoin_rpc_server.mine_blocks(2); - let server = TestServer::spawn_with_args(&rpc_server, &[]); + let ord_server = TestServer::spawn_with_args(&bitcoin_rpc_server, &[]); - assert_eq!(server.request("/r/blockheight").json::().unwrap(), 2); + assert_eq!( + ord_server.request("/r/blockheight").json::().unwrap(), + 2 + ); - assert_eq!(server.request("/r/blocktime").json::().unwrap(), 2); + assert_eq!(ord_server.request("/r/blocktime").json::().unwrap(), 2); assert_eq!( - server.request("/r/blockhash").json::().unwrap(), + ord_server.request("/r/blockhash").json::().unwrap(), "70a93647a8d559c7e7ff2df9bd875f5b726a2ff8ca3562003d257df5a4c47ae2" ); assert_eq!( - server.request("/r/blockhash/0").json::().unwrap(), + ord_server + .request("/r/blockhash/0") + .json::() + .unwrap(), "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" ); - assert!(server.request("/blockhash").json::().is_err()); + assert!(ord_server.request("/blockhash").json::().is_err()); - assert!(server.request("/blockhash/2").json::().is_err()); + assert!(ord_server.request("/blockhash/2").json::().is_err()); } #[test] fn sat_recursive_endpoints_without_sat_index_return_404() { - let rpc_server = test_bitcoincore_rpc::spawn(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); - rpc_server.mine_blocks(1); - - let server = TestServer::spawn_with_args(&rpc_server, &[""]); + let server = TestServer::spawn_with_args(&bitcoin_rpc_server, &[""]); assert_eq!( server.request("/r/sat/5000000000").status(), @@ -462,33 +473,36 @@ fn sat_recursive_endpoints_without_sat_index_return_404() { #[test] fn inscription_transactions_are_stored_with_transaction_index() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let (_inscription, reveal) = inscribe(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-transactions"], &[]); - let server = TestServer::spawn_with_args(&rpc_server, &["--index-transactions"]); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let coinbase = rpc_server.tx(1, 0).txid(); + let (_inscription, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + let coinbase = bitcoin_rpc_server.tx(1, 0).txid(); assert_eq!( - server.request(format!("/tx/{reveal}")).status(), + ord_rpc_server.request(format!("/tx/{reveal}")).status(), StatusCode::OK, ); assert_eq!( - server.request(format!("/tx/{coinbase}")).status(), + ord_rpc_server.request(format!("/tx/{coinbase}")).status(), StatusCode::OK, ); - rpc_server.clear_state(); + bitcoin_rpc_server.clear_state(); assert_eq!( - server.request(format!("/tx/{reveal}")).status(), + ord_rpc_server.request(format!("/tx/{reveal}")).status(), StatusCode::OK, ); assert_eq!( - server.request(format!("/tx/{coinbase}")).status(), + ord_rpc_server.request(format!("/tx/{coinbase}")).status(), StatusCode::NOT_FOUND, ); } @@ -506,7 +520,7 @@ fn run_no_sync() { let tempdir = Arc::new(TempDir::new().unwrap()); let builder = CommandBuilder::new(format!("server --address 127.0.0.1 --http-port {port}",)) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .temp_dir(tempdir.clone()); let mut command = builder.command(); @@ -535,7 +549,7 @@ fn run_no_sync() { let builder = CommandBuilder::new(format!( "server --no-sync --address 127.0.0.1 --http-port {port}", )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .temp_dir(tempdir); let mut command = builder.command(); @@ -561,3 +575,47 @@ fn run_no_sync() { child.kill().unwrap(); } + +#[test] +fn authentication() { + let rpc_server = test_bitcoincore_rpc::spawn(); + + let port = TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port(); + + let builder = CommandBuilder::new(format!( + " --server-username foo --server-password bar server --address 127.0.0.1 --http-port {port}" + )) + .bitcoin_rpc_server(&rpc_server); + + let mut command = builder.command(); + + let mut child = command.spawn().unwrap(); + + for attempt in 0.. { + if let Ok(response) = reqwest::blocking::get(format!("http://localhost:{port}")) { + if response.status() == 401 { + break; + } + } + + if attempt == 100 { + panic!("Server did not respond"); + } + + thread::sleep(Duration::from_millis(50)); + } + + let response = reqwest::blocking::Client::new() + .get(format!("http://localhost:{port}")) + .basic_auth("foo", Some("bar")) + .send() + .unwrap(); + + assert_eq!(response.status(), 200); + + child.kill().unwrap(); +} diff --git a/tests/settings.rs b/tests/settings.rs new file mode 100644 index 0000000000..bfd271d28b --- /dev/null +++ b/tests/settings.rs @@ -0,0 +1,142 @@ +use super::*; + +#[test] +fn default() { + CommandBuilder::new("settings") + .integration_test(false) + .stdout_regex( + r#"\{ + "bitcoin_data_dir": ".*(Bitcoin|bitcoin)", + "bitcoin_rpc_password": null, + "bitcoin_rpc_url": "127.0.0.1:8332", + "bitcoin_rpc_username": null, + "chain": "mainnet", + "commit_interval": 5000, + "config": null, + "config_dir": null, + "cookie_file": ".*\.cookie", + "data_dir": ".*", + "first_inscription_height": 767430, + "height_limit": null, + "hidden": \[\], + "index": ".*index\.redb", + "index_cache_size": \d+, + "index_runes": false, + "index_sats": false, + "index_spent_sats": false, + "index_transactions": false, + "integration_test": false, + "no_index_inscriptions": false, + "server_password": null, + "server_url": null, + "server_username": null +\} +"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn config_is_loaded_from_config_option() { + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + fs::write(&config, "chain: regtest").unwrap(); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn config_invalid_error_message() { + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + fs::write(&config, "foo").unwrap(); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stderr_regex("error: failed to deserialize config file `.*ord.yaml`\nbecause:.*") + .expected_exit_code(1) + .run_and_extract_stdout(); +} + +#[test] +fn config_not_found_error_message() { + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stderr_regex("error: failed to open config file `.*ord.yaml`\nbecause:.*") + .expected_exit_code(1) + .run_and_extract_stdout(); +} + +#[test] +fn config_is_loaded_from_config_dir() { + let tempdir = TempDir::new().unwrap(); + + fs::write(tempdir.path().join("ord.yaml"), "chain: regtest").unwrap(); + + CommandBuilder::new(format!( + "--config-dir {} settings", + tempdir.path().to_str().unwrap() + )) + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn config_is_loaded_from_data_dir() { + CommandBuilder::new("settings") + .write("ord.yaml", "chain: regtest") + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn env_is_loaded() { + CommandBuilder::new("settings") + .stdout_regex( + r#".* + "chain": "mainnet", +.*"#, + ) + .run_and_extract_stdout(); + + CommandBuilder::new("settings") + .env("ORD_CHAIN", "regtest") + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[cfg(unix)] +#[test] +fn invalid_env_error_message() { + use std::os::unix::ffi::OsStringExt; + + CommandBuilder::new("settings") + .env("ORD_BAR", OsString::from_vec(b"\xFF".into())) + .stderr_regex("error: environment variable `ORD_BAR` not valid unicode: `�`\n") + .expected_exit_code(1) + .run_and_extract_stdout(); +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 7541264411..03781b33e0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,42 +1,41 @@ use { super::*, - crate::command_builder::ToArgs, + axum_server::Handle, bitcoincore_rpc::{Auth, Client, RpcApi}, + ord::{parse_ord_server_args, Index}, reqwest::blocking::Response, }; pub(crate) struct TestServer { - child: Child, + bitcoin_rpc_url: String, + ord_server_handle: Handle, port: u16, #[allow(unused)] tempdir: TempDir, - rpc_url: String, } impl TestServer { + pub(crate) fn spawn(bitcoin_rpc_server: &test_bitcoincore_rpc::Handle) -> Self { + Self::spawn_with_server_args(bitcoin_rpc_server, &[], &[]) + } + pub(crate) fn spawn_with_args( - rpc_server: &test_bitcoincore_rpc::Handle, + bitcoin_rpc_server: &test_bitcoincore_rpc::Handle, ord_args: &[&str], ) -> Self { - Self::spawn_with_server_args(rpc_server, ord_args, &[]) + Self::spawn_with_server_args(bitcoin_rpc_server, ord_args, &[]) } pub(crate) fn spawn_with_server_args( - rpc_server: &test_bitcoincore_rpc::Handle, + bitcoin_rpc_server: &test_bitcoincore_rpc::Handle, ord_args: &[&str], - server_args: &[&str], + ord_server_args: &[&str], ) -> Self { let tempdir = TempDir::new().unwrap(); - let cookie_file = match rpc_server.network().as_str() { - "mainnet" => tempdir.path().join(".cookie"), - network => { - fs::create_dir(tempdir.path().join(network)).unwrap(); - tempdir.path().join(format!("{network}/.cookie")) - } - }; + let cookiefile = tempdir.path().join("cookie"); - fs::write(cookie_file.clone(), "foo:bar").unwrap(); + fs::write(&cookiefile, "username:password").unwrap(); let port = TcpListener::bind("127.0.0.1:0") .unwrap() @@ -44,36 +43,43 @@ impl TestServer { .unwrap() .port(); - let child = Command::new(executable_path("ord")).args(format!( - "--rpc-url {} --bitcoin-data-dir {} --data-dir {} {} server {} --http-port {port} --address 127.0.0.1", - rpc_server.url(), + let (settings, server) = parse_ord_server_args(&format!( + "ord --bitcoin-rpc-url {} --cookie-file {} --bitcoin-data-dir {} --data-dir {} {} server {} --http-port {port} --address 127.0.0.1", + bitcoin_rpc_server.url(), + cookiefile.to_str().unwrap(), tempdir.path().display(), tempdir.path().display(), ord_args.join(" "), - server_args.join(" "), - ).to_args()) - .env("ORD_INTEGRATION_TEST", "1") - .current_dir(&tempdir) - .spawn().unwrap(); + ord_server_args.join(" "), + )); + + let index = Arc::new(Index::open(&settings).unwrap()); + let ord_server_handle = Handle::new(); + + { + let index = index.clone(); + let ord_server_handle = ord_server_handle.clone(); + thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); + } for i in 0.. { match reqwest::blocking::get(format!("http://127.0.0.1:{port}/status")) { Ok(_) => break, Err(err) => { if i == 400 { - panic!("Server failed to start: {err}"); + panic!("ord server failed to start: {err}"); } } } - thread::sleep(Duration::from_millis(25)); + thread::sleep(Duration::from_millis(50)); } Self { - child, - tempdir, + bitcoin_rpc_url: bitcoin_rpc_server.url(), + ord_server_handle, port, - rpc_url: rpc_server.url(), + tempdir, } } @@ -89,6 +95,18 @@ impl TestServer { assert_regex_match!(response.text().unwrap(), regex.as_ref()); } + pub(crate) fn assert_response(&self, path: impl AsRef, expected_response: &str) { + self.sync_server(); + let response = reqwest::blocking::get(self.url().join(path.as_ref()).unwrap()).unwrap(); + assert_eq!( + response.status(), + StatusCode::OK, + "{}", + response.text().unwrap() + ); + pretty_assert_eq!(response.text().unwrap(), expected_response); + } + pub(crate) fn request(&self, path: impl AsRef) -> Response { self.sync_server(); @@ -108,24 +126,28 @@ impl TestServer { } pub(crate) fn sync_server(&self) { - let client = Client::new(&self.rpc_url, Auth::None).unwrap(); + let client = Client::new(&self.bitcoin_rpc_url, Auth::None).unwrap(); let chain_block_count = client.get_block_count().unwrap() + 1; for i in 0.. { let response = reqwest::blocking::get(self.url().join("/blockcount").unwrap()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); - if response.text().unwrap().parse::().unwrap() >= chain_block_count { + + let ord_height = response.text().unwrap().parse::().unwrap(); + + if ord_height >= chain_block_count { break; } else if i == 20 { panic!("index failed to synchronize with chain"); } - thread::sleep(Duration::from_millis(25)); + thread::sleep(Duration::from_millis(50)); } } } impl Drop for TestServer { fn drop(&mut self) { - self.child.kill().unwrap() + self.ord_server_handle.shutdown(); } } diff --git a/tests/traits.rs b/tests/traits.rs index 3eb4aa6fa1..3274397cfb 100644 --- a/tests/traits.rs +++ b/tests/traits.rs @@ -1,4 +1,4 @@ -use {super::*, ord::subcommand::traits::Output, ord::Rarity}; +use {super::*, ord::subcommand::traits::Output, ordinals::Rarity}; #[test] fn traits_command_prints_sat_traits() { diff --git a/tests/wallet.rs b/tests/wallet.rs index 3e04064183..91cdd81c66 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -1,8 +1,10 @@ use super::*; +mod authentication; mod balance; mod cardinals; mod create; +mod dump; mod inscribe; mod inscriptions; mod outputs; diff --git a/tests/wallet/authentication.rs b/tests/wallet/authentication.rs new file mode 100644 index 0000000000..d639ac1a43 --- /dev/null +++ b/tests/wallet/authentication.rs @@ -0,0 +1,39 @@ +use {super::*, ord::subcommand::wallet::balance::Output}; + +#[test] +fn authentication() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args( + &bitcoin_rpc_server, + &["--server-username", "foo", "--server-password", "bar"], + &[], + ); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + assert_eq!( + CommandBuilder::new("--server-username foo --server-password bar wallet balance") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::() + .cardinal, + 0 + ); + + bitcoin_rpc_server.mine_blocks(1); + + assert_eq!( + CommandBuilder::new("--server-username foo --server-password bar wallet balance") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(), + Output { + cardinal: 50 * COIN_VALUE, + ordinal: 0, + runic: None, + runes: None, + total: 50 * COIN_VALUE, + } + ); +} diff --git a/tests/wallet/balance.rs b/tests/wallet/balance.rs index ea50fe36b8..120029a895 100644 --- a/tests/wallet/balance.rs +++ b/tests/wallet/balance.rs @@ -2,22 +2,27 @@ use {super::*, ord::subcommand::wallet::balance::Output}; #[test] fn wallet_balance() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); assert_eq!( CommandBuilder::new("wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .cardinal, 0 ); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); assert_eq!( CommandBuilder::new("wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), Output { cardinal: 50 * COIN_VALUE, @@ -31,13 +36,16 @@ fn wallet_balance() { #[test] fn inscribed_utxos_are_deducted_from_cardinal() { - let rpc_server = test_bitcoincore_rpc::spawn(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); assert_eq!( CommandBuilder::new("wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), Output { cardinal: 0, @@ -48,11 +56,12 @@ fn inscribed_utxos_are_deducted_from_cardinal() { } ); - inscribe(&rpc_server); + inscribe(&bitcoin_rpc_server, &ord_rpc_server); assert_eq!( CommandBuilder::new("wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), Output { cardinal: 100 * COIN_VALUE - 10_000, @@ -66,15 +75,19 @@ fn inscribed_utxos_are_deducted_from_cardinal() { #[test] fn runic_utxos_are_deducted_from_cardinal() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest", "--index-runes"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); assert_eq!( CommandBuilder::new("--regtest --index-runes wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), Output { cardinal: 0, @@ -85,11 +98,12 @@ fn runic_utxos_are_deducted_from_cardinal() { } ); - etch(&rpc_server, Rune(RUNE)); + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); assert_eq!( CommandBuilder::new("--regtest --index-runes wallet balance") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), Output { cardinal: 100 * COIN_VALUE - 10_000, @@ -100,3 +114,45 @@ fn runic_utxos_are_deducted_from_cardinal() { } ); } +#[test] +fn unsynced_wallet_fails_with_unindexed_output() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + assert_eq!( + CommandBuilder::new("wallet balance") + .ord_rpc_server(&ord_rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .run_and_deserialize_output::(), + Output { + cardinal: 50 * COIN_VALUE, + ordinal: 0, + runic: None, + runes: None, + total: 50 * COIN_VALUE, + } + ); + + let no_sync_ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &["--no-sync"]); + + inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + CommandBuilder::new("wallet balance") + .ord_rpc_server(&no_sync_ord_rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .expected_exit_code(1) + .expected_stderr("error: wallet failed to synchronize with ord server\n") + .run_and_extract_stdout(); + + CommandBuilder::new("wallet --no-sync balance") + .ord_rpc_server(&no_sync_ord_rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .expected_exit_code(1) + .stderr_regex(r"error: output in wallet but not in ord server: [[:xdigit:]]{64}:\d+.*") + .run_and_extract_stdout(); +} diff --git a/tests/wallet/cardinals.rs b/tests/wallet/cardinals.rs index db966c39a8..f33896cb5e 100644 --- a/tests/wallet/cardinals.rs +++ b/tests/wallet/cardinals.rs @@ -5,17 +5,22 @@ use { #[test] fn cardinals() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - inscribe(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + inscribe(&bitcoin_rpc_server, &ord_rpc_server); let all_outputs = CommandBuilder::new("wallet outputs") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); let cardinal_outputs = CommandBuilder::new("wallet cardinals") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(all_outputs.len() - cardinal_outputs.len(), 1); diff --git a/tests/wallet/create.rs b/tests/wallet/create.rs index 59dd85525c..f0ed37ee20 100644 --- a/tests/wallet/create.rs +++ b/tests/wallet/create.rs @@ -7,7 +7,7 @@ fn create() { assert!(!rpc_server.wallets().contains("ord")); CommandBuilder::new("wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output::(); assert!(rpc_server.wallets().contains("ord")); @@ -16,7 +16,7 @@ fn create() { #[test] fn seed_phrases_are_twelve_words_long() { let Output { mnemonic, .. } = CommandBuilder::new("wallet create") - .rpc_server(&test_bitcoincore_rpc::spawn()) + .bitcoin_rpc_server(&test_bitcoincore_rpc::spawn()) .run_and_deserialize_output(); assert_eq!(mnemonic.word_count(), 12); @@ -27,7 +27,7 @@ fn wallet_creates_correct_mainnet_taproot_descriptor() { let rpc_server = test_bitcoincore_rpc::spawn(); CommandBuilder::new("wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output::(); assert_eq!(rpc_server.descriptors().len(), 2); @@ -48,7 +48,7 @@ fn wallet_creates_correct_test_network_taproot_descriptor() { .build(); CommandBuilder::new("--chain signet wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output::(); assert_eq!(rpc_server.descriptors().len(), 2); @@ -67,13 +67,13 @@ fn detect_wrong_descriptors() { let rpc_server = test_bitcoincore_rpc::spawn(); CommandBuilder::new("wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output::(); rpc_server.import_descriptor("wpkh([aslfjk])#a23ad2l".to_string()); CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .stderr_regex( r#"error: wallet "ord" contains unexpected output descriptors, and does not appear to be an `ord` wallet, create a new wallet with `ord wallet create`\n"#, ) @@ -88,7 +88,7 @@ fn create_with_different_name() { assert!(!rpc_server.wallets().contains("inscription-wallet")); CommandBuilder::new("wallet --name inscription-wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output::(); assert!(rpc_server.wallets().contains("inscription-wallet")); diff --git a/tests/wallet/dump.rs b/tests/wallet/dump.rs new file mode 100644 index 0000000000..29ce0a190d --- /dev/null +++ b/tests/wallet/dump.rs @@ -0,0 +1,77 @@ +use super::*; + +#[test] +fn dumped_descriptors_match_wallet_descriptors() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let output = CommandBuilder::new("wallet dump") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stderr_regex(".*") + .run_and_deserialize_output::(); + + assert!(bitcoin_rpc_server + .descriptors() + .iter() + .zip(output.descriptors.iter()) + .all(|(wallet_descriptor, output_descriptor)| *wallet_descriptor == output_descriptor.desc)); +} + +#[test] +fn dumped_descriptors_restore() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let output = CommandBuilder::new("wallet dump") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stderr_regex(".*") + .run_and_deserialize_output::(); + + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new("wallet restore --from descriptor") + .stdin(serde_json::to_string(&output).unwrap().as_bytes().to_vec()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_extract_stdout(); + + assert!(bitcoin_rpc_server + .descriptors() + .iter() + .zip(output.descriptors.iter()) + .all(|(wallet_descriptor, output_descriptor)| *wallet_descriptor == output_descriptor.desc)); +} + +#[test] +fn dump_and_restore_descriptors_with_minify() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let output = CommandBuilder::new("--minify wallet dump") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stderr_regex(".*") + .run_and_deserialize_output::(); + + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new("wallet restore --from descriptor") + .stdin(serde_json::to_string(&output).unwrap().as_bytes().to_vec()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_extract_stdout(); + + assert!(bitcoin_rpc_server + .descriptors() + .iter() + .zip(output.descriptors.iter()) + .all(|(wallet_descriptor, output_descriptor)| *wallet_descriptor == output_descriptor.desc)); +} diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 91a9b6a537..877de7edb2 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -1,20 +1,25 @@ -use {super::*, std::ops::Deref}; +use { + super::*, + ord::subcommand::wallet::{create, inscriptions, receive, send}, + std::ops::Deref, +}; #[test] fn inscribe_creates_inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - assert_eq!(rpc_server.descriptors().len(), 0); + bitcoin_rpc_server.mine_blocks(1); - create_wallet(&rpc_server); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 0); - let (inscription, _) = inscribe(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - assert_eq!(rpc_server.descriptors().len(), 3); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - let request = - TestServer::spawn_with_args(&rpc_server, &[]).request(format!("/content/{inscription}")); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); + + let request = ord_rpc_server.request(format!("/content/{inscription}")); assert_eq!(request.status(), 200); assert_eq!( @@ -26,36 +31,42 @@ fn inscribe_creates_inscriptions() { #[test] fn inscribe_works_with_huge_expensive_inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet inscribe --file foo.txt --satpoint {txid}:0:0 --fee-rate 10" )) .write("foo.txt", [0; 350_000]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); } #[test] fn metaprotocol_appears_on_inscription_page() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); let inscribe = CommandBuilder::new(format!( "wallet inscribe --file foo.txt --metaprotocol foo --satpoint {txid}:0:0 --fee-rate 10" )) .write("foo.txt", [0; 350_000]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", inscribe.inscriptions[0].id), r".*
metaprotocol
\s*
foo
.*", ); @@ -63,41 +74,51 @@ fn metaprotocol_appears_on_inscription_page() { #[test] fn inscribe_fails_if_bitcoin_core_is_too_old() { - let rpc_server = test_bitcoincore_rpc::builder().version(230000).build(); + let bitcoin_rpc_server = test_bitcoincore_rpc::builder().version(230000).build(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); CommandBuilder::new("wallet inscribe --file hello.txt --fee-rate 1") .write("hello.txt", "HELLOWORLD") .expected_exit_code(1) .expected_stderr("error: Bitcoin Core 24.0.0 or newer required, current version is 23.0.0\n") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_extract_stdout(); } #[test] fn inscribe_no_backup() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); - create_wallet(&rpc_server); - assert_eq!(rpc_server.descriptors().len(), 2); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + assert_eq!(bitcoin_rpc_server.descriptors().len(), 2); CommandBuilder::new("wallet inscribe --file hello.txt --no-backup --fee-rate 1") .write("hello.txt", "HELLOWORLD") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - assert_eq!(rpc_server.descriptors().len(), 2); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 2); } #[test] fn inscribe_unknown_file_extension() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --file pepe.xyz --fee-rate 1") .write("pepe.xyz", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex(r"error: unsupported file extension `\.xyz`, supported extensions: apng .*\n") .run_and_extract_stdout(); @@ -105,15 +126,18 @@ fn inscribe_unknown_file_extension() { #[test] fn inscribe_exceeds_chain_limit() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Signet) .build(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + + let ord_rpc_server = TestServer::spawn_with_args(&bitcoin_rpc_server, &["--signet"]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("--chain signet wallet inscribe --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr( "error: content size of 1025 bytes exceeds 1024 byte limit for signet inscriptions\n", @@ -123,45 +147,58 @@ fn inscribe_exceeds_chain_limit() { #[test] fn regtest_has_no_content_size_limit() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("--chain regtest wallet inscribe --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stdout_regex(".*") .run_and_extract_stdout(); } #[test] fn mainnet_has_no_content_size_limit() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Bitcoin) .build(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stdout_regex(".*") .run_and_extract_stdout(); } #[test] fn inscribe_does_not_use_inscribed_sats_as_cardinal_utxos() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks_with_subsidy(1, 100); + bitcoin_rpc_server.mine_blocks_with_subsidy(1, 100); CommandBuilder::new( "wallet inscribe --file degenerate.png --fee-rate 1" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .write("degenerate.png", [1; 100]) .expected_exit_code(1) .expected_stderr("error: wallet does not contain enough cardinal UTXOs, please add additional funds to wallet.\n") @@ -170,20 +207,23 @@ fn inscribe_does_not_use_inscribed_sats_as_cardinal_utxos() { #[test] fn refuse_to_reinscribe_sats() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let (_, reveal) = inscribe(&rpc_server); + let (_, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks_with_subsidy(1, 100); + bitcoin_rpc_server.mine_blocks_with_subsidy(1, 100); CommandBuilder::new(format!( "wallet inscribe --satpoint {reveal}:0:0 --file hello.txt --fee-rate 1" )) .write("hello.txt", "HELLOWORLD") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr(format!("error: sat at {reveal}:0:0 already inscribed\n")) .run_and_extract_stdout(); @@ -191,10 +231,12 @@ fn refuse_to_reinscribe_sats() { #[test] fn refuse_to_inscribe_already_inscribed_utxo() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); - let (inscription, reveal) = inscribe(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let (inscription, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); let output = OutPoint { txid: reveal, @@ -205,55 +247,66 @@ fn refuse_to_inscribe_already_inscribed_utxo() { "wallet inscribe --satpoint {output}:55555 --file hello.txt --fee-rate 1" )) .write("hello.txt", "HELLOWORLD") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr(format!( - "error: utxo {output} already inscribed with inscription {inscription} on sat {output}:0\n", + "error: utxo {output} with sat {output}:0 already inscribed with the following inscriptions:\n{inscription}\n", )) .run_and_extract_stdout(); } #[test] fn inscribe_with_optional_satpoint_arg() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); let Inscribe { inscriptions, .. } = CommandBuilder::new(format!( "wallet inscribe --file foo.txt --satpoint {txid}:0:10000 --fee-rate 1" )) .write("foo.txt", "FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - TestServer::spawn_with_args(&rpc_server, &["--index-sats"]).assert_response_regex( + ord_rpc_server.assert_response_regex( "/sat/5000010000", format!(".*
.*"), ); - TestServer::spawn_with_args(&rpc_server, &[]) - .assert_response_regex(format!("/content/{inscription}",), "FOO"); + ord_rpc_server.assert_response_regex(format!("/content/{inscription}",), "FOO"); } #[test] fn inscribe_with_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("--index-sats wallet inscribe --file degenerate.png --fee-rate 2.0") .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - let tx1 = &rpc_server.mempool()[0]; + let tx1 = &bitcoin_rpc_server.mempool()[0]; let mut fee = 0; for input in &tx1.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); @@ -266,7 +319,7 @@ fn inscribe_with_fee_rate() { pretty_assert_eq!(fee_rate, 2.0); - let tx2 = &rpc_server.mempool()[1]; + let tx2 = &bitcoin_rpc_server.mempool()[1]; let mut fee = 0; for input in &tx2.input { fee += &tx1.output[input.previous_output.vout as usize].value; @@ -289,21 +342,26 @@ fn inscribe_with_fee_rate() { #[test] fn inscribe_with_commit_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new( "--index-sats wallet inscribe --file degenerate.png --commit-fee-rate 2.0 --fee-rate 1", ) .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - let tx1 = &rpc_server.mempool()[0]; + let tx1 = &bitcoin_rpc_server.mempool()[0]; let mut fee = 0; for input in &tx1.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); @@ -316,7 +374,7 @@ fn inscribe_with_commit_fee_rate() { pretty_assert_eq!(fee_rate, 2.0); - let tx2 = &rpc_server.mempool()[1]; + let tx2 = &bitcoin_rpc_server.mempool()[1]; let mut fee = 0; for input in &tx2.input { fee += &tx1.output[input.previous_output.vout as usize].value; @@ -332,58 +390,78 @@ fn inscribe_with_commit_fee_rate() { #[test] fn inscribe_with_wallet_named_foo() { - let rpc_server = test_bitcoincore_rpc::spawn(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); CommandBuilder::new("wallet --name foo create") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet --name foo inscribe --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); } #[test] fn inscribe_with_dry_run_flag() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - CommandBuilder::new("wallet inscribe --dry-run --file degenerate.png --fee-rate 1") - .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - assert!(rpc_server.mempool().is_empty()); + bitcoin_rpc_server.mine_blocks(1); - CommandBuilder::new("wallet inscribe --file degenerate.png --fee-rate 1") + let inscribe = + CommandBuilder::new("wallet inscribe --dry-run --file degenerate.png --fee-rate 1") + .write("degenerate.png", [1; 520]) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert!(inscribe.commit_psbt.is_some()); + assert!(inscribe.reveal_psbt.is_some()); + + assert!(bitcoin_rpc_server.mempool().is_empty()); + + let inscribe = CommandBuilder::new("wallet inscribe --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - assert_eq!(rpc_server.mempool().len(), 2); + assert!(inscribe.commit_psbt.is_none()); + assert!(inscribe.reveal_psbt.is_none()); + + assert_eq!(bitcoin_rpc_server.mempool().len(), 2); } #[test] fn inscribe_with_dry_run_flag_fees_increase() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let total_fee_dry_run = CommandBuilder::new("wallet inscribe --dry-run --file degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .total_fees; let total_fee_normal = CommandBuilder::new("wallet inscribe --dry-run --file degenerate.png --fee-rate 1.1") .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .total_fees; @@ -392,13 +470,17 @@ fn inscribe_with_dry_run_flag_fees_increase() { #[test] fn inscribe_to_specific_destination() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let destination = CommandBuilder::new("wallet receive") - .rpc_server(&rpc_server) - .run_and_deserialize_output::() + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::() .address; let txid = CommandBuilder::new(format!( @@ -406,11 +488,12 @@ fn inscribe_to_specific_destination() { destination.clone().assume_checked() )) .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .reveal; - let reveal_tx = &rpc_server.mempool()[1]; // item 0 is the commit, item 1 is the reveal. + let reveal_tx = &bitcoin_rpc_server.mempool()[1]; // item 0 is the commit, item 1 is the reveal. assert_eq!(reveal_tx.txid(), txid); assert_eq!( reveal_tx.output.first().unwrap().script_pubkey, @@ -420,15 +503,19 @@ fn inscribe_to_specific_destination() { #[test] fn inscribe_to_address_on_different_network() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new( "wallet inscribe --destination tb1qsgx55dp6gn53tsmyjjv4c2ye403hgxynxs0dnm --file degenerate.png --fee-rate 1" ) .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex("error: address tb1qsgx55dp6gn53tsmyjjv4c2ye403hgxynxs0dnm belongs to network testnet which is different from required bitcoin\n") .run_and_extract_stdout(); @@ -436,42 +523,53 @@ fn inscribe_to_address_on_different_network() { #[test] fn inscribe_with_no_limit() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let four_megger = std::iter::repeat(0).take(4_000_000).collect::>(); CommandBuilder::new("wallet inscribe --no-limit degenerate.png --fee-rate 1") .write("degenerate.png", four_megger) - .rpc_server(&rpc_server); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server); } #[test] fn inscribe_works_with_postage() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --file foo.txt --postage 5btc --fee-rate 10".to_string()) .write("foo.txt", [0; 350]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let inscriptions = CommandBuilder::new("wallet inscriptions".to_string()) .write("foo.txt", [0; 350]) - .rpc_server(&rpc_server) - .run_and_deserialize_output::>(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::>(); pretty_assert_eq!(inscriptions[0].postage, 5 * COIN_VALUE); } #[test] fn inscribe_with_non_existent_parent_inscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let parent_id = "0000000000000000000000000000000000000000000000000000000000000000i0"; @@ -479,7 +577,8 @@ fn inscribe_with_non_existent_parent_inscription() { "wallet inscribe --fee-rate 1.0 --parent {parent_id} --file child.png" )) .write("child.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_stderr(format!("error: parent {parent_id} does not exist\n")) .expected_exit_code(1) .run_and_extract_stdout(); @@ -487,20 +586,24 @@ fn inscribe_with_non_existent_parent_inscription() { #[test] fn inscribe_with_parent_inscription_and_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); let parent_id = parent_output.inscriptions[0].id; - let commit_tx = &rpc_server.mempool()[0]; - let reveal_tx = &rpc_server.mempool()[1]; + let commit_tx = &bitcoin_rpc_server.mempool()[0]; + let reveal_tx = &bitcoin_rpc_server.mempool()[1]; assert_eq!( ord::FeeRate::try_from(5.0) @@ -510,20 +613,21 @@ fn inscribe_with_parent_inscription_and_fee_rate() { parent_output.total_fees ); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let child_output = CommandBuilder::new(format!( "wallet inscribe --fee-rate 7.3 --parent {parent_id} --file child.png" )) .write("child.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - assert_eq!(rpc_server.descriptors().len(), 4); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 4); assert_eq!(parent_id, child_output.parent.unwrap()); - let commit_tx = &rpc_server.mempool()[0]; - let reveal_tx = &rpc_server.mempool()[1]; + let commit_tx = &bitcoin_rpc_server.mempool()[0]; + let reveal_tx = &bitcoin_rpc_server.mempool()[1]; assert_eq!( ord::FeeRate::try_from(7.3) @@ -533,11 +637,9 @@ fn inscribe_with_parent_inscription_and_fee_rate() { child_output.total_fees ); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", child_output.parent.unwrap()), format!( ".*
children
.*
.*", @@ -545,10 +647,10 @@ fn inscribe_with_parent_inscription_and_fee_rate() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", child_output.inscriptions[0].id), format!( - ".*
parent
.*
.*", + ".*
parent
.*
.*", child_output.parent.unwrap() ), ); @@ -556,24 +658,27 @@ fn inscribe_with_parent_inscription_and_fee_rate() { #[test] fn reinscribe_with_flag() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 0); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 0); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); let inscribe = CommandBuilder::new("wallet inscribe --file tulip.png --fee-rate 5.0 ") .write("tulip.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); - let txid = rpc_server.mine_blocks(1)[0].txdata[2].txid(); + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[2].txid(); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - let request = ord_server.request(format!("/content/{}", inscribe.inscriptions[0].id)); + let request = ord_rpc_server.request(format!("/content/{}", inscribe.inscriptions[0].id)); assert_eq!(request.status(), 200); @@ -581,45 +686,56 @@ fn reinscribe_with_flag() { "wallet inscribe --file orchid.png --fee-rate 1.1 --reinscribe --satpoint {txid}:0:0" )) .write("orchid.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &["--index-sats"]); - let request = ord_server.request(format!("/content/{}", reinscribe.inscriptions[0].id)); + let request = ord_rpc_server.request(format!("/content/{}", reinscribe.inscriptions[0].id)); assert_eq!(request.status(), 200); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/sat/{}", 50 * COIN_VALUE), format!( ".*
inscriptions
.*
.*.*", inscribe.inscriptions[0].id, reinscribe.inscriptions[0].id ), ); + + let inscriptions = CommandBuilder::new("wallet inscriptions") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert_eq!(inscriptions[0].inscription, inscribe.inscriptions[0].id); + assert_eq!(inscriptions[1].inscription, reinscribe.inscriptions[0].id); } #[test] fn with_reinscribe_flag_but_not_actually_a_reinscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --file tulip.png --fee-rate 5.0 ") .write("tulip.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - let coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let coinbase = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet inscribe --file orchid.png --fee-rate 1.1 --reinscribe --satpoint {coinbase}:0:0" )) .write("orchid.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex("error: reinscribe flag set but this would not be a reinscription.*") .run_and_extract_stdout(); @@ -627,28 +743,31 @@ fn with_reinscribe_flag_but_not_actually_a_reinscription() { #[test] fn try_reinscribe_without_flag() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let reveal_txid = CommandBuilder::new("wallet inscribe --file tulip.png --fee-rate 5.0 ") .write("tulip.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .reveal; - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new(format!( "wallet inscribe --file orchid.png --fee-rate 1.1 --satpoint {reveal_txid}:0:0" )) .write("orchid.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex(format!( "error: sat at {reveal_txid}:0:0 already inscribed.*" @@ -658,23 +777,26 @@ fn try_reinscribe_without_flag() { #[test] fn no_metadata_appears_on_inscription_page_if_no_metadata_is_passed() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new("wallet inscribe --fee-rate 1 --file content.png") .write("content.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); - assert!(!ord_server + assert!(!ord_rpc_server .request(format!("/inscription/{inscription}"),) .text() .unwrap() @@ -683,25 +805,28 @@ fn no_metadata_appears_on_inscription_page_if_no_metadata_is_passed() { #[test] fn json_metadata_appears_on_inscription_page() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new( "wallet inscribe --fee-rate 1 --json-metadata metadata.json --file content.png", ) .write("content.png", [1; 520]) .write("metadata.json", r#"{"foo": "bar", "baz": 1}"#) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{inscription}"), ".*
metadata
.*
foo
bar
baz
1
.*", ); @@ -709,9 +834,12 @@ fn json_metadata_appears_on_inscription_page() { #[test] fn cbor_metadata_appears_on_inscription_page() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new( "wallet inscribe --fee-rate 1 --cbor-metadata metadata.cbor --file content.png", @@ -723,16 +851,15 @@ fn cbor_metadata_appears_on_inscription_page() { 0xA2, 0x63, b'f', b'o', b'o', 0x63, b'b', b'a', b'r', 0x63, b'b', b'a', b'z', 0x01, ], ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{inscription}"), ".*
metadata
.*
foo
bar
baz
1
.*", ); @@ -740,11 +867,18 @@ fn cbor_metadata_appears_on_inscription_page() { #[test] fn error_message_when_parsing_json_metadata_is_reasonable() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + CommandBuilder::new( "wallet inscribe --fee-rate 1 --json-metadata metadata.json --file content.png", ) .write("content.png", [1; 520]) .write("metadata.json", "{") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stderr_regex(".*failed to parse JSON metadata.*") .expected_exit_code(1) .run_and_extract_stdout(); @@ -752,11 +886,18 @@ fn error_message_when_parsing_json_metadata_is_reasonable() { #[test] fn error_message_when_parsing_cbor_metadata_is_reasonable() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + CommandBuilder::new( "wallet inscribe --fee-rate 1 --cbor-metadata metadata.cbor --file content.png", ) .write("content.png", [1; 520]) .write("metadata.cbor", [0x61]) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stderr_regex(".*failed to parse CBOR metadata.*") .expected_exit_code(1) .run_and_extract_stdout(); @@ -764,17 +905,19 @@ fn error_message_when_parsing_cbor_metadata_is_reasonable() { #[test] fn batch_inscribe_fails_if_batchfile_has_no_inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --fee-rate 2.1 --batch batch.yaml") .write("inscription.txt", "Hello World") .write("batch.yaml", "mode: shared-output\ninscriptions: []\n") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stderr_regex(".*batchfile must contain at least one inscription.*") .expected_exit_code(1) .run_and_extract_stdout(); @@ -782,12 +925,13 @@ fn batch_inscribe_fails_if_batchfile_has_no_inscriptions() { #[test] fn batch_inscribe_can_create_one_inscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --fee-rate 2.1 --batch batch.yaml") .write("inscription.txt", "Hello World") @@ -795,16 +939,15 @@ fn batch_inscribe_can_create_one_inscription() { "batch.yaml", "mode: shared-output\ninscriptions:\n- file: inscription.txt\n metadata: 123\n metaprotocol: foo", ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); - - assert_eq!(rpc_server.descriptors().len(), 3); + bitcoin_rpc_server.mine_blocks(1); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); - let request = ord_server.request(format!("/content/{}", output.inscriptions[0].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[0].id)); assert_eq!(request.status(), 200); assert_eq!( @@ -813,7 +956,7 @@ fn batch_inscribe_can_create_one_inscription() { ); assert_eq!(request.text().unwrap(), "Hello World"); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), r".*
metadata
\s*
\n 123\n
.*
metaprotocol
\s*
foo
.*", ); @@ -821,12 +964,13 @@ fn batch_inscribe_can_create_one_inscription() { #[test] fn batch_inscribe_with_multiple_inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --batch batch.yaml --fee-rate 55") .write("inscription.txt", "Hello World") @@ -836,15 +980,15 @@ fn batch_inscribe_with_multiple_inscriptions() { "batch.yaml", "mode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); - let request = TestServer::spawn_with_args(&rpc_server, &[]) - .request(format!("/content/{}", output.inscriptions[0].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[0].id)); assert_eq!(request.status(), 200); assert_eq!( request.headers().get("content-type").unwrap(), @@ -852,34 +996,34 @@ fn batch_inscribe_with_multiple_inscriptions() { ); assert_eq!(request.text().unwrap(), "Hello World"); - let request = TestServer::spawn_with_args(&rpc_server, &[]) - .request(format!("/content/{}", output.inscriptions[1].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[1].id)); assert_eq!(request.status(), 200); assert_eq!(request.headers().get("content-type").unwrap(), "image/png"); - let request = TestServer::spawn_with_args(&rpc_server, &[]) - .request(format!("/content/{}", output.inscriptions[2].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[2].id)); assert_eq!(request.status(), 200); assert_eq!(request.headers().get("content-type").unwrap(), "audio/wav"); } #[test] fn batch_inscribe_with_multiple_inscriptions_with_parent() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); let parent_id = parent_output.inscriptions[0].id; @@ -891,37 +1035,36 @@ fn batch_inscribe_with_multiple_inscriptions_with_parent() { "batch.yaml", format!("parent: {parent_id}\nmode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n") ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), r".*
parent
\s*
.*
.*", ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), r".*
parent
\s*
.*
.*", ); - let request = TestServer::spawn_with_args(&rpc_server, &[]) - .request(format!("/content/{}", output.inscriptions[2].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[2].id)); assert_eq!(request.status(), 200); assert_eq!(request.headers().get("content-type").unwrap(), "audio/wav"); } #[test] fn batch_inscribe_respects_dry_run_flag() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --fee-rate 2.1 --batch batch.yaml --dry-run") .write("inscription.txt", "Hello World") @@ -929,25 +1072,28 @@ fn batch_inscribe_respects_dry_run_flag() { "batch.yaml", "mode: shared-output\ninscriptions:\n- file: inscription.txt\n", ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert!(rpc_server.mempool().is_empty()); + assert!(bitcoin_rpc_server.mempool().is_empty()); - let request = TestServer::spawn_with_args(&rpc_server, &[]) - .request(format!("/content/{}", output.inscriptions[0].id)); + let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[0].id)); assert_eq!(request.status(), 404); } #[test] fn batch_in_same_output_but_different_satpoints() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") .write("inscription.txt", "Hello World") @@ -957,7 +1103,8 @@ fn batch_in_same_output_but_different_satpoints() { "batch.yaml", "mode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); let outpoint = output.inscriptions[0].location.outpoint; @@ -971,13 +1118,11 @@ fn batch_in_same_output_but_different_satpoints() { ); } - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let outpoint = output.inscriptions[0].location.outpoint; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( r".*
location
.*
{}:0
.*", @@ -985,7 +1130,7 @@ fn batch_in_same_output_but_different_satpoints() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( r".*
location
.*
{}:10000
.*", @@ -993,7 +1138,7 @@ fn batch_in_same_output_but_different_satpoints() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( r".*
location
.*
{}:20000
.*", @@ -1001,7 +1146,7 @@ fn batch_in_same_output_but_different_satpoints() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/output/{}", output.inscriptions[0].location.outpoint), format!(r".*
.*.*.*.*.*.*", output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id), ); @@ -1009,10 +1154,13 @@ fn batch_in_same_output_but_different_satpoints() { #[test] fn batch_in_same_output_with_non_default_postage() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") .write("inscription.txt", "Hello World") @@ -1022,10 +1170,12 @@ fn batch_in_same_output_with_non_default_postage() { "batch.yaml", "mode: shared-output\npostage: 777\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); let outpoint = output.inscriptions[0].location.outpoint; + for (i, inscription) in output.inscriptions.iter().enumerate() { assert_eq!( inscription.location, @@ -1036,13 +1186,11 @@ fn batch_in_same_output_with_non_default_postage() { ); } - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let outpoint = output.inscriptions[0].location.outpoint; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( r".*
location
.*
{}:0
.*", @@ -1050,7 +1198,7 @@ fn batch_in_same_output_with_non_default_postage() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( r".*
location
.*
{}:777
.*", @@ -1058,7 +1206,7 @@ fn batch_in_same_output_with_non_default_postage() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( r".*
location
.*
{}:1554
.*", @@ -1066,7 +1214,7 @@ fn batch_in_same_output_with_non_default_postage() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/output/{}", output.inscriptions[0].location.outpoint), format!(r".*.*.*.*.*.*.*", output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id), ); @@ -1074,21 +1222,23 @@ fn batch_in_same_output_with_non_default_postage() { #[test] fn batch_in_separate_outputs_with_parent() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); let parent_id = parent_output.inscriptions[0].id; @@ -1100,7 +1250,8 @@ fn batch_in_separate_outputs_with_parent() { "batch.yaml", format!("parent: {parent_id}\nmode: separate-outputs\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n") ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); for inscription in &output.inscriptions { @@ -1115,34 +1266,32 @@ fn batch_in_separate_outputs_with_parent() { outpoints.dedup(); assert_eq!(outpoints.len(), output.inscriptions.len()); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let output_1 = output.inscriptions[0].location.outpoint; let output_2 = output.inscriptions[1].location.outpoint; let output_3 = output.inscriptions[2].location.outpoint; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
10000
.*.*
location
.*
{}:0
.*", output_1 ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
10000
.*.*
location
.*
{}:0
.*", output_2 ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
10000
.*.*
location
.*
{}:0
.*", output_3 ), ); @@ -1150,21 +1299,23 @@ fn batch_in_separate_outputs_with_parent() { #[test] fn batch_in_separate_outputs_with_parent_and_non_default_postage() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); let parent_id = parent_output.inscriptions[0].id; @@ -1176,7 +1327,8 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { "batch.yaml", format!("parent: {parent_id}\nmode: separate-outputs\npostage: 777\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n") ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); for inscription in &output.inscriptions { @@ -1192,34 +1344,32 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { outpoints.dedup(); assert_eq!(outpoints.len(), output.inscriptions.len()); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let output_1 = output.inscriptions[0].location.outpoint; let output_2 = output.inscriptions[1].location.outpoint; let output_3 = output.inscriptions[2].location.outpoint; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
777
.*.*
location
.*
{}:0
.*", output_1 ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
777
.*.*
location
.*
{}:0
.*", output_2 ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
777
.*.*
location
.*
{}:0
.*", output_3 ), ); @@ -1227,16 +1377,20 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { #[test] fn inscribe_does_not_pick_locked_utxos() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let coinbase_tx = &rpc_server.mine_blocks(1)[0].txdata[0]; + let coinbase_tx = &bitcoin_rpc_server.mine_blocks(1)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); - rpc_server.lock(outpoint); + bitcoin_rpc_server.lock(outpoint); CommandBuilder::new("wallet inscribe --file hello.txt --fee-rate 1") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .write("hello.txt", "HELLOWORLD") .expected_exit_code(1) .stderr_regex("error: wallet contains no cardinal utxos\n") @@ -1245,24 +1399,26 @@ fn inscribe_does_not_pick_locked_utxos() { #[test] fn inscribe_can_compress() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new("wallet inscribe --compress --file foo.txt --fee-rate 1".to_string()) .write("foo.txt", [0; 350_000]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); - - let test_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); - test_server.sync_server(); + ord_rpc_server.sync_server(); let client = reqwest::blocking::Client::builder() .brotli(false) @@ -1271,7 +1427,7 @@ fn inscribe_can_compress() { let response = client .get( - test_server + ord_rpc_server .url() .join(format!("/content/{inscription}",).as_ref()) .unwrap(), @@ -1292,7 +1448,7 @@ fn inscribe_can_compress() { let response = client .get( - test_server + ord_rpc_server .url() .join(format!("/content/{inscription}",).as_ref()) .unwrap(), @@ -1306,24 +1462,26 @@ fn inscribe_can_compress() { #[test] fn inscriptions_are_not_compressed_if_no_space_is_saved_by_compression() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new("wallet inscribe --compress --file foo.txt --fee-rate 1".to_string()) .write("foo.txt", "foo") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); - - let test_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); - test_server.sync_server(); + ord_rpc_server.sync_server(); let client = reqwest::blocking::Client::builder() .brotli(false) @@ -1332,7 +1490,7 @@ fn inscriptions_are_not_compressed_if_no_space_is_saved_by_compression() { let response = client .get( - test_server + ord_rpc_server .url() .join(format!("/content/{inscription}",).as_ref()) .unwrap(), @@ -1346,52 +1504,66 @@ fn inscriptions_are_not_compressed_if_no_space_is_saved_by_compression() { #[test] fn batch_inscribe_fails_if_invalid_network_destination_address() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - rpc_server.mine_blocks(1); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest"], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("--regtest wallet inscribe --fee-rate 2.1 --batch batch.yaml") .write("inscription.txt", "Hello World") .write("batch.yaml", "mode: separate-outputs\ninscriptions:\n- file: inscription.txt\n destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stderr_regex("error: address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 belongs to network bitcoin which is different from required regtest\n") .expected_exit_code(1) .run_and_extract_stdout(); } #[test] -fn batch_inscribe_fails_with_shared_output_and_destination_set() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); +fn batch_inscribe_fails_with_shared_output_or_same_sat_and_destination_set() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - assert_eq!(rpc_server.descriptors().len(), 0); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --fee-rate 2.1 --batch batch.yaml") .write("inscription.txt", "Hello World") .write("tulip.png", "") .write("batch.yaml", "mode: shared-output\ninscriptions:\n- file: inscription.txt\n destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\n- file: tulip.png") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) - .stderr_regex("error: individual inscription destinations cannot be set in shared-output mode\n") + .stderr_regex("error: individual inscription destinations cannot be set in `shared-output` or `same-sat` mode\n") + .run_and_extract_stdout(); + + CommandBuilder::new("wallet inscribe --fee-rate 2.1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", "") + .write("batch.yaml", "mode: same-sat\nsat: 5000000000\ninscriptions:\n- file: inscription.txt\n destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\n- file: tulip.png") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_exit_code(1) + .stderr_regex("error: individual inscription destinations cannot be set in `shared-output` or `same-sat` mode\n") .run_and_extract_stdout(); } #[test] fn batch_inscribe_works_with_some_destinations_set_and_others_not() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --batch batch.yaml --fee-rate 55") .write("inscription.txt", "Hello World") @@ -1401,33 +1573,32 @@ fn batch_inscribe_works_with_some_destinations_set_and_others_not() { "batch.yaml", "mode: separate-outputs\ninscriptions:\n- file: inscription.txt\n destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\n- file: tulip.png\n- file: meow.wav\n destination: bc1pxwww0ct9ue7e8tdnlmug5m2tamfn7q06sahstg39ys4c9f3340qqxrdu9k\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), ".*
address
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
.*", ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( ".*
address
{}
.*", - rpc_server.change_addresses()[0] + bitcoin_rpc_server.change_addresses()[0] ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), ".*
address
@@ -1437,10 +1608,13 @@ fn batch_inscribe_works_with_some_destinations_set_and_others_not() { #[test] fn batch_same_sat() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") .write("inscription.txt", "Hello World") @@ -1450,7 +1624,8 @@ fn batch_same_sat() { "batch.yaml", "mode: same-sat\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -1462,13 +1637,11 @@ fn batch_same_sat() { output.inscriptions[2].location ); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let outpoint = output.inscriptions[0].location.outpoint; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( r".*
location
.*
{}:0
.*", @@ -1476,7 +1649,7 @@ fn batch_same_sat() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( r".*
location
.*
{}:0
.*", @@ -1484,7 +1657,7 @@ fn batch_same_sat() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( r".*
location
.*
{}:0
.*", @@ -1492,7 +1665,7 @@ fn batch_same_sat() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/output/{}", output.inscriptions[0].location.outpoint), format!(r".*.*.*.*.*.*.*", output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id), ); @@ -1500,17 +1673,21 @@ fn batch_same_sat() { #[test] fn batch_same_sat_with_parent() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let parent_id = parent_output.inscriptions[0].id; @@ -1522,7 +1699,8 @@ fn batch_same_sat_with_parent() { "batch.yaml", format!("mode: same-sat\nparent: {parent_id}\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n") ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -1534,13 +1712,11 @@ fn batch_same_sat_with_parent() { output.inscriptions[2].location ); - rpc_server.mine_blocks(1); - - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); + bitcoin_rpc_server.mine_blocks(1); let txid = output.inscriptions[0].location.outpoint.txid; - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", parent_id), format!( r".*
location
.*
{}:0:0
.*", @@ -1548,7 +1724,7 @@ fn batch_same_sat_with_parent() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( r".*
location
.*
{}:1:0
.*", @@ -1556,7 +1732,7 @@ fn batch_same_sat_with_parent() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( r".*
location
.*
{}:1:0
.*", @@ -1564,7 +1740,7 @@ fn batch_same_sat_with_parent() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( r".*
location
.*
{}:1:0
.*", @@ -1572,76 +1748,191 @@ fn batch_same_sat_with_parent() { ), ); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/output/{}", output.inscriptions[0].location.outpoint), format!(r".*.*.*.*.*.*.*", output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id), ); } +#[test] +fn batch_same_sat_with_satpoint_and_reinscription() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png") + .write("parent.png", [1; 520]) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + let inscription_id = output.inscriptions[0].id; + let satpoint = output.inscriptions[0].location; + + CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", [0; 555]) + .write("meow.wav", [0; 2048]) + .write( + "batch.yaml", + format!("mode: same-sat\nsatpoint: {}\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n", satpoint) + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_exit_code(1) + .stderr_regex(".*error: sat at .*:0:0 already inscribed.*") + .run_and_extract_stdout(); + + let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", [0; 555]) + .write("meow.wav", [0; 2048]) + .write( + "batch.yaml", + format!("mode: same-sat\nsatpoint: {}\nreinscribe: true\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n", satpoint) + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert_eq!( + output.inscriptions[0].location, + output.inscriptions[1].location + ); + assert_eq!( + output.inscriptions[1].location, + output.inscriptions[2].location + ); + + bitcoin_rpc_server.mine_blocks(1); + + let outpoint = output.inscriptions[0].location.outpoint; + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_id), + format!( + r".*
location
.*
{}:0
.*", + outpoint + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", output.inscriptions[0].id), + format!( + r".*
location
.*
{}:0
.*", + outpoint + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", output.inscriptions[1].id), + format!( + r".*
location
.*
{}:0
.*", + outpoint + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", output.inscriptions[2].id), + format!( + r".*
location
.*
{}:0
.*", + outpoint + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/output/{}", output.inscriptions[0].location.outpoint), + format!(r".*.*.*.*.*.*.*.*.*", inscription_id, output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id), + ); +} + #[test] fn inscribe_with_sat_arg() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(2); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(2); let Inscribe { inscriptions, .. } = CommandBuilder::new( "--index-sats wallet inscribe --file foo.txt --sat 5010000000 --fee-rate 1", ) .write("foo.txt", "FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - TestServer::spawn_with_args(&rpc_server, &["--index-sats"]).assert_response_regex( + ord_rpc_server.assert_response_regex( "/sat/5010000000", format!(".*.*"), ); - TestServer::spawn_with_args(&rpc_server, &[]) - .assert_response_regex(format!("/content/{inscription}",), "FOO"); + ord_rpc_server.assert_response_regex(format!("/content/{inscription}",), "FOO"); } #[test] fn inscribe_with_sat_arg_fails_if_no_index_or_not_found() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("wallet inscribe --file foo.txt --sat 5010000000 --fee-rate 1") .write("foo.txt", "FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) - .expected_stderr("error: index must be built with `--index-sats` to use `--sat`\n") + .expected_stderr("error: ord index must be built with `--index-sats` to use `--sat`\n") .run_and_extract_stdout(); CommandBuilder::new("--index-sats wallet inscribe --sat 5000000000 --file foo.txt --fee-rate 1") .write("foo.txt", "FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&TestServer::spawn_with_server_args( + &bitcoin_rpc_server, + &["--index-sats"], + &[], + )) .expected_exit_code(1) - .expected_stderr("error: could not find sat `5000000000`\n") + .expected_stderr("error: could not find sat `5000000000` in wallet outputs\n") .run_and_extract_stdout(); } #[test] fn batch_inscribe_with_sat_argument_with_parent() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); - assert_eq!(rpc_server.descriptors().len(), 0); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - create_wallet(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); let parent_output = CommandBuilder::new("--index-sats wallet inscribe --fee-rate 5.0 --file parent.png") .write("parent.png", [1; 520]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - assert_eq!(rpc_server.descriptors().len(), 3); + assert_eq!(bitcoin_rpc_server.descriptors().len(), 3); let parent_id = parent_output.inscriptions[0].id; @@ -1653,12 +1944,13 @@ fn batch_inscribe_with_sat_argument_with_parent() { "batch.yaml", format!("parent: {parent_id}\nmode: same-sat\nsat: 5000111111\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n") ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - TestServer::spawn_with_args(&rpc_server, &["--index-sats"]).assert_response_regex( + ord_rpc_server.assert_response_regex( "/sat/5000111111", format!( ".*.*.*.*", @@ -1669,9 +1961,13 @@ fn batch_inscribe_with_sat_argument_with_parent() { #[test] fn batch_inscribe_with_sat_arg_fails_if_wrong_mode() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") .write("inscription.txt", "Hello World") @@ -1681,17 +1977,57 @@ fn batch_inscribe_with_sat_arg_fails_if_wrong_mode() { "batch.yaml", "mode: shared-output\nsat: 5000111111\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) - .expected_stderr("error: `sat` can only be set in `same-sat` mode\n") + .expected_stderr("error: neither `sat` nor `satpoint` can be set in `same-sat` mode\n") .run_and_extract_stdout(); } +#[test] +fn batch_inscribe_with_satpoint() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); + + let output = CommandBuilder::new("wallet inscribe --fee-rate 1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", [0; 555]) + .write("meow.wav", [0; 2048]) + .write( + "batch.yaml", + format!("mode: same-sat\nsatpoint: {txid}:0:55555\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n", ) + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + ord_rpc_server.assert_response_regex( + "/sat/5000055555", + format!( + ".*.*.*.*", + output.inscriptions[0].id, output.inscriptions[1].id, output.inscriptions[2].id + ), + ); +} + #[test] fn batch_inscribe_with_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(2); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(2); let set_fee_rate = 1.0; @@ -1703,13 +2039,14 @@ fn batch_inscribe_with_fee_rate() { "batch.yaml", "mode: same-sat\nsat: 5000111111\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n" ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - let commit_tx = &rpc_server.mempool()[0]; + let commit_tx = &bitcoin_rpc_server.mempool()[0]; let mut fee = 0; for input in &commit_tx.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); @@ -1720,7 +2057,7 @@ fn batch_inscribe_with_fee_rate() { let fee_rate = fee as f64 / commit_tx.vsize() as f64; pretty_assert_eq!(fee_rate, set_fee_rate); - let reveal_tx = &rpc_server.mempool()[1]; + let reveal_tx = &bitcoin_rpc_server.mempool()[1]; let mut fee = 0; for input in &reveal_tx.input { fee += &commit_tx.output[input.previous_output.vout as usize].value; @@ -1742,24 +2079,26 @@ fn batch_inscribe_with_fee_rate() { #[test] fn server_can_decompress_brotli() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - create_wallet(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let Inscribe { inscriptions, .. } = CommandBuilder::new("wallet inscribe --compress --file foo.txt --fee-rate 1".to_string()) .write("foo.txt", [0; 350_000]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output(); let inscription = inscriptions[0].id; - rpc_server.mine_blocks(1); - - let test_server = TestServer::spawn_with_server_args(&rpc_server, &[], &[]); + bitcoin_rpc_server.mine_blocks(1); - test_server.sync_server(); + ord_rpc_server.sync_server(); let client = reqwest::blocking::Client::builder() .brotli(false) @@ -1768,7 +2107,7 @@ fn server_can_decompress_brotli() { let response = client .get( - test_server + ord_rpc_server .url() .join(format!("/content/{inscription}",).as_ref()) .unwrap(), @@ -1778,7 +2117,7 @@ fn server_can_decompress_brotli() { assert_eq!(response.status(), StatusCode::NOT_ACCEPTABLE); - let test_server = TestServer::spawn_with_server_args(&rpc_server, &[], &["--decompress"]); + let test_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &["--decompress"]); test_server.sync_server(); @@ -1800,3 +2139,461 @@ fn server_can_decompress_brotli() { assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.bytes().unwrap().deref(), [0; 350_000]); } + +#[test] +fn file_inscribe_with_delegate_inscription() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (delegate, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + let inscribe = CommandBuilder::new(format!( + "wallet inscribe --fee-rate 1.0 --delegate {delegate} --file inscription.txt" + )) + .write("inscription.txt", "INSCRIPTION") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscribe.inscriptions[0].id), + format!(r#".*
delegate
\s*
{delegate}
.*"#,), + ); + + ord_rpc_server.assert_response(format!("/content/{}", inscribe.inscriptions[0].id), "FOO"); +} + +#[test] +fn file_inscribe_with_non_existent_delegate_inscription() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let delegate = "0000000000000000000000000000000000000000000000000000000000000000i0"; + + CommandBuilder::new(format!( + "wallet inscribe --fee-rate 1.0 --delegate {delegate} --file child.png" + )) + .write("child.png", [1; 520]) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_stderr(format!("error: delegate {delegate} does not exist\n")) + .expected_exit_code(1) + .run_and_extract_stdout(); +} + +#[test] +fn batch_inscribe_with_delegate_inscription() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (delegate, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + let inscribe = CommandBuilder::new("wallet inscribe --fee-rate 1.0 --batch batch.yaml") + .write("inscription.txt", "INSCRIPTION") + .write( + "batch.yaml", + format!( + "mode: shared-output +inscriptions: +- delegate: {delegate} + file: inscription.txt +" + ), + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscribe.inscriptions[0].id), + format!(r#".*
delegate
\s*
{delegate}
.*"#,), + ); + + ord_rpc_server.assert_response(format!("/content/{}", inscribe.inscriptions[0].id), "FOO"); +} + +#[test] +fn batch_inscribe_with_non_existent_delegate_inscription() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let delegate = "0000000000000000000000000000000000000000000000000000000000000000i0"; + + CommandBuilder::new("wallet inscribe --fee-rate 1.0 --batch batch.yaml") + .write("hello.txt", "Hello, world!") + .write( + "batch.yaml", + format!( + "mode: shared-output +inscriptions: +- delegate: {delegate} + file: hello.txt +" + ), + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_stderr(format!("error: delegate {delegate} does not exist\n")) + .expected_exit_code(1) + .run_and_extract_stdout(); +} + +#[test] +fn batch_inscribe_with_satpoints_with_parent() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let parent_output = + CommandBuilder::new("--index-sats wallet inscribe --fee-rate 5.0 --file parent.png") + .write("parent.png", [1; 520]) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + let txids = bitcoin_rpc_server + .mine_blocks(3) + .iter() + .map(|block| block.txdata[0].txid()) + .collect::>(); + + let satpoint_1 = SatPoint { + outpoint: OutPoint { + txid: txids[0], + vout: 0, + }, + offset: 0, + }; + + let satpoint_2 = SatPoint { + outpoint: OutPoint { + txid: txids[1], + vout: 0, + }, + offset: 0, + }; + + let satpoint_3 = SatPoint { + outpoint: OutPoint { + txid: txids[2], + vout: 0, + }, + offset: 0, + }; + + let sat_1 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_1.outpoint)) + .text() + .unwrap(), + ) + .unwrap() + .sat_ranges + .unwrap()[0] + .0; + + let sat_2 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_2.outpoint)) + .text() + .unwrap(), + ) + .unwrap() + .sat_ranges + .unwrap()[0] + .0; + + let sat_3 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_3.outpoint)) + .text() + .unwrap(), + ) + .unwrap() + .sat_ranges + .unwrap()[0] + .0; + + let parent_id = parent_output.inscriptions[0].id; + + let output = CommandBuilder::new("--index-sats wallet inscribe --fee-rate 1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", [0; 555]) + .write("meow.wav", [0; 2048]) + .write( + "batch.yaml", + format!( + r#" +mode: satpoints +parent: {parent_id} +inscriptions: +- file: inscription.txt + satpoint: {} +- file: tulip.png + satpoint: {} +- file: meow.wav + satpoint: {} +"#, + satpoint_1, satpoint_2, satpoint_3 + ), + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", parent_id), + format!( + r".*
location
.*
{}:0:0
.*", + output.reveal + ), + ); + + for inscription in &output.inscriptions { + assert_eq!(inscription.location.offset, 0); + } + + let outpoints = output + .inscriptions + .iter() + .map(|inscription| inscription.location.outpoint) + .collect::>(); + + assert_eq!(outpoints.len(), output.inscriptions.len()); + + let inscription_1 = output.inscriptions[0]; + let inscription_2 = output.inscriptions[1]; + let inscription_3 = output.inscriptions[2]; + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_1.id), + format!(r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + 50 * COIN_VALUE, + sat_1, + inscription_1.location, + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_2.id), + format!(r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + 50 * COIN_VALUE, + sat_2, + inscription_2.location + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_3.id), + format!(r".*
parent
\s*
.*{parent_id}.*
.*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + 50 * COIN_VALUE, + sat_3, + inscription_3.location + ), + ); +} + +#[test] +fn batch_inscribe_with_satpoints_with_different_sizes() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(3); + + let outpoint_1 = OutPoint { + txid: CommandBuilder::new( + "--index-sats wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 25btc", + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stdout_regex(r".*") + .run_and_deserialize_output::() + .txid, + vout: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let outpoint_2 = OutPoint { + txid: CommandBuilder::new( + "--index-sats wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc", + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stdout_regex(r".*") + .run_and_deserialize_output::() + .txid, + vout: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let outpoint_3 = OutPoint { + txid: CommandBuilder::new( + "--index-sats wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 3btc", + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stdout_regex(r".*") + .run_and_deserialize_output::() + .txid, + vout: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let satpoint_1 = SatPoint { + outpoint: outpoint_1, + offset: 0, + }; + + let satpoint_2 = SatPoint { + outpoint: outpoint_2, + offset: 0, + }; + + let satpoint_3 = SatPoint { + outpoint: outpoint_3, + offset: 0, + }; + + let output_1 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_1.outpoint)) + .text() + .unwrap(), + ) + .unwrap(); + assert_eq!(output_1.value, 25 * COIN_VALUE); + + let output_2 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_2.outpoint)) + .text() + .unwrap(), + ) + .unwrap(); + assert_eq!(output_2.value, COIN_VALUE); + + let output_3 = serde_json::from_str::( + &ord_rpc_server + .json_request(format!("/output/{}", satpoint_3.outpoint)) + .text() + .unwrap(), + ) + .unwrap(); + assert_eq!(output_3.value, 3 * COIN_VALUE); + + let sat_1 = output_1.sat_ranges.unwrap()[0].0; + let sat_2 = output_2.sat_ranges.unwrap()[0].0; + let sat_3 = output_3.sat_ranges.unwrap()[0].0; + + let output = CommandBuilder::new("--index-sats wallet inscribe --fee-rate 1 --batch batch.yaml") + .write("inscription.txt", "Hello World") + .write("tulip.png", [0; 5]) + .write("meow.wav", [0; 2]) + .write( + "batch.yaml", + format!( + r#" +mode: satpoints +inscriptions: +- file: inscription.txt + satpoint: {} +- file: tulip.png + satpoint: {} +- file: meow.wav + satpoint: {} +"#, + satpoint_1, satpoint_2, satpoint_3 + ), + ) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + for inscription in &output.inscriptions { + assert_eq!(inscription.location.offset, 0); + } + + let outpoints = output + .inscriptions + .iter() + .map(|inscription| inscription.location.outpoint) + .collect::>(); + + assert_eq!(outpoints.len(), output.inscriptions.len()); + + let inscription_1 = output.inscriptions[0]; + let inscription_2 = output.inscriptions[1]; + let inscription_3 = output.inscriptions[2]; + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_1.id), + format!( + r".*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + 25 * COIN_VALUE, + sat_1, + inscription_1.location + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_2.id), + format!( + r".*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + COIN_VALUE, + sat_2, + inscription_2.location + ), + ); + + ord_rpc_server.assert_response_regex( + format!("/inscription/{}", inscription_3.id), + format!( + r".*
value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + 3 * COIN_VALUE, + sat_3, + inscription_3.location + ), + ); +} diff --git a/tests/wallet/inscriptions.rs b/tests/wallet/inscriptions.rs index 4ff430510c..9c10ed1924 100644 --- a/tests/wallet/inscriptions.rs +++ b/tests/wallet/inscriptions.rs @@ -5,14 +5,19 @@ use { #[test] fn inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let (inscription, reveal) = inscribe(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (inscription, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); let output = CommandBuilder::new("wallet inscriptions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output.len(), 1); @@ -24,7 +29,8 @@ fn inscriptions() { ); let address = CommandBuilder::new("wallet receive") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .address; @@ -32,16 +38,18 @@ fn inscriptions() { "wallet send --fee-rate 1 {} {inscription}", address.assume_checked() )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(0) .stdout_regex(".*") .run_and_deserialize_output::() - .transaction; + .txid; - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscriptions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output.len(), 1); @@ -51,22 +59,26 @@ fn inscriptions() { #[test] fn inscriptions_includes_locked_utxos() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - rpc_server.mine_blocks(1); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (inscription, reveal) = inscribe(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); - rpc_server.mine_blocks(1); + let (inscription, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.lock(OutPoint { + bitcoin_rpc_server.mine_blocks(1); + + bitcoin_rpc_server.lock(OutPoint { txid: reveal, vout: 0, }); let output = CommandBuilder::new("wallet inscriptions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output.len(), 1); @@ -76,20 +88,26 @@ fn inscriptions_includes_locked_utxos() { #[test] fn inscriptions_with_postage() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); - let (inscription, _) = inscribe(&rpc_server); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); let output = CommandBuilder::new("wallet inscriptions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].postage, 10000); let address = CommandBuilder::new("wallet receive") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() .address; @@ -97,15 +115,17 @@ fn inscriptions_with_postage() { "wallet send --fee-rate 1 {} {inscription}", address.assume_checked() )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(0) .stdout_regex(".*") .run_and_extract_stdout(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet inscriptions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].postage, 9889); diff --git a/tests/wallet/outputs.rs b/tests/wallet/outputs.rs index 39a648e38e..cc7bd9c824 100644 --- a/tests/wallet/outputs.rs +++ b/tests/wallet/outputs.rs @@ -2,15 +2,19 @@ use {super::*, ord::subcommand::wallet::outputs::Output}; #[test] fn outputs() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let coinbase_tx = &rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let coinbase_tx = &bitcoin_rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); let amount = coinbase_tx.output[0].value; let output = CommandBuilder::new("wallet outputs") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].output, outpoint); @@ -19,17 +23,21 @@ fn outputs() { #[test] fn outputs_includes_locked_outputs() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - let coinbase_tx = &rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let coinbase_tx = &bitcoin_rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); let amount = coinbase_tx.output[0].value; - rpc_server.lock(outpoint); + bitcoin_rpc_server.lock(outpoint); let output = CommandBuilder::new("wallet outputs") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].output, outpoint); @@ -38,17 +46,21 @@ fn outputs_includes_locked_outputs() { #[test] fn outputs_includes_unbound_outputs() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let coinbase_tx = &rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; + let coinbase_tx = &bitcoin_rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); let amount = coinbase_tx.output[0].value; - rpc_server.lock(outpoint); + bitcoin_rpc_server.lock(outpoint); let output = CommandBuilder::new("wallet outputs") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].output, outpoint); diff --git a/tests/wallet/receive.rs b/tests/wallet/receive.rs index 3f5f9120d4..448c19af52 100644 --- a/tests/wallet/receive.rs +++ b/tests/wallet/receive.rs @@ -1,13 +1,16 @@ -use {super::*, ord::subcommand::wallet::receive::Output}; +use {super::*, ord::subcommand::wallet::receive}; #[test] fn receive() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); let output = CommandBuilder::new("wallet receive") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); assert!(output.address.is_valid_for_network(Network::Bitcoin)); } diff --git a/tests/wallet/restore.rs b/tests/wallet/restore.rs index 5106c5eb33..ba917da30e 100644 --- a/tests/wallet/restore.rs +++ b/tests/wallet/restore.rs @@ -1,4 +1,4 @@ -use {super::*, ord::subcommand::wallet::create, ord::subcommand::Empty}; +use {super::*, ord::subcommand::wallet::create}; #[test] fn restore_generates_same_descriptors() { @@ -6,7 +6,7 @@ fn restore_generates_same_descriptors() { let rpc_server = test_bitcoincore_rpc::spawn(); let create::Output { mnemonic, .. } = CommandBuilder::new("wallet create") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output(); (mnemonic, rpc_server.descriptors()) @@ -14,9 +14,10 @@ fn restore_generates_same_descriptors() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new(["wallet", "restore", &mnemonic.to_string()]) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + CommandBuilder::new(["wallet", "restore", "--from", "mnemonic"]) + .stdin(mnemonic.to_string().into()) + .bitcoin_rpc_server(&rpc_server) + .run_and_extract_stdout(); assert_eq!(rpc_server.descriptors(), descriptors); } @@ -29,7 +30,7 @@ fn restore_generates_same_descriptors_with_passphrase() { let create::Output { mnemonic, .. } = CommandBuilder::new(["wallet", "create", "--passphrase", passphrase]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&rpc_server) .run_and_deserialize_output(); (mnemonic, rpc_server.descriptors()) @@ -42,10 +43,154 @@ fn restore_generates_same_descriptors_with_passphrase() { "restore", "--passphrase", passphrase, - &mnemonic.to_string(), + "--from", + "mnemonic", ]) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .stdin(mnemonic.to_string().into()) + .bitcoin_rpc_server(&rpc_server) + .run_and_extract_stdout(); assert_eq!(rpc_server.descriptors(), descriptors); } + +#[test] +fn restore_to_existing_wallet_fails() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let descriptors = bitcoin_rpc_server.descriptors(); + + let output = CommandBuilder::new("wallet dump") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .stderr_regex(".*") + .run_and_deserialize_output::(); + + CommandBuilder::new("wallet restore --from descriptor") + .stdin(serde_json::to_string(&output).unwrap().as_bytes().to_vec()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_exit_code(1) + .expected_stderr("error: wallet `ord` already exists\n") + .run_and_extract_stdout(); + + assert_eq!( + descriptors, + output + .descriptors + .into_iter() + .map(|descriptor| descriptor.desc) + .collect::>() + ); +} + +#[test] +fn restore_with_wrong_descriptors_fails() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new("wallet --name foo restore --from descriptor") + .stdin(r#" +{ + "wallet_name": "bar", + "descriptors": [ + { + "desc": "rawtr(cVMYXp8uf1yFU9AAY6NJu1twA2uT94mHQBGkfgqCCzp6RqiTWCvP)#tah5crv7", + "timestamp": 1706047934, + "active": false, + "internal": null, + "range": null, + "next": null + }, + { + "desc": "rawtr(cVdVu6VRwYXsTPMiptqVYLcp7EtQi5sjxLzbPTSNwW6CkCxBbEFs)#5afaht8d", + "timestamp": 1706047934, + "active": false, + "internal": null, + "range": null, + "next": null + }, + { + "desc": "wpkh([c0b9536d/86'/1'/0']tprv8fXhtVjj3vb7kgxKuiWXzcUsur44gbLbbtwxL4HKmpzkBNoMrYqbQhMe7MWhrZjLFc9RBpTRYZZkrS8HH1Q3SmD5DkfpjKqtd97q1JWfqzr/0/*)#dweuu0ww", + "timestamp": 1706047839, + "active": true, + "internal": false, + "range": [ + 0, + 1000 + ], + "next": 1 + }, + { + "desc": "tr([c0b9536d/86'/1'/0']tprv8fXhtVjj3vb7kgxKuiWXzcUsur44gbLbbtwxL4HKmpzkBNoMrYqbQhMe7MWhrZjLFc9RBpTRYZZkrS8HH1Q3SmD5DkfpjKqtd97q1JWfqzr/1/*)#u6uap67k", + "timestamp": 1706047839, + "active": true, + "internal": true, + "range": [ + 0, + 1013 + ], + "next": 14 + } + ] +}"#.into()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .expected_exit_code(1) + .expected_stderr("error: wallet \"foo\" contains unexpected output descriptors, and does not appear to be an `ord` wallet, create a new wallet with `ord wallet create`\n") + .run_and_extract_stdout(); +} + +#[test] +fn restore_with_compact_works() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new("wallet restore --from descriptor") + .stdin(r#"{"wallet_name":"foo","descriptors":[{"desc":"rawtr(cVMYXp8uf1yFU9AAY6NJu1twA2uT94mHQBGkfgqCCzp6RqiTWCvP)#tah5crv7","timestamp":1706047934,"active":false,"internal":null,"range":null,"next":null},{"desc":"rawtr(cVdVu6VRwYXsTPMiptqVYLcp7EtQi5sjxLzbPTSNwW6CkCxBbEFs)#5afaht8d","timestamp":1706047934,"active":false,"internal":null,"range":null,"next":null},{"desc":"tr([c0b9536d/86'/1'/0']tprv8fXhtVjj3vb7kgxKuiWXzcUsur44gbLbbtwxL4HKmpzkBNoMrYqbQhMe7MWhrZjLFc9RBpTRYZZkrS8HH1Q3SmD5DkfpjKqtd97q1JWfqzr/0/*)#dweuu0ww","timestamp":1706047839,"active":true,"internal":false,"range":[0,1000],"next":1},{"desc":"tr([c0b9536d/86'/1'/0']tprv8fXhtVjj3vb7kgxKuiWXzcUsur44gbLbbtwxL4HKmpzkBNoMrYqbQhMe7MWhrZjLFc9RBpTRYZZkrS8HH1Q3SmD5DkfpjKqtd97q1JWfqzr/1/*)#u6uap67k","timestamp":1706047839,"active":true,"internal":true,"range":[0,1013],"next":14}]}"#.into()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .expected_exit_code(0) + .run_and_extract_stdout(); +} + +#[test] +fn restore_with_blank_mnemonic_generates_same_descriptors() { + let (mnemonic, descriptors) = { + let rpc_server = test_bitcoincore_rpc::spawn(); + + let create::Output { mnemonic, .. } = CommandBuilder::new("wallet create") + .bitcoin_rpc_server(&rpc_server) + .run_and_deserialize_output(); + + (mnemonic, rpc_server.descriptors()) + }; + + let rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new(["wallet", "restore", "--from", "mnemonic"]) + .stdin(mnemonic.to_string().into()) + .bitcoin_rpc_server(&rpc_server) + .run_and_extract_stdout(); + + assert_eq!(rpc_server.descriptors(), descriptors); +} + +#[test] +fn passphrase_conflicts_with_descriptor() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn(&bitcoin_rpc_server); + + CommandBuilder::new([ + "wallet", + "restore", + "--from", + "descriptor", + "--passphrase", + "supersecurepassword", + ]) + .stdin("".into()) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_exit_code(1) + .expected_stderr("error: descriptor does not take a passphrase\n") + .run_and_extract_stdout(); +} diff --git a/tests/wallet/sats.rs b/tests/wallet/sats.rs index ca425bd665..3eea9b8b03 100644 --- a/tests/wallet/sats.rs +++ b/tests/wallet/sats.rs @@ -5,11 +5,15 @@ use { #[test] fn requires_sat_index() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("wallet sats") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: sats requires index created with `--index-sats` flag\n") .run_and_extract_stdout(); @@ -17,12 +21,18 @@ fn requires_sat_index() { #[test] fn sats() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let second_coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let second_coinbase = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); let output = CommandBuilder::new("--index-sats wallet sats") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].sat, 50 * COIN_VALUE); @@ -31,13 +41,19 @@ fn sats() { #[test] fn sats_from_tsv_success() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - let second_coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let second_coinbase = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); let output = CommandBuilder::new("--index-sats wallet sats --tsv foo.tsv") .write("foo.tsv", "nvtcsezkbtg") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_eq!(output[0].sat, "nvtcsezkbtg"); @@ -46,25 +62,36 @@ fn sats_from_tsv_success() { #[test] fn sats_from_tsv_parse_error() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("--index-sats wallet sats --tsv foo.tsv") .write("foo.tsv", "===") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr( - "error: failed to parse sat from string \"===\" on line 1: invalid digit found in string\n", + "error: failed to parse sat from string \"===\" on line 1: failed to parse sat `===`: invalid integer: invalid digit found in string\n", ) .run_and_extract_stdout(); } #[test] fn sats_from_tsv_file_not_found() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + CommandBuilder::new("--index-sats wallet sats --tsv foo.tsv") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex("error: I/O error reading `.*`\nbecause: .*\n") .run_and_extract_stdout(); diff --git a/tests/wallet/send.rs b/tests/wallet/send.rs index 61c7f8d3fc..bcab8969e5 100644 --- a/tests/wallet/send.rs +++ b/tests/wallet/send.rs @@ -1,31 +1,41 @@ -use {super::*, ord::subcommand::wallet::send::Output, std::collections::BTreeMap}; +use { + super::*, + base64::Engine, + bitcoin::psbt::Psbt, + ord::subcommand::wallet::{balance, create, send}, + std::collections::BTreeMap, +}; #[test] fn inscriptions_can_be_sent() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let (inscription, _) = inscribe(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - rpc_server.mine_blocks(1); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription}", )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stdout_regex(r".*") - .run_and_deserialize_output::(); + .run_and_deserialize_output::(); - let txid = rpc_server.mempool()[0].txid(); - assert_eq!(txid, output.transaction); + let txid = bitcoin_rpc_server.mempool()[0].txid(); + assert_eq!(txid, output.txid); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let send_txid = output.transaction; + let send_txid = output.txid; - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{inscription}"), format!( ".*

Inscription 0

.*
.* @@ -45,15 +55,19 @@ fn inscriptions_can_be_sent() { #[test] fn send_unknown_inscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {txid}i0" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_stderr(format!("error: inscription {txid}i0 not found\n")) .expected_exit_code(1) .run_and_extract_stdout(); @@ -61,26 +75,30 @@ fn send_unknown_inscription() { #[test] fn send_inscribed_sat() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (inscription, _) = inscribe(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); - rpc_server.mine_blocks(1); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {inscription}", )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let send_txid = output.transaction; + let send_txid = output.txid; - let ord_server = TestServer::spawn_with_args(&rpc_server, &[]); - ord_server.assert_response_regex( + ord_rpc_server.assert_response_regex( format!("/inscription/{inscription}"), format!( ".*

Inscription 0

.*
location
.*
{send_txid}:0:0
.*", @@ -90,30 +108,40 @@ fn send_inscribed_sat() { #[test] fn send_on_mainnnet_works_with_wallet_named_foo() { - let rpc_server = test_bitcoincore_rpc::spawn(); - let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + let txid = bitcoin_rpc_server.mine_blocks(1)[0].txdata[0].txid(); CommandBuilder::new("wallet --name foo create") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); CommandBuilder::new(format!( "wallet --name foo send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0" )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); } #[test] fn send_addresses_must_be_valid_for_network() { - let rpc_server = test_bitcoincore_rpc::builder().build(); - let txid = rpc_server.mine_blocks_with_subsidy(1, 1_000)[0].txdata[0].txid(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::builder().build(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks_with_subsidy(1, 1_000)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet send --fee-rate 1 tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz {txid}:0:0" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_stderr( "error: address tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz belongs to network testnet which is different from required bitcoin\n", ) @@ -123,37 +151,47 @@ fn send_addresses_must_be_valid_for_network() { #[test] fn send_on_mainnnet_works_with_wallet_named_ord() { - let rpc_server = test_bitcoincore_rpc::builder().build(); - let txid = rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0].txid(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::builder().build(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0].txid(); let output = CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0" )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - assert_eq!(rpc_server.mempool()[0].txid(), output.transaction); + assert_eq!(bitcoin_rpc_server.mempool()[0].txid(), output.txid); } #[test] fn send_does_not_use_inscribed_sats_as_cardinal_utxos() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let txid = rpc_server.mine_blocks_with_subsidy(1, 10_000)[0].txdata[0].txid(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let txid = bitcoin_rpc_server.mine_blocks_with_subsidy(1, 10_000)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet inscribe --satpoint {txid}:0:0 --file degenerate.png --fee-rate 0" )) .write("degenerate.png", [1; 100]) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - let txid = rpc_server.mine_blocks_with_subsidy(1, 100)[0].txdata[0].txid(); + let txid = bitcoin_rpc_server.mine_blocks_with_subsidy(1, 100)[0].txdata[0].txid(); CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: wallet does not contain enough cardinal UTXOs, please add additional funds to wallet.\n") .run_and_extract_stdout(); @@ -161,12 +199,15 @@ fn send_does_not_use_inscribed_sats_as_cardinal_utxos() { #[test] fn do_not_send_within_dust_limit_of_an_inscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - let (inscription, reveal) = inscribe(&rpc_server); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + let (inscription, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); let output = OutPoint { txid: reveal, @@ -176,7 +217,8 @@ fn do_not_send_within_dust_limit_of_an_inscription() { CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {output}:329" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr(format!( "error: cannot send {output}:329 without also sending inscription {inscription} at {output}:0\n" @@ -186,12 +228,15 @@ fn do_not_send_within_dust_limit_of_an_inscription() { #[test] fn can_send_after_dust_limit_from_an_inscription() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (_, reveal) = inscribe(&rpc_server); + let (_, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = OutPoint { txid: reveal, @@ -201,20 +246,26 @@ fn can_send_after_dust_limit_from_an_inscription() { CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {output}:330" )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); } #[test] fn splitting_merged_inscriptions_is_possible() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(3); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-sats"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(3); let inscription = envelope(&[b"ord", &[1], b"text/plain;charset=utf-8", &[], b"bar"]); // merging 3 inscriptions into one utxo - let reveal_txid = rpc_server.broadcast_tx(TransactionTemplate { + let reveal_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[ (1, 0, 0, inscription.clone()), (2, 0, 0, inscription.clone()), @@ -224,28 +275,17 @@ fn splitting_merged_inscriptions_is_possible() { ..Default::default() }); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); - let server = - TestServer::spawn_with_server_args(&rpc_server, &["--index-sats"], &["--enable-json-api"]); - - let response = server.json_request(format!("/output/{}:0", reveal_txid)); + let response = ord_rpc_server.json_request(format!("/output/{}:0", reveal_txid)); assert_eq!(response.status(), StatusCode::OK); - let output_json: OutputJson = serde_json::from_str(&response.text().unwrap()).unwrap(); + let output_json: api::Output = serde_json::from_str(&response.text().unwrap()).unwrap(); pretty_assert_eq!( output_json, - OutputJson { - value: 3 * 50 * COIN_VALUE, - script_pubkey: "".to_string(), + api::Output { address: None, - transaction: reveal_txid.to_string(), - sat_ranges: Some(vec![ - (5000000000, 10000000000,), - (10000000000, 15000000000,), - (15000000000, 20000000000,), - ],), inscriptions: vec![ InscriptionId { txid: reveal_txid, @@ -260,7 +300,17 @@ fn splitting_merged_inscriptions_is_possible() { index: 2 }, ], - runes: BTreeMap::new(), + indexed: true, + runes: Vec::new(), + sat_ranges: Some(vec![ + (5000000000, 10000000000,), + (10000000000, 15000000000,), + (15000000000, 20000000000,), + ],), + script_pubkey: "".to_string(), + spent: false, + transaction: reveal_txid.to_string(), + value: 3 * 50 * COIN_VALUE, } ); @@ -269,7 +319,8 @@ fn splitting_merged_inscriptions_is_possible() { "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {}i0", reveal_txid, )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr(format!( "error: cannot send {reveal_txid}:0:0 without also sending inscription {reveal_txid}i2 at {reveal_txid}:0:{}\n", 100 * COIN_VALUE @@ -281,43 +332,50 @@ fn splitting_merged_inscriptions_is_possible() { "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {}i2", reveal_txid, )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); // splitting second to last CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {}i1", reveal_txid, )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); // splitting send first CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {}i0", reveal_txid, )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); } #[test] fn inscriptions_cannot_be_sent_by_satpoint() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (_, reveal) = inscribe(&rpc_server); + let (_, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new(format!( "wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal}:0:0" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_stderr("error: inscriptions must be sent by inscription ID\n") .expected_exit_code(1) .run_and_extract_stdout(); @@ -325,25 +383,31 @@ fn inscriptions_cannot_be_sent_by_satpoint() { #[test] fn send_btc_with_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new( - "wallet send --fee-rate 13.3 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc", + "wallet send --fee-rate 13.3 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 2btc", ) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + let tx = &bitcoin_rpc_server.mempool()[0]; - let tx = &rpc_server.mempool()[0]; let mut fee = 0; for input in &tx.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); } + for output in &tx.output { fee -= output.value; } @@ -353,58 +417,54 @@ fn send_btc_with_fee_rate() { assert!(f64::abs(fee_rate - 13.3) < 0.1); assert_eq!( - rpc_server.sent(), - &[Sent { - amount: 1.0, - address: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" - .parse::>() - .unwrap() - .assume_checked(), - locked: Vec::new(), - }] + Address::from_script(&tx.output[0].script_pubkey, Network::Bitcoin).unwrap(), + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" + .parse::>() + .unwrap() + .assume_checked() ); + + assert_eq!(tx.output[0].value, 2 * COIN_VALUE); } #[test] fn send_btc_locks_inscriptions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - rpc_server.mine_blocks(1); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let (_, reveal) = inscribe(&rpc_server); + bitcoin_rpc_server.mine_blocks(1); + + let (_, reveal) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - assert_eq!( - rpc_server.sent(), - &[Sent { - amount: 1.0, - address: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" - .parse::>() - .unwrap() - .assume_checked(), - locked: vec![OutPoint { - txid: reveal, - vout: 0, - }] - }] - ) + assert!(bitcoin_rpc_server.get_locked().contains(&OutPoint { + txid: reveal, + vout: 0, + })) } #[test] fn send_btc_fails_if_lock_unspent_fails() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .fail_lock_unspent(true) .build(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_stderr("error: failed to lock UTXOs\n") .expected_exit_code(1) .run_and_extract_stdout(); @@ -412,22 +472,27 @@ fn send_btc_fails_if_lock_unspent_fails() { #[test] fn wallet_send_with_fee_rate() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); - let (inscription, _) = inscribe(&rpc_server); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new(format!( "wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription} --fee-rate 2.0" )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - let tx = &rpc_server.mempool()[0]; + let tx = &bitcoin_rpc_server.mempool()[0]; let mut fee = 0; for input in &tx.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); @@ -443,16 +508,21 @@ fn wallet_send_with_fee_rate() { #[test] fn user_must_provide_fee_rate_to_send() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let (inscription, _) = inscribe(&rpc_server); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new(format!( "wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription}" )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(2) .stderr_regex( ".*error: the following required arguments were not provided: @@ -463,22 +533,27 @@ fn user_must_provide_fee_rate_to_send() { #[test] fn wallet_send_with_fee_rate_and_target_postage() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); - rpc_server.mine_blocks(1); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); - let (inscription, _) = inscribe(&rpc_server); + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new(format!( "wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription} --fee-rate 2.0 --postage 77000sat" )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - let tx = &rpc_server.mempool()[0]; + let tx = &bitcoin_rpc_server.mempool()[0]; let mut fee = 0; for input in &tx.input { - fee += rpc_server + fee += bitcoin_rpc_server .get_utxo_amount(&input.previous_output) .unwrap() .to_sat(); @@ -495,16 +570,20 @@ fn wallet_send_with_fee_rate_and_target_postage() { #[test] fn send_btc_does_not_send_locked_utxos() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - let coinbase_tx = &rpc_server.mine_blocks(1)[0].txdata[0]; + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + let coinbase_tx = &bitcoin_rpc_server.mine_blocks(1)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); - rpc_server.lock(outpoint); + bitcoin_rpc_server.lock(outpoint); CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex("error:.*") .run_and_extract_stdout(); @@ -512,19 +591,23 @@ fn send_btc_does_not_send_locked_utxos() { #[test] fn sending_rune_that_has_not_been_etched_is_an_error() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - let coinbase_tx = &rpc_server.mine_blocks(1)[0].txdata[0]; + let coinbase_tx = &bitcoin_rpc_server.mine_blocks(1)[0].txdata[0]; let outpoint = OutPoint::new(coinbase_tx.txid(), 0); - rpc_server.lock(outpoint); + bitcoin_rpc_server.lock(outpoint); CommandBuilder::new("--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: rune `FOO` has not been etched\n") .run_and_extract_stdout(); @@ -532,19 +615,23 @@ fn sending_rune_that_has_not_been_etched_is_an_error() { #[test] fn sending_rune_with_excessive_precision_is_an_error() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); - etch(&rpc_server, Rune(RUNE)); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1.1{}", Rune(RUNE) )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: excessive precision\n") .run_and_extract_stdout(); @@ -552,19 +639,23 @@ fn sending_rune_with_excessive_precision_is_an_error() { #[test] fn sending_rune_with_insufficient_balance_is_an_error() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - etch(&rpc_server, Rune(RUNE)); + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1001{}", Rune(RUNE) )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: insufficient `AAAAAAAAAAAAA` balance, only 1000\u{00A0}¢ in wallet\n") .run_and_extract_stdout(); @@ -572,25 +663,30 @@ fn sending_rune_with_insufficient_balance_is_an_error() { #[test] fn sending_rune_works() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - etch(&rpc_server, Rune(RUNE)); + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); let output = CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000{}", Rune(RUNE) )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let balances = CommandBuilder::new("--regtest --index-runes balances") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -600,7 +696,7 @@ fn sending_rune_works() { Rune(RUNE), vec![( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 2 }, 1000 @@ -616,24 +712,29 @@ fn sending_rune_works() { #[test] fn sending_spaced_rune_works() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); - etch(&rpc_server, Rune(RUNE)); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); let output = CommandBuilder::new( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000A•AAAAAAAAAAAA", ) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let balances = CommandBuilder::new("--regtest --index-runes balances") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -643,7 +744,7 @@ fn sending_spaced_rune_works() { Rune(RUNE), vec![( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 2 }, 1000 @@ -659,13 +760,16 @@ fn sending_spaced_rune_works() { #[test] fn sending_rune_with_divisibility_works() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let rune = Rune(RUNE); @@ -675,22 +779,25 @@ fn sending_rune_with_divisibility_works() { rune, ) ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 10.1{}", rune )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let balances = CommandBuilder::new("--regtest --index-runes balances") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -701,14 +808,14 @@ fn sending_rune_with_divisibility_works() { vec![ ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 1 }, 899 ), ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 2 }, 101 @@ -725,25 +832,30 @@ fn sending_rune_with_divisibility_works() { #[test] fn sending_rune_leaves_unspent_runes_in_wallet() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - etch(&rpc_server, Rune(RUNE)); + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); let output = CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 750{}", Rune(RUNE) )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let balances = CommandBuilder::new("--regtest --index-runes balances") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( @@ -754,14 +866,14 @@ fn sending_rune_leaves_unspent_runes_in_wallet() { vec![ ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 1 }, 250 ), ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 2 }, 750 @@ -775,13 +887,13 @@ fn sending_rune_leaves_unspent_runes_in_wallet() { } ); - let tx = rpc_server.tx(3, 1); + let tx = bitcoin_rpc_server.tx(3, 1); - assert_eq!(tx.txid(), output.transaction); + assert_eq!(tx.txid(), output.txid); let address = Address::from_script(&tx.output[1].script_pubkey, Network::Regtest).unwrap(); - assert!(rpc_server + assert!(bitcoin_rpc_server .change_addresses() .iter() .any(|change_address| change_address == &address)); @@ -789,45 +901,48 @@ fn sending_rune_leaves_unspent_runes_in_wallet() { #[test] fn sending_rune_creates_transaction_with_expected_runestone() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); - let rune = Rune(RUNE); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - etch(&rpc_server, rune); + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); let output = CommandBuilder::new(format!( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 750{}", - rune, + Rune(RUNE), )) - .rpc_server(&rpc_server) - .run_and_deserialize_output::(); + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let balances = CommandBuilder::new("--regtest --index-runes balances") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); assert_eq!( balances, ord::subcommand::balances::Output { runes: vec![( - rune, + Rune(RUNE), vec![ ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 1 }, 250 ), ( OutPoint { - txid: output.transaction, + txid: output.txid, vout: 2 }, 750 @@ -841,9 +956,9 @@ fn sending_rune_creates_transaction_with_expected_runestone() { } ); - let tx = rpc_server.tx(3, 1); + let tx = bitcoin_rpc_server.tx(3, 1); - assert_eq!(tx.txid(), output.transaction); + assert_eq!(tx.txid(), output.txid); assert_eq!( Runestone::from_transaction(&tx).unwrap(), @@ -860,30 +975,36 @@ fn sending_rune_creates_transaction_with_expected_runestone() { output: 2 }], burn: false, + claim: None, }, ); } #[test] fn error_messages_use_spaced_runes() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); - etch(&rpc_server, Rune(RUNE)); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); CommandBuilder::new( "--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1001A•AAAAAAAAAAAA", ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: insufficient `A•AAAAAAAAAAAA` balance, only 1000\u{00A0}¢ in wallet\n") .run_and_extract_stdout(); CommandBuilder::new("--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1F•OO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .expected_stderr("error: rune `FOO` has not been etched\n") .run_and_extract_stdout(); @@ -891,28 +1012,33 @@ fn error_messages_use_spaced_runes() { #[test] fn sending_rune_does_not_send_inscription() { - let rpc_server = test_bitcoincore_rpc::builder() + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() .network(Network::Regtest) .build(); - create_wallet(&rpc_server); + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); - rpc_server.mine_blocks_with_subsidy(1, 10000); + bitcoin_rpc_server.mine_blocks_with_subsidy(1, 10000); let rune = Rune(RUNE); CommandBuilder::new("--chain regtest --index-runes wallet inscribe --fee-rate 0 --file foo.txt") .write("foo.txt", "FOO") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks_with_subsidy(1, 10000); + bitcoin_rpc_server.mine_blocks_with_subsidy(1, 10000); assert_eq!( CommandBuilder::new("--regtest --index-runes wallet balance") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(), - ord::subcommand::wallet::balance::Output { + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(), + balance::Output { cardinal: 10000, ordinal: 10000, runic: Some(0), @@ -927,16 +1053,18 @@ fn sending_rune_does_not_send_inscription() { rune ) ) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(); - rpc_server.mine_blocks_with_subsidy(1, 0); + bitcoin_rpc_server.mine_blocks_with_subsidy(1, 0); assert_eq!( CommandBuilder::new("--regtest --index-runes wallet balance") - .rpc_server(&rpc_server) - .run_and_deserialize_output::(), - ord::subcommand::wallet::balance::Output { + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(), + balance::Output { cardinal: 0, ordinal: 10000, runic: Some(10000), @@ -949,8 +1077,46 @@ fn sending_rune_does_not_send_inscription() { "--chain regtest --index-runes wallet send --fee-rate 0 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000{}", rune )) - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) .stderr_regex("error:.*") .run_and_extract_stdout(); } + +#[test] +fn send_dry_run() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let (inscription, _) = inscribe(&bitcoin_rpc_server, &ord_rpc_server); + + bitcoin_rpc_server.mine_blocks(1); + + let output = CommandBuilder::new(format!( + "wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {inscription} --dry-run", + )) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert!(bitcoin_rpc_server.mempool().is_empty()); + assert_eq!( + Psbt::deserialize( + &base64::engine::general_purpose::STANDARD + .decode(output.psbt) + .unwrap() + ) + .unwrap() + .fee() + .unwrap() + .to_sat(), + output.fee + ); + assert_eq!(output.outgoing, Outgoing::InscriptionId(inscription)); +} diff --git a/tests/wallet/transactions.rs b/tests/wallet/transactions.rs index 8f3fd4ffc9..e728e58371 100644 --- a/tests/wallet/transactions.rs +++ b/tests/wallet/transactions.rs @@ -2,22 +2,26 @@ use {super::*, ord::subcommand::wallet::transactions::Output}; #[test] fn transactions() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); - assert!(rpc_server.loaded_wallets().is_empty()); + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + assert!(bitcoin_rpc_server.loaded_wallets().is_empty()); CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); - assert_eq!(rpc_server.loaded_wallets().len(), 1); - assert_eq!(rpc_server.loaded_wallets().first().unwrap(), "ord"); + assert_eq!(bitcoin_rpc_server.loaded_wallets().len(), 1); + assert_eq!(bitcoin_rpc_server.loaded_wallets().first().unwrap(), "ord"); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_regex_match!(output[0].transaction.to_string(), "[[:xdigit:]]{64}"); @@ -26,34 +30,40 @@ fn transactions() { #[test] fn transactions_with_limit() { - let rpc_server = test_bitcoincore_rpc::spawn(); - create_wallet(&rpc_server); + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + let ord_rpc_server = TestServer::spawn_with_server_args(&bitcoin_rpc_server, &[], &[]); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .stdout_regex(".*") .run_and_extract_stdout(); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_regex_match!(output[0].transaction.to_string(), "[[:xdigit:]]{64}"); assert_eq!(output[0].confirmations, 1); - rpc_server.mine_blocks(1); + bitcoin_rpc_server.mine_blocks(1); let output = CommandBuilder::new("wallet transactions") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_regex_match!(output[1].transaction.to_string(), "[[:xdigit:]]{64}"); assert_eq!(output[1].confirmations, 2); let output = CommandBuilder::new("wallet transactions --limit 1") - .rpc_server(&rpc_server) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::>(); assert_regex_match!(output[0].transaction.to_string(), "[[:xdigit:]]{64}");