diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f84b6cf16a..9088573c13 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -352,6 +352,23 @@ jobs: name: nextest runs-on: ubicloud-standard-30 timeout-minutes: 60 + + services: + postgres: + image: postgres:latest + env: + POSTGRES_DB: clementine + POSTGRES_USER: clementine + POSTGRES_PASSWORD: clementine + POSTGRES_INITDB_ARGS: "-c shared_buffers=8GB -c max_connections=5000" + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: - uses: actions/checkout@v5 - uses: rui314/setup-mold@v1 diff --git a/Cargo.lock b/Cargo.lock index 64b124627f..87a9b8180c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,10 +102,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239e728d663a3bdababa24dfdc697faec987593161c5ff54d72ee01df6721d59" dependencies = [ "alloy-consensus 0.13.0", + "alloy-contract 0.13.0", "alloy-core 0.8.23", "alloy-eips 0.13.0", + "alloy-genesis 0.13.0", "alloy-network 0.13.0", "alloy-provider 0.13.0", + "alloy-pubsub", "alloy-rpc-client 0.13.0", "alloy-rpc-types 0.13.0", "alloy-serde 0.13.0", @@ -113,6 +116,8 @@ dependencies = [ "alloy-signer-local 0.13.0", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", + "alloy-transport-ipc", + "alloy-transport-ws", ] [[package]] @@ -122,7 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0093d23bf026b580c1f66ed3a053d8209c104a446c5264d3ad99587f6edef24e" dependencies = [ "alloy-consensus 1.0.9", - "alloy-contract", + "alloy-contract 1.0.9", "alloy-core 1.1.2", "alloy-eips 1.0.9", "alloy-network 1.0.9", @@ -237,6 +242,28 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f39e8b96c9e25dde7222372332489075f7e750e4fd3e81c11eec0939b78b71b8" +dependencies = [ + "alloy-consensus 0.13.0", + "alloy-dyn-abi 0.8.26", + "alloy-json-abi 0.8.26", + "alloy-network 0.13.0", + "alloy-network-primitives 0.13.0", + "alloy-primitives 0.8.26", + "alloy-provider 0.13.0", + "alloy-pubsub", + "alloy-rpc-types-eth 0.13.0", + "alloy-sol-types 0.8.26", + "alloy-transport 0.13.0", + "futures", + "futures-util", + "thiserror 2.0.12", +] + [[package]] name = "alloy-contract" version = "1.0.9" @@ -264,7 +291,11 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca1380cc3c81b83d5234865779494970c83b5893b423c59cdd68c3cd1ed0b671" dependencies = [ + "alloy-dyn-abi 0.8.26", + "alloy-json-abi 0.8.26", "alloy-primitives 0.8.26", + "alloy-rlp", + "alloy-sol-types 0.8.26", ] [[package]] @@ -720,12 +751,19 @@ dependencies = [ "alloy-network 0.13.0", "alloy-network-primitives 0.13.0", "alloy-primitives 0.8.26", + "alloy-pubsub", "alloy-rpc-client 0.13.0", + "alloy-rpc-types-anvil 0.13.0", + "alloy-rpc-types-debug", "alloy-rpc-types-eth 0.13.0", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", "alloy-signer 0.13.0", "alloy-sol-types 0.8.26", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", + "alloy-transport-ipc", + "alloy-transport-ws", "async-stream", "async-trait", "auto_impl", @@ -736,6 +774,7 @@ dependencies = [ "lru 0.13.0", "parking_lot", "pin-project", + "reqwest", "serde", "serde_json", "thiserror 2.0.12", @@ -786,6 +825,26 @@ dependencies = [ "wasmtimer", ] +[[package]] +name = "alloy-pubsub" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a261caff6c2ec6fe1d6eb77ba41159024c8387d05e4138804a387d403def55" +dependencies = [ + "alloy-json-rpc 0.13.0", + "alloy-primitives 0.8.26", + "alloy-transport 0.13.0", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tracing", +] + [[package]] name = "alloy-rlp" version = "0.3.11" @@ -816,11 +875,15 @@ checksum = "cec6dc89c4c3ef166f9fa436d1831f8142c16cf2e637647c936a6aaaabd8d898" dependencies = [ "alloy-json-rpc 0.13.0", "alloy-primitives 0.8.26", + "alloy-pubsub", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", + "alloy-transport-ipc", + "alloy-transport-ws", "async-stream", "futures", "pin-project", + "reqwest", "serde", "serde_json", "tokio", @@ -864,8 +927,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3849f8131a18cc5d7f95f301d68a6af5aa2db28ad8522fb9db1f27b3794e8b68" dependencies = [ "alloy-primitives 0.8.26", + "alloy-rpc-types-anvil 0.13.0", "alloy-rpc-types-engine", "alloy-rpc-types-eth 0.13.0", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", "alloy-serde 0.13.0", "serde", ] @@ -1357,6 +1423,7 @@ dependencies = [ "http-body-util", "hyper 1.6.0", "hyper-util", + "reqwest", "serde_json", "tower 0.5.2", "tracing", @@ -1378,6 +1445,44 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-transport-ipc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c6f8e20aa6b748357bed157c14e561a176d0f6cffed7f99ee37758a7d16202" +dependencies = [ + "alloy-json-rpc 0.13.0", + "alloy-pubsub", + "alloy-transport 0.13.0", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef7a4301e8967c1998f193755fd9429e0ca81730e2e134e30c288c43dbf96f0" +dependencies = [ + "alloy-pubsub", + "alloy-transport 0.13.0", + "futures", + "http 1.2.0", + "rustls 0.23.25", + "serde_json", + "tokio", + "tokio-tungstenite 0.26.2", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "alloy-trie" version = "0.7.9" @@ -2129,6 +2234,26 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -2615,6 +2740,53 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "backoff" version = "0.4.0" @@ -2742,6 +2914,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -2824,11 +3002,12 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin" -version = "0.32.5" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", + "base64 0.21.7", "bech32 0.11.0", "bitcoin-internals", "bitcoin-io", @@ -2852,6 +3031,7 @@ dependencies = [ "borsh", "citrea-common", "citrea-primitives", + "clementine-tx-sender", "crypto-bigint 0.5.5", "futures", "hex", @@ -2913,7 +3093,7 @@ dependencies = [ [[package]] name = "bitcoincore-rpc" version = "0.18.0" -source = "git+https://github.com/chainwayxyz/rust-bitcoincore-rpc.git?rev=a6e011a#a6e011abec5e345c6474dd47a0b58fd93d486193" +source = "git+https://github.com/chainwayxyz/rust-bitcoincore-rpc.git?rev=13fb11651212868bb1581146305770654b909637#13fb11651212868bb1581146305770654b909637" dependencies = [ "async-trait", "bitcoincore-rpc-json", @@ -2928,7 +3108,7 @@ dependencies = [ [[package]] name = "bitcoincore-rpc-json" version = "0.18.0" -source = "git+https://github.com/chainwayxyz/rust-bitcoincore-rpc.git?rev=a6e011a#a6e011abec5e345c6474dd47a0b58fd93d486193" +source = "git+https://github.com/chainwayxyz/rust-bitcoincore-rpc.git?rev=13fb11651212868bb1581146305770654b909637#13fb11651212868bb1581146305770654b909637" dependencies = [ "bitcoin", "serde", @@ -3301,7 +3481,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.24.0", "tracing", "tracing-subscriber 0.3.20", "url", @@ -4132,6 +4312,121 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clementine-config" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "bitcoin", + "clementine-errors", + "eyre", + "serde", + "toml", +] + +[[package]] +name = "clementine-errors" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "alloy 0.13.0", + "bitcoin", + "clementine-primitives", + "eyre", + "hex", + "http 1.2.0", + "reqwest", + "sqlx", + "thiserror 1.0.69", + "tonic", +] + +[[package]] +name = "clementine-extended-rpc" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "async-trait", + "bitcoin", + "bitcoincore-rpc", + "clementine-errors", + "clementine-primitives", + "eyre", + "http 1.2.0", + "reqwest", + "secrecy", + "serde", + "serde_json", + "tokio", + "tokio-retry", + "tracing", +] + +[[package]] +name = "clementine-primitives" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "bitcoin", + "eyre", + "hex", + "lazy_static", + "secp256k1 0.31.1", + "serde", + "sqlx", +] + +[[package]] +name = "clementine-tx-sender" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "async-trait", + "bitcoin", + "bitcoincore-rpc", + "borsh", + "clementine-config", + "clementine-errors", + "clementine-extended-rpc", + "clementine-primitives", + "clementine-utils", + "eyre", + "hex", + "jsonrpsee", + "log", + "reqwest", + "secrecy", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx", + "tempfile", + "tokio", + "tokio-retry", + "tracing", + "tracing-subscriber 0.3.20", + "tx-sender-jsonrpc-client", + "tx-sender-types", +] + +[[package]] +name = "clementine-utils" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "bitcoin", + "clementine-errors", + "clementine-primitives", + "color-eyre", + "eyre", + "secp256k1 0.31.1", + "serde", + "sqlx", + "tracing", + "tracing-subscriber 0.3.20", + "tx-sender-types", +] + [[package]] name = "cmake" version = "0.1.54" @@ -4147,6 +4442,33 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -4427,6 +4749,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -4867,6 +5198,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -5138,12 +5481,23 @@ dependencies = [ ] [[package]] -name = "ethereum-rpc" -version = "1.2.2" +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "alloy-consensus 0.13.0", - "alloy-network 0.13.0", - "alloy-primitives 0.8.26", + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "ethereum-rpc" +version = "1.2.2" +dependencies = [ + "alloy-consensus 0.13.0", + "alloy-network 0.13.0", + "alloy-primitives 0.8.26", "alloy-rpc-types 0.13.0", "alloy-rpc-types-eth 0.13.0", "alloy-rpc-types-trace", @@ -5385,6 +5739,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -5521,6 +5886,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -5570,7 +5946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers 0.2.6", - "send_wrapper", + "send_wrapper 0.4.0", ] [[package]] @@ -5923,9 +6299,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -6265,6 +6641,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -6663,6 +7052,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "interprocess" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "intrusive-collections" version = "0.9.7" @@ -7330,9 +7734,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.32.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -7491,6 +7895,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maybe-async" version = "0.2.10" @@ -8183,6 +8593,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + [[package]] name = "p256" version = "0.11.1" @@ -8324,6 +8740,16 @@ dependencies = [ "indexmap 2.7.1", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + [[package]] name = "phf" version = "0.11.3" @@ -8918,6 +9344,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.9" @@ -11183,14 +11615,14 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rusqlite" -version = "0.34.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ "bitflags 2.8.0", "fallible-iterator", "fallible-streaming-iterator", - "hashlink 0.10.0", + "hashlink 0.9.1", "libsqlite3-sys", "smallvec", ] @@ -11580,7 +12012,7 @@ checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] @@ -11592,7 +12024,19 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.0", + "secp256k1-sys 0.11.0", "serde", ] @@ -11605,6 +12049,25 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -11674,6 +12137,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.218" @@ -12358,6 +12827,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -12385,6 +12857,190 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.4.0", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink 0.10.0", + "indexmap 2.7.1", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.98", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-postgres", + "syn 2.0.98", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.8.0", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.8.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.12", + "tracing", + "url", +] + [[package]] name = "stability" version = "0.2.1" @@ -12419,6 +13075,17 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -12811,6 +13478,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -12854,7 +13532,23 @@ dependencies = [ "native-tls", "tokio", "tokio-native-tls", - "tungstenite", + "tungstenite 0.24.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.25", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tungstenite 0.26.2", + "webpki-roots", ] [[package]] @@ -12906,6 +13600,38 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.8", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile 2.2.0", + "socket2", + "tokio", + "tokio-rustls 0.26.1", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -13030,6 +13756,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber 0.3.20", +] + [[package]] name = "tracing-futures" version = "0.2.5" @@ -13181,6 +13917,46 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand 0.9.0", + "rustls 0.23.25", + "rustls-pki-types", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + +[[package]] +name = "tx-sender-jsonrpc-client" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "bitcoin", + "jsonrpsee", + "tx-sender-types", +] + +[[package]] +name = "tx-sender-types" +version = "0.6.4" +source = "git+https://github.com/chainwayxyz/clementine?rev=3d7c59f81b6e27fcd7ae468d1f759f9138f72e92#3d7c59f81b6e27fcd7ae468d1f759f9138f72e92" +dependencies = [ + "bitcoin", + "clementine-primitives", + "serde", + "sqlx", +] + [[package]] name = "typenum" version = "1.18.0" @@ -13235,12 +14011,33 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -13472,6 +14269,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -13620,6 +14423,16 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "widestring" version = "1.2.0" @@ -14147,6 +14960,25 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper 0.6.0", + "thiserror 2.0.12", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index d9fa3c450b..d95311bc2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,8 +173,12 @@ alloy-signer-local = { version = "0.13.0", default-features = false } citrea-e2e = { git = "https://github.com/chainwayxyz/citrea-e2e", rev = "10ff3e2" } +clementine-tx-sender = { git = "https://github.com/chainwayxyz/clementine", package = "clementine-tx-sender", rev = "3d7c59f81b6e27fcd7ae468d1f759f9138f72e92", features = ["citrea", "standalone", "testing"] } + +tx-sender-jsonrpc-client = { git = "https://github.com/chainwayxyz/clementine", package = "tx-sender-jsonrpc-client", rev = "3d7c59f81b6e27fcd7ae468d1f759f9138f72e92", features = ["citrea"] } + [patch.crates-io] -bitcoincore-rpc = { version = "0.18.0", git = "https://github.com/chainwayxyz/rust-bitcoincore-rpc.git", rev = "a6e011a" } +bitcoincore-rpc = { version = "0.18.0", git = "https://github.com/chainwayxyz/rust-bitcoincore-rpc.git", rev = "13fb11651212868bb1581146305770654b909637" } # Use our fork of revm-inspectors to apply fixes after v0.18.1 # Cannot update to latest as it requires latest revm, which we cannot use yet revm-inspectors = { git = "https://github.com/chainwayxyz/revm-inspectors", rev = "574d614ce505d3c0c4534d8b8e62d8acb3fa9651" } diff --git a/crates/bitcoin-da/Cargo.toml b/crates/bitcoin-da/Cargo.toml index 06938ffb67..b1c1b2b459 100644 --- a/crates/bitcoin-da/Cargo.toml +++ b/crates/bitcoin-da/Cargo.toml @@ -20,6 +20,7 @@ async-trait = { workspace = true, optional = true } backoff = { workspace = true, optional = true } bitcoin = { workspace = true } borsh = { workspace = true } +clementine-tx-sender = { workspace = true, optional = true, features = ["citrea", "standalone", "testing"] } crypto-bigint = { workspace = true } futures = { workspace = true, optional = true } hex = { workspace = true, features = ["serde"] } @@ -63,6 +64,7 @@ native = [ "dep:reqwest", "dep:jsonrpsee", "dep:secp256k1", + "dep:clementine-tx-sender", ] testing = [] diff --git a/crates/bitcoin-da/src/fee.rs b/crates/bitcoin-da/src/fee.rs index edb35c525b..189d1a0b5b 100644 --- a/crates/bitcoin-da/src/fee.rs +++ b/crates/bitcoin-da/src/fee.rs @@ -175,6 +175,7 @@ impl FeeService { txid: utxo.tx_id, vout: utxo.vout, sequence: Some(Sequence::ENABLE_RBF_NO_LOCKTIME.to_consensus_u32()), + weight: None, }], &outputs, None, diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index 221e6f7a5f..93b635bcc8 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -26,6 +26,12 @@ use borsh::BorshDeserialize; use citrea_common::utils::read_env; use citrea_primitives::compression::{compress_blob, decompress_blob}; use citrea_primitives::{MAX_COMPRESSED_BLOB_SIZE, MAX_TX_BODY_SIZE}; +use clementine_tx_sender::citrea::CitreaTxRequest; +use clementine_tx_sender::config::TxSenderBitcoinRpcConfig; +use clementine_tx_sender::jsonrpc::client::JsonRpcTxSenderClient; +use clementine_tx_sender::task::spawn_txsender_loop_with_free_localhost_jsonrpc_port; +use clementine_tx_sender::DEFAULT_FINALITY_DEPTH; +use itertools::Itertools; use lru::LruCache; use reth_tasks::shutdown::GracefulShutdown; use serde::{Deserialize, Serialize}; @@ -156,11 +162,13 @@ pub struct BitcoinService { tx_queue: Arc>>, pub(crate) tx_signer: TxSigner, pub(crate) utxo_manager: UtxoManager, + pub(crate) tx_sender_client: Option, } impl BitcoinService { #[allow(clippy::too_many_arguments)] - fn new( + async fn new( + btc_config: &BitcoinServiceConfig, client: Arc, network: bitcoin::Network, network_constants: NetworkConstants, @@ -173,6 +181,31 @@ impl BitcoinService { tx_queue: Arc>>, utxo_manager: UtxoManager, ) -> Self { + let tx_sender_client = match da_private_key { + Some(da_private_key) => { + let (mut config, _, _) = + clementine_tx_sender::test_utils::create_test_environment(true, false).await; + config.network = network; + config.secret_key = SecretKey::new(&mut bitcoin::secp256k1::rand::thread_rng()); + config.private_da_key = Some(da_private_key); + config.bitcoin_rpc = TxSenderBitcoinRpcConfig { + url: btc_config.node_url.clone(), + user: btc_config.node_username.clone().into(), + password: btc_config.node_password.clone().into(), + }; + config.finality_depth = DEFAULT_FINALITY_DEPTH; + config.include_unsafe = true; + let (txsender_addr, _handle) = + spawn_txsender_loop_with_free_localhost_jsonrpc_port(config); + let url = format!("http://{txsender_addr}"); + Some(JsonRpcTxSenderClient::new(&url).expect("Failed to create tx sender client")) + } + None => { + tracing::warn!("No DA private key provided in config, not using txsender"); + None + } + }; + Self { tx_signer: TxSigner::new(client.clone()), client, @@ -189,6 +222,7 @@ impl BitcoinService { ))), tx_queue, utxo_manager, + tx_sender_client, } } @@ -238,6 +272,7 @@ impl BitcoinService { ); Ok(Self::new( + config, client, network, network_constants, @@ -249,7 +284,8 @@ impl BitcoinService { tx_backup_dir.to_path_buf(), tx_queue, utxo_manager, - )) + ) + .await) } /// Run the task to process the DA commands from the queue. @@ -261,8 +297,6 @@ impl BitcoinService { mut shutdown: GracefulShutdown, ) { trace!("BitcoinDA queue is initialized. Waiting for the first request..."); - let mut fee_rate_multiplier = self.fee.base_fee_rate_multiplier(); - loop { select! { biased; @@ -281,51 +315,29 @@ impl BitcoinService { request_opt = rx.recv() => { if let Some(request) = request_opt { trace!("A new request is received"); - - loop { - // Build and queue tx with retries: - let fee_sat_per_vbyte = match self.fee.get_fee_rate().await { - Ok(rate) => rate * fee_rate_multiplier, - Err(e) => { - error!(?e, "Failed to call get_fee_rate. Retrying..."); - tokio::time::sleep(Duration::from_secs(1)).await; - continue; - } - }; - match self - .send_transaction_with_fee_rate( - request.tx_request.clone(), - fee_sat_per_vbyte, - ) - .await - { - Ok(txs) => { - let txid = txs.last().unwrap()[1].id; - let tx_id = TxidWrapper(txid); - info!(%txid, "Sent tx to BitcoinDA"); - let _ = request.notify.send(Ok(tx_id)); - - fee_rate_multiplier = self.fee.base_fee_rate_multiplier(); - } - Err(e) => { - error!(?e, "Failed to send transaction to DA layer"); - tokio::time::sleep(Duration::from_secs(1)).await; - - match e { - BitcoinServiceError::MempoolRejection(MempoolRejection::MinRelayFeeNotMet) | BitcoinServiceError::FeeCalculation(_) => { - fee_rate_multiplier = self.fee.get_next_fee_rate_multiplier(fee_rate_multiplier); - }, - BitcoinServiceError::QueueNotEmpty => { - let _ = self.process_transaction_queue().await; - }, - _ => {} - } - - continue; - } + let citrea_rx_req = match request.tx_request { + DaTxRequest::ZKProof(zkproof) => { + let compressed_proof = + compress_blob(&zkproof).expect("Compression should not fail"); + CitreaTxRequest::BatchProof { bytes: compressed_proof, chunk_size: None } + }, + DaTxRequest::SequencerCommitment(comm) => { + let data = DataOnDa::SequencerCommitment(comm); + let blob = borsh::to_vec(&data).expect("DataOnDa serialize must not fail"); + CitreaTxRequest::SequencerCommitment(blob) } - break; - } + DaTxRequest::BatchProofMethodId(method_id) => { + let data = DataOnDa::BatchProofMethodId(method_id); + let blob = borsh::to_vec(&data).expect("DataOnDa serialize must not fail"); + CitreaTxRequest::BatchProofMethodId(blob) + } + }; + + let _id = self.tx_sender_client.as_ref().expect("No da key given so no txsender exists").send_citrea_tx(citrea_rx_req).await.expect("Failed to send transaction to txsender"); + + let txid = Txid::all_zeros(); + let tx_id = TxidWrapper(txid); + let _ = request.notify.send(Ok(tx_id)); } } } @@ -1388,6 +1400,7 @@ pub(crate) fn split_proof(zk_proof: Proof) -> Result { for chunk in original_compressed.chunks(MAX_TX_BODY_SIZE) { let data = DataOnDa::Chunk(chunk.to_vec()); let blob = borsh::to_vec(&data).expect("zk::Proof Chunk serialize must not fail"); + chunks.push(blob) } diff --git a/crates/sovereign-sdk/adapters/mock-da/Cargo.toml b/crates/sovereign-sdk/adapters/mock-da/Cargo.toml index 8f89e5c0b0..0aa4fe41fb 100644 --- a/crates/sovereign-sdk/adapters/mock-da/Cargo.toml +++ b/crates/sovereign-sdk/adapters/mock-da/Cargo.toml @@ -19,7 +19,7 @@ serde = { workspace = true } hex = { workspace = true } sha2 = { workspace = true } tokio = { workspace = true, optional = true } -rusqlite = { version = "0.34.0", features = ["bundled"], optional = true } +rusqlite = { version = "0.32.1", features = ["bundled"], optional = true } serde_json = { workspace = true, optional = true } tracing = { workspace = true, optional = true, features = ["attributes"]}