Commit Graph

3891 Commits

Author SHA1 Message Date
Andrew Poelstra 04715e3e60
absolute: make is_* methods uniform with the ones from relative 2024-03-21 22:10:17 +00:00
Andrew Poelstra 878b865f85
relative locktime: introduce is_* methods to check units
Copy these from absolute::LockTime. While we are at it, make the
functions in absolute::LockTime const.
2024-03-21 22:10:17 +00:00
Andrew Poelstra c2f87c7ab3
relative locktime: add is_implied_by method for sequences
This gives a way to determine whether a CSV will pass, given a sequence
number, in a type-safe way where you can't get the two things backward.
2024-03-21 22:10:17 +00:00
Andrew Poelstra 319e102fed
relative locktime: use From/TryFrom to convert between relative locktimes and Sequence 2024-03-21 22:10:17 +00:00
Andrew Poelstra 0ed26915f6
relative locktime: add conversions to/from sequence 2024-03-21 22:10:17 +00:00
Andrew Poelstra 5c8fb5c11b
relative locktime: add consensus encode/decode functions 2024-03-21 22:10:13 +00:00
Andrew Poelstra ac968e02b6
relative locktime: constify a bunch of constructors 2024-03-21 22:07:29 +00:00
Andrew Poelstra f27e675e1e
relative locktime: add "obvious" constructors
Adds constructors to allow directly creating locktimes from time or
block counts; adds a flooring constructor to Time to match the ceiling
one; adds an explicit constructor to Height since the From<u16> was not
very discoverable.
2024-03-21 22:07:25 +00:00
Andrew Poelstra f02b1dac5b
relative locktime: copy comments and PartialOrd impl from absolute locktimes 2024-03-21 22:07:25 +00:00
Andrew Poelstra 2ff5085e70
locktimes: run cargo fmt 2024-03-21 22:07:25 +00:00
Tobin C. Harding dec05b63e9
Refactor witness_version and is_witness_program
These two functions are related. We cannot, by definition, get the
witness version from a script that is not a witness program but
currently the code is not linking these two things.

Refactor by doing:

- Move the check of the witness program bip rules to `witness_version`
- Call `witness_version().is_some()` in the predicate

Improve the docs while we are at it to include the bip text in the
rustdoc. Note I didn't bother referencing the segwit bip number, this
bip text is pretty well known.
2024-03-22 07:07:07 +11:00
Tobin C. Harding dac552b436
Add unit tests for shortest/longest witness program
Add two unit tests that verify we can correctly determine if a
shortest allowed and longest allowed script is a witness program.

Done in preparation for patching the `witness_version` function.
2024-03-22 07:03:30 +11:00
Tobin C. Harding 0ca5a43ce5
hashes: Bump version to v0.14.0
In preparation for release add a changlelog entry and bump the version.

I'm not 100% sure that this release is API breaking, dependencies
definitely changed. The rest might be only additives but I didn't bother
looking exactly because I think its better to bump the minor version and
err on the side of caution.

Note the hashes 0.13.0 dependency stays in the dependency graph because
of secp, we can update secp after releasing `hashes` then update the
secp dependency in `rust-bitcoin` thereby removing the `hashes v0.13.0`
dependency - phew.
2024-03-22 06:04:40 +11:00
Andrew Poelstra 7c910d5612
Merge rust-bitcoin/rust-bitcoin#2622: fuzz: Use path in manifest instead of version
d38cb8af9e fuzz: Use path in manifest instead of version (Tobin C. Harding)

Pull request description:

  Using `path` instead of `version` makes the `fuzz` crate easier to maintain because we don't have to update the version number to do releases.

ACKs for top commit:
  apoelstra:
    ACK d38cb8af9e neat, I did not know you could do this

Tree-SHA512: dd3d4526e6599c0752aef07393238b381b166d0d5b436a4babe967c6b282aa47079583b36ecea7a17279b1de135591950c07c9d6f2ba8e50d349bc62ca77f5c6
2024-03-21 14:55:12 +00:00
Tobin C. Harding d38cb8af9e
fuzz: Use path in manifest instead of version
Using `path` instead of `version` makes the `fuzz` crate easier to
maintain because we don't have to update the version number to do
releases.
2024-03-21 16:20:03 +11:00
Andrew Poelstra 2b9568158b
Merge rust-bitcoin/rust-bitcoin#2617: Revert #2525 (filter CI)
50e772fe79 Revert "ci: introduce `classify-pr.sh` script which determines whether a PR should have CI run" (Andrew Poelstra)
ae381fcc01 Revert "ci: gate CI workflow on source being changed" (Andrew Poelstra)
495d7e8acd Revert "ci: gate fuzztesting on whether source code changed" (Andrew Poelstra)
ec3e4e8801 Revert "ci: gate coverage analysis on whether source code changed" (Andrew Poelstra)

Pull request description:

  This PR did not work on the "master" CI runs and I really don't care enough to figure it out (or even how to test it). Just revert it.

ACKs for top commit:
  tcharding:
    ACK 50e772fe79

Tree-SHA512: c56b77dab93e9917b2420f5b7f178bfa05500f8329852296102d72df33c5de87d45ee79f235a4ce30e629bc79b2b290a2fcb97d7808c406bfe07c9fe7e1cc997
2024-03-20 22:13:33 +00:00
Tobin C. Harding af49841433
Hide base58::Error internals
As is convention here in `rust-bitcoin`, hide the `base58::Error`
internals by adding struct error types.
2024-03-21 06:22:51 +11:00
Tobin C. Harding 4f68e79da0
bitcoin: Stop using base58 errors
We are currently using the `base58::Error` type to create errors in
`bitcoin`, these are bitcoin errors not `base58` errors.

Note that we add what looks like duplicate
`InvalidBase58PayloadLengthError` types but they are different because
of the expected length. This could have been a field but I elected not
to do so for two reasons:

1. We will need to do so anyways if we crate smash more
2. The `crypto::key` one can have one of two values 33 or 34.

With this applied we can remove the now unused error variants from
`base58::Error`.
2024-03-21 06:22:51 +11:00
Tobin C. Harding 669d5e8fc6
base58: Add InvalidCharacterError for decoding
The `base58::decode` function can only return a single error type, add a
`InvalidCharacterError` struct (leaf error) to use as the return type.
2024-03-21 06:22:51 +11:00
Tobin C. Harding ec8609393b
base58: Add error module
In preparation for improving the `base58` error types crate an `error`
module and move the single current error type there. Make the module
public and reexport the type.
2024-03-21 06:22:51 +11:00
Tobin C. Harding 42fabbab03
base58: Run the formatter 2024-03-21 06:22:49 +11:00
Andrew Poelstra 6ff850539a
Merge rust-bitcoin/rust-bitcoin#2503: base58: Re-name crate to base58ck
6b09857f55 base58: Re-name crate to base58ck (Tobin C. Harding)

Pull request description:

  The current name `base58check` is taken, as is `base58`. Use `base58ck` instead.

  Add a brief section to the readme about the crate naming.

ACKs for top commit:
  apoelstra:
    ACK 6b09857f55
  sanket1729:
    ACK 6b09857f55

Tree-SHA512: 86ee08105906a6f3403dc2602e827b0d46226798ecdedb420ad3ac4b657d6a00e25eabcdfbdb9f8e89bdc3a38e608189f1e073e65593f89a2ad853e8ff027f69
2024-03-20 18:56:22 +00:00
Andrew Poelstra 4d90e0b2c4
Merge rust-bitcoin/rust-bitcoin#2528: hash_types: add unit tests for display of all hash types in the library
b816c0bb01 hash_types: add unit tests for display of all hash types in the library (Andrew Poelstra)

Pull request description:

  This can be checked against the 0.29.x branch, and against the commit prior to #1659 (40c246743b^) and you will see that it is consistent EXCEPT:

  * In rust-bitcoin 0.29.x we did not have multiple sighash types, only `Sighash`; we now have `LegacySighash`, `SegwitV0Sighash`, and `TapSighash`.
  * In #1565 we deliberately changed the display direction of the sighashes, to match BIP 143.

  Fixes #2495.

ACKs for top commit:
  tcharding:
    That's a win. ACK b816c0bb01

Tree-SHA512: 5b44f40165699910ea9ba945657cd1f960cf00a0b4dfa44c513feb3b74cda33ed80d3551042c15b85d6e57c30a54242db202eefd9ec8c8b6e1498b5578e52800
2024-03-20 16:33:58 +00:00
Andrew Poelstra 50e772fe79
Revert "ci: introduce `classify-pr.sh` script which determines whether a PR should have CI run"
This reverts commit 9aca8a18c7.
2024-03-20 14:08:17 +00:00
Andrew Poelstra ae381fcc01
Revert "ci: gate CI workflow on source being changed"
This reverts commit 09f7fc3cff.
2024-03-20 14:08:16 +00:00
Andrew Poelstra 495d7e8acd
Revert "ci: gate fuzztesting on whether source code changed"
This reverts commit 2203c02347.
2024-03-20 14:08:15 +00:00
Andrew Poelstra ec3e4e8801
Revert "ci: gate coverage analysis on whether source code changed"
This reverts commit 32f9b1a231.
2024-03-20 14:08:14 +00:00
Andrew Poelstra f495bd9681
Merge rust-bitcoin/rust-bitcoin#2525: ci: skip CI for things that modify only markdown files
32f9b1a231 ci: gate coverage analysis on whether source code changed (Andrew Poelstra)
2203c02347 ci: gate fuzztesting on whether source code changed (Andrew Poelstra)
09f7fc3cff ci: gate CI workflow on source being changed (Andrew Poelstra)
9aca8a18c7 ci: introduce `classify-pr.sh` script which determines whether a PR should have CI run (Andrew Poelstra)

Pull request description:

  Fixes #2523

ACKs for top commit:
  tcharding:
    ACK 32f9b1a231

Tree-SHA512: 7d53365bdf4e8ae5480221e43b4c515d4b16048ec1a00cb62ab65b2d2b91a37f8945c39b619a82bfe4ca74f3508176d9879481b908bb8d3fe8d214b173d3bf18
2024-03-20 14:01:32 +00:00
Andrew Poelstra 59a58fdca8
Merge rust-bitcoin/rust-bitcoin#2518: Re-export `address::error::P2shError` from `address` module
0d517dcfdd Re-export P2shError (Tobin C. Harding)
646ee1a837 Put re-exports in alphabetic order (Tobin C. Harding)

Pull request description:

  As with the rest of the errors in `address::error` that are returned by a pubic function from the `address` module.

  Note please, this PR just makes the `address/mod.rs` file uniform, debating the merit of the re-exports is out of scope.

ACKs for top commit:
  apoelstra:
    ACK 0d517dcfdd

Tree-SHA512: a65ebe6de62b83c6d3723c7c97a0953ddb8da51964ef54e69865855502ae5fa51b2b49c6fd408c452414b56518f4c7ab763faca925e8d81f0fe806af1df92aa2
2024-03-20 13:43:52 +00:00
Andrew Poelstra 51bb6a2d69
Merge rust-bitcoin/rust-bitcoin#2582: fix: Manually implement serde traits
911f8cbd6a fix: Manually implement serde traits (Tobin C. Harding)

Pull request description:

  Currently we are deriving the serde traits for the `absolute::{Height, Time}` types, this is incorrect because we maintain an invariant on the inner `u32` of both types that it is above or below the threshold.

  Manually implement the serde traits and pass the deserialized `u32` to `from_consensus` to maintain the invariant.

  Close: #2559

ACKs for top commit:
  apoelstra:
    ACK 911f8cbd6a
  sanket1729:
    ACK 911f8cbd6a.

Tree-SHA512: 28f9f3e23b2016e00216e5e94f35e22a5bfa6b29e1dd30fa3351575eafc2e47dbd98aa8e51631de2f09eec8d881b0e5e79231185c7a293c1ba0170a90fbbd19d
2024-03-20 13:28:59 +00:00
Tobin C. Harding 911f8cbd6a
fix: Manually implement serde traits
Currently we are deriving the serde traits for the `absolute::{Height,
Time}` types, this is incorrect because we maintain an invariant on
the inner `u32` of both types that it is above or below the threshold.

Manually implement the serde traits and pass the deserialized `u32` to
`from_consensus` to maintain the invariant.

Close: #2559
2024-03-20 11:33:14 +11:00
Andrew Poelstra 6389d3f7fc
Merge rust-bitcoin/rust-bitcoin#2577: Fix cargo cult programming
290e4418e6 units: Fix cargo cult programming (Tobin C. Harding)

Pull request description:

  When creating the ParseIntError in `hex_u32` I (tobin) just cargo cult programmed the generic stuff without thinking.

  - The `is_signed` field is used to denote whether we were attempting to parse a signed or unsigned integer, it should be `false`.
  - The `bits` field should be directly set to 32.

ACKs for top commit:
  apoelstra:
    ACK 290e4418e6
  sanket1729:
    ACK 290e4418e6

Tree-SHA512: 7dfd9f0cd98eff1c2b27a92dac5c4e2fe0fa4ae724528ef741fe43d8d923e2d31cbdcd4e540ecfba1b953860dc2b728a958e756e2d2012d9a9e715c0ca3c5068
2024-03-19 15:50:33 +00:00
Andrew Poelstra 002590be70
Merge rust-bitcoin/rust-bitcoin#2607: merkle_block: add resource limit check during deserialization
f1dcfab293 merkle_block: add resource limit check during deserialization (Andrew Poelstra)

Pull request description:

  Fixes #2606

ACKs for top commit:
  tcharding:
    ACK f1dcfab293
  sanket1729:
    ACK f1dcfab293

Tree-SHA512: f0ab0f7a7bd7f38741149263a2c321b0ec9f09d299be96c5174c4e21edd383fc2824574a790331769dee1f06e3ad1dd53601921eb82038e08235b797d68a37fd
2024-03-19 15:31:29 +00:00
Tobin C. Harding 290e4418e6
units: Fix cargo cult programming
When creating the ParseIntError in `hex_u32` I (Tobin) just cargo cult
programmed the generic stuff without thinking.

- The `is_signed` field is used to denote whether we were attempting to
parse a signed or unsigned integer, it should be `false`.
- The `bits` field should be directly set to 32.
2024-03-19 09:59:11 +11:00
Tobin C. Harding 6b09857f55
base58: Re-name crate to base58ck
The current name `base58check` is taken, as is `base58`. Use `base58ck`
instead.

Add a brief section to the readme about the crate naming.
2024-03-19 09:00:46 +11:00
Andrew Poelstra f1dcfab293
merkle_block: add resource limit check during deserialization
Fixes #2606
2024-03-17 22:30:20 +00:00
Andrew Poelstra 7b7461c5d9
Merge rust-bitcoin/rust-bitcoin#2039: Add a `consensus::deserialize_hex` function
16a813734c Implement consensus deserialize_hex (Tobin C. Harding)

Pull request description:

  We have `serialize_hex` and `deserialize` but no `deserialize_hex`, add it.

  Move the `IterReader` out of `consensus::serde` to the `consensus` module.

  Add some additional logic to the `DecodeError`, I'm not sure why this wasn't there before?

  Use the `HexSliceToBytesIter` by way of the `IterReader` to deserialize an arbitrary hex string. Add unit tests to check that we consume all bytes when deserializing a fixed size object (a transaction).

ACKs for top commit:
  apoelstra:
    ACK 16a813734c
  sanket1729:
    ACK 16a813734c

Tree-SHA512: 121285cb328ca01bf9fd2a716e6d625fa93113a11613d44c576e3e49a9d06dc181165d2d9bfb9beea7c3d2aff264f64ade4965acd594b05ce0d1660e7493d2e4
2024-03-17 20:19:02 +00:00
Andrew Poelstra 5269adfc62
Merge rust-bitcoin/rust-bitcoin#2596: Release tracking PR: `io v0.1.2`
13ec770f10 io: Bump version to 0.1.2 (Tobin C. Harding)

Pull request description:

  There have only been two PRs merged that touched the `io` crate since it as last released. The changes are additive so we can do a pre-0.1 point release.

  In preparation for release bump the version and add a changelog entry.

ACKs for top commit:
  apoelstra:
    ACK 13ec770f10
  sanket1729:
    utACK 13ec770f10

Tree-SHA512: 319edb80cedffe796df798ba5fc640953ccfde12295d0bf5b7987ab69b1b677108a7351d4d1d335e4136801589503666172891d2b6ee73ed8be58ddafc9547fe
2024-03-17 18:04:12 +00:00
Andrew Poelstra 93ca42cb4d
Merge rust-bitcoin/rust-bitcoin#2604: Enforce displaying Amount with trailing zeros
d887423efc Enforce displaying Amount with trailing zeros (448 OG)

Pull request description:

  It is common to display bitcoins using trailing zeros upto 8 decimals. This commit enforces:
  - Displaying Amount in BTC with trailing zeroes by eight decimal places if a precision on the Amount is not specified.
  - Displaying Amount in BTC upto the precision specified truncating the insignificant zeros.
  - Displaying amount in BTC without any decimals if the remainder of the amount divided by the satoshis in 1 BTC is equal to zero using formula `satoshis.rem_euclid(Amount::ONE_BTC.to_sat()) != 0`

  These are not breaking changes and all previous tests pass.

  A testcase is added to for changes introduced.

  Resolves: #2136

ACKs for top commit:
  sanket1729:
    ACK d887423efc
  apoelstra:
    ACK d887423efc

Tree-SHA512: c32e41216477f60a8d95f164bf4a1f6644ea14adc7e169743ce419b0f26ecb6603f3a516f9b18d6508c04ce658f6a0a78ff3b0b062aeb7101b28bbb1e9d522bc
2024-03-17 17:50:50 +00:00
448 OG d887423efc
Enforce displaying Amount with trailing zeros
It is common to display bitcoins using trailing zeros upto 8 decimals.
This commit enforces:
- Displaying Amount in BTC with trailing zeroes by eight decimal places
  if a precision on the Amount is not specified.
- Displaying Amount in BTC upto the precision specified truncating the insignificant zeros.
- Displaying amount in BTC without any decimals if the remainder of the amount
  divided by the satoshis in 1 BTC is equal to zero using formula `satoshis.rem_euclid(Amount::ONE_BTC.to_sat()) != 0`

These are not breaking changes and all previous tests pass.

A testcase is added to for changes introduced.

Resolves: #2136
2024-03-17 17:55:59 +03:00
Andrew Poelstra 37ba219631
Merge rust-bitcoin/rust-bitcoin#2603: fix: small typo in SECURITY.md
438c000316 fix: small typo in SECURITY.md (Jose Storopoli)

Pull request description:

  Just caught a small typo in SECURITY.md

ACKs for top commit:
  apoelstra:
    ACK 438c000316

Tree-SHA512: d2bcc4d5d02b531cebaa8290c2055360753ef06102fee7c16a0345dbecd1769a79e820b7139e7fc60a0d931d7a35b6fb5e7bac2ee32dc4eb55460b05b7e22064
2024-03-16 10:50:06 +00:00
Jose Storopoli 438c000316
fix: small typo in SECURITY.md 2024-03-16 06:10:47 -03:00
Andrew Poelstra 750b4dfb8b
Merge rust-bitcoin/rust-bitcoin#2569: Move types to `units`
cbee9781e8 Move unit types to units (Tobin C. Harding)
5bd0d7194b Remove unused absolute::Error (Tobin C. Harding)

Pull request description:

  Move the following unit types to the new `units` crate:

  - `locktime::absolute::{Height, Time}`
  - `locktime::relative::{Height, Time}`
  - `FeeRate`
  - `Weight`

  Also move the `parse` module as well as constants as required.

  Do minimal changes to get things building:

  - Feature gate on "alloc" as needed.
  - Remove rustdocs that use `bitcoin` types.
  - Re-export units types so this is a non-breaking change.
  - Fix import paths.

  Patch 1 was originally #2526, putting it in via this PR to try and speed up the process.

  Close: #2282

ACKs for top commit:
  sanket1729:
    ACK cbee9781e8
  apoelstra:
    ACK cbee9781e8 lgtm. this is a good start. I think the LockTime types should follow Height and Time

Tree-SHA512: 6b0d63c7b054008598d7fa81be7d8c112f2778883b5529d79d446617b94b3c196c9ac735f840d1dfb488700894d3161c6976d44ab0e12ac3af4008068eac5f87
2024-03-15 22:43:51 +00:00
Andrew Poelstra d1c5eca24f
Merge rust-bitcoin/rust-bitcoin#2600: CI: Disable MSAN job
097a00b133 CI: Disable MSAN job (Tobin C. Harding)

Pull request description:

  I believe there is currently a bug in the MemorySanitizer, when we pass various types across the FFI boundry MSAN gives a unititialized variable error:

  - `usize` passed as `size_t` (cannot be uninitialized)
  - byte slice passed as `const char *`

  In order to let other work continue disable the MSAN job.

  The issue is further described in #2579

ACKs for top commit:
  apoelstra:
    ACK 097a00b133

Tree-SHA512: 0252ef0bd21afd55e878e495be1182d5b45b54e931dca9eb2e111731acd889cb5f0eb38b670b239cfb8511af5bf2145875b8853bd919d46bc278de12cda93414
2024-03-15 09:04:30 +00:00
Andrew Poelstra 42b5a6a26e
Merge rust-bitcoin/rust-bitcoin#2563: Added more tests for PublicKey::from_str
0d64ae6eb4 Added tests for PublicKey::from_str (Sh0g0-1758)

Pull request description:

  Fixes: #2550

  Added some new tests and refactored some older tests.

ACKs for top commit:
  sanket1729:
    ACK 0d64ae6eb4
  apoelstra:
    ACK 0d64ae6eb4 thanks for bearing with me!
  tcharding:
    ACK 0d64ae6eb4

Tree-SHA512: b6792590c56ccac8e8cf6f182e74cb77c4652c537c0357456ff21a7814ebcc8cf48e0fad4c8d47e6e786a50e2cbb48134cb64406bcc900b4fcad9304d9cf4167
2024-03-15 08:07:18 +00:00
Andrew Poelstra bf4783db47
Merge rust-bitcoin/rust-bitcoin#2458: Support signing taproot in psbt
41e8fb0863 Support signing taproot in psbt (yu)

Pull request description:

  Hi team, I'm from Keystone Wallet team. currently rust-bitcoin does not support signing taproot transactions in psbt.
  We think this founction should be included in the psbt module, we submit this PR. Some context and discussion about this PR can be found here: #2418.

  For this PR, mostly two new functions are introduced:

  - `bip32_sign_schnorr`:  sign a taproot input.
  - `sighash_taproot`: calculate the sighash message to sign a taproot input along with the sighash type.

  Looking forward to your feedback.

ACKs for top commit:
  tcharding:
    ACK 41e8fb0863
  sanket1729:
    ACK 41e8fb0863.

Tree-SHA512: 2eb14a3204e6ed848515483778dd7986662aacb332783d187da72d29e207b78a2d427939f2b958135a32de5459221385e6f1f5bae89f491b58d8bc79f202b724
2024-03-15 07:52:27 +00:00
Tobin C. Harding 097a00b133
CI: Disable MSAN job
I believe there is currently a bug in the MemorySanitizer, when we pass
various types across the FFI boundry MSAN gives a unititialized variable
error:

- `usize` passed as `size_t` (cannot be uninitialized)
- byte slice passed as `const char *`

In order to let other work continue disable the MSAN job.
2024-03-15 09:44:38 +11:00
Tobin C. Harding 13ec770f10
io: Bump version to 0.1.2
There have only been two PRs merged that touched the `io` crate since it
as last released. The changes are additive so we can do a pre-0.1 point
release.

In preparation for release bump the version and add a changelog entry.
2024-03-15 06:25:55 +11:00
Andrew Poelstra e0d58a9c1c
Merge rust-bitcoin/rust-bitcoin#2576: Return error when constructing pubkey from slice
6ecc41d126 Return error when constructing pubkey from slice (Tobin C. Harding)

Pull request description:

  This PR fixes a bug introduced by me in #2473, and uncovered by #2563 - amazing that it was found so quickly!

  Constructing a pubkey using `PublicKey::from_slice` can fail for reasons other than just incorrect length - we should not be using `expect` but rather returning the error.

  A purist might argue that we are now returning a nested error type with an unreachable variant:

    `ParsePublicKeyError::Encoding(FromSliceError::InvalidLength)`

  Is this acceptable or do we want to further improve this?

ACKs for top commit:
  sanket1729:
    ACK 6ecc41d126
  apoelstra:
    ACK 6ecc41d126

Tree-SHA512: ae8299b21c4787a104f98533105308e8e7678cd5a29b78c30012982d741c05ba5f2bb1edd1d61d3a5ce028235d18c1511e1f94207479bc19e88cfec7a7ca1737
2024-03-14 07:19:12 +00:00
Tobin C. Harding 8bd0394b0a
Document how to write commits
Reviewers often find themselves linking to blog posts to encourage newer
devs to improve their commit logs, we can save everyones time by putting
the links in the contributing docs, then we can just point devs there.
2024-03-14 13:21:26 +11:00