Merge pull request #493 from logos-blockchain/arjentix/bridge-deposit

feat: Implement deposit operation for bridge
This commit is contained in:
Daniil Polyakov 2026-05-29 23:13:33 +03:00 committed by GitHub
commit d3390efc6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 1349 additions and 261 deletions

View File

@ -16,6 +16,7 @@ ignore = [
{ id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2024-0370", reason = "transitive dependency of `logos-blockchain-http-api-common`, can't do anything than wait for upstream fix" },
]
yanked = "deny"
unused-ignored-advisory = "deny"

390
Cargo.lock generated
View File

@ -733,8 +733,6 @@ checksum = "86887daca11d02e0b04f37a9cb81888aae881397fb48ff66494e356aea97554a"
dependencies = [
"itertools 0.10.5",
"lazy_static",
"rand 0.8.5",
"serde",
]
[[package]]
@ -1056,7 +1054,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
dependencies = [
"fastrand",
"gloo-timers 0.3.0",
"tokio",
]
@ -1319,6 +1316,14 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "bridge_core"
version = "0.1.0"
dependencies = [
"nssa_core",
"serde",
]
[[package]]
name = "bs58"
version = "0.5.1"
@ -2181,7 +2186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
dependencies = [
"data-encoding",
"syn 2.0.117",
"syn 1.0.109",
]
[[package]]
@ -2665,7 +2670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -2847,6 +2852,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -2854,7 +2868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared",
"foreign-types-shared 0.3.1",
]
[[package]]
@ -2868,6 +2882,12 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@ -2997,7 +3017,7 @@ version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
dependencies = [
"gloo-timers 0.2.6",
"gloo-timers",
"send_wrapper 0.4.0",
]
@ -3167,18 +3187,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gloo-timers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "gloo-utils"
version = "0.2.0"
@ -3638,6 +3646,22 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
@ -3655,10 +3679,12 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.5.10",
"socket2 0.6.3",
"system-configuration 0.7.0",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@ -4071,6 +4097,8 @@ dependencies = [
"anyhow",
"ata_core",
"authenticated_transfer_core",
"borsh",
"bridge_core",
"bytesize",
"common",
"faucet_core",
@ -4080,8 +4108,11 @@ dependencies = [
"indexer_service_rpc",
"key_protocol",
"log",
"logos-blockchain-core",
"logos-blockchain-http-api-common",
"nssa",
"nssa_core",
"reqwest",
"sequencer_core",
"sequencer_service_rpc",
"serde_json",
@ -5283,7 +5314,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "logos-blockchain-blend-crypto"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"blake2",
"logos-blockchain-groth16",
@ -5291,13 +5322,13 @@ dependencies = [
"logos-blockchain-poseidon2",
"logos-blockchain-utils",
"rs-merkle-tree",
"thiserror 1.0.69",
"thiserror 2.0.18",
]
[[package]]
name = "logos-blockchain-blend-message"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"blake2",
"derivative",
@ -5308,11 +5339,12 @@ dependencies = [
"logos-blockchain-core",
"logos-blockchain-groth16",
"logos-blockchain-key-management-system-keys",
"logos-blockchain-log-targets",
"logos-blockchain-utils",
"serde",
"serde-big-array",
"serde_with",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tracing",
"zeroize",
]
@ -5320,7 +5352,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-blend-proofs"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ed25519-dalek",
"generic-array 1.3.5",
@ -5329,17 +5361,18 @@ dependencies = [
"logos-blockchain-groth16",
"logos-blockchain-pol",
"logos-blockchain-poq",
"logos-blockchain-poseidon2",
"logos-blockchain-utils",
"num-bigint 0.4.6",
"serde",
"thiserror 1.0.69",
"thiserror 2.0.18",
"zeroize",
]
[[package]]
name = "logos-blockchain-chain-broadcast-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"derivative",
@ -5355,7 +5388,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-chain-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"bytes",
@ -5377,7 +5410,8 @@ dependencies = [
"serde",
"serde_with",
"strum",
"thiserror 1.0.69",
"thiserror 2.0.18",
"time",
"tokio",
"tracing",
"tracing-futures",
@ -5386,7 +5420,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-prover"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-circuits-utils",
"tempfile",
@ -5395,7 +5429,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-circuits-utils"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"dirs",
]
@ -5403,10 +5437,11 @@ dependencies = [
[[package]]
name = "logos-blockchain-common-http-client"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"futures",
"hex",
"log",
"logos-blockchain-chain-broadcast-service",
"logos-blockchain-chain-service",
"logos-blockchain-core",
@ -5416,14 +5451,15 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tokio-util",
"url",
]
[[package]]
name = "logos-blockchain-core"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ark-ff 0.4.2",
"bincode",
@ -5448,20 +5484,21 @@ dependencies = [
"rpds",
"serde",
"strum",
"thiserror 1.0.69",
"thiserror 2.0.18",
"time",
"tracing",
]
[[package]]
name = "logos-blockchain-cryptarchia-engine"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-pol",
"logos-blockchain-utils",
"serde",
"serde_with",
"thiserror 1.0.69",
"thiserror 2.0.18",
"time",
"tokio",
"tracing",
@ -5470,7 +5507,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-cryptarchia-sync"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"bytes",
"futures",
@ -5481,7 +5518,7 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_with",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tokio",
"tracing",
]
@ -5489,7 +5526,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-groth16"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ark-bn254 0.4.0",
"ark-ec 0.4.2",
@ -5507,7 +5544,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-http-api-common"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"axum 0.7.9",
"logos-blockchain-core",
@ -5515,14 +5552,19 @@ dependencies = [
"logos-blockchain-tracing",
"serde",
"serde_json",
"serde_urlencoded",
"serde_with",
"time",
"tracing",
"url",
"utoipa",
"validator",
]
[[package]]
name = "logos-blockchain-key-management-system-keys"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"bytes",
@ -5548,7 +5590,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-key-management-system-macros"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"proc-macro2",
"quote",
@ -5558,7 +5600,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-key-management-system-operators"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"logos-blockchain-blend-proofs",
@ -5574,7 +5616,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-key-management-system-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"log",
@ -5591,7 +5633,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-ledger"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"derivative",
"logos-blockchain-blend-crypto",
@ -5610,14 +5652,14 @@ dependencies = [
"rpds",
"serde",
"serde_arrays",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "logos-blockchain-libp2p"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"backon",
@ -5628,6 +5670,7 @@ dependencies = [
"igd-next 0.16.2",
"libp2p",
"logos-blockchain-cryptarchia-sync",
"logos-blockchain-log-targets",
"logos-blockchain-utils",
"multiaddr",
"natpmp",
@ -5636,16 +5679,34 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_with",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tokio",
"tracing",
"zerocopy",
]
[[package]]
name = "logos-blockchain-log-targets"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-log-targets-macros",
]
[[package]]
name = "logos-blockchain-log-targets-macros"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "logos-blockchain-mmr"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ark-ff 0.4.2",
"logos-blockchain-groth16",
@ -5658,13 +5719,14 @@ dependencies = [
[[package]]
name = "logos-blockchain-network-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"futures",
"logos-blockchain-core",
"logos-blockchain-cryptarchia-sync",
"logos-blockchain-libp2p",
"logos-blockchain-log-targets",
"logos-blockchain-tracing",
"overwatch",
"rand 0.8.5",
@ -5678,47 +5740,48 @@ dependencies = [
[[package]]
name = "logos-blockchain-poc"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-circuits-prover",
"logos-blockchain-circuits-utils",
"logos-blockchain-groth16",
"logos-blockchain-proofs-error",
"logos-blockchain-witness-generator",
"num-bigint 0.4.6",
"serde",
"serde_json",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "logos-blockchain-pol"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"astro-float",
"logos-blockchain-circuits-prover",
"logos-blockchain-circuits-utils",
"logos-blockchain-groth16",
"logos-blockchain-proofs-error",
"logos-blockchain-utils",
"logos-blockchain-witness-generator",
"num-bigint 0.4.6",
"num-traits",
"serde",
"serde_json",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "logos-blockchain-poq"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-circuits-prover",
"logos-blockchain-circuits-utils",
"logos-blockchain-groth16",
"logos-blockchain-pol",
"logos-blockchain-proofs-error",
"logos-blockchain-witness-generator",
"num-bigint 0.4.6",
"serde",
@ -5730,7 +5793,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-poseidon2"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ark-bn254 0.4.0",
"ark-ff 0.4.2",
@ -5738,10 +5801,20 @@ dependencies = [
"num-bigint 0.4.6",
]
[[package]]
name = "logos-blockchain-proofs-error"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-groth16",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "logos-blockchain-services-utils"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"futures",
@ -5749,24 +5822,25 @@ dependencies = [
"overwatch",
"serde",
"serde_json",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "logos-blockchain-storage-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"bytes",
"futures",
"logos-blockchain-core",
"logos-blockchain-cryptarchia-engine",
"logos-blockchain-log-targets",
"logos-blockchain-tracing",
"overwatch",
"serde",
"thiserror 1.0.69",
"thiserror 2.0.18",
"tokio",
"tracing",
]
@ -5774,12 +5848,13 @@ dependencies = [
[[package]]
name = "logos-blockchain-time-service"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"futures",
"log",
"logos-blockchain-cryptarchia-engine",
"logos-blockchain-log-targets",
"logos-blockchain-tracing",
"logos-blockchain-utils",
"overwatch",
@ -5796,8 +5871,10 @@ dependencies = [
[[package]]
name = "logos-blockchain-tracing"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"flate2",
"logos-blockchain-log-targets",
"opentelemetry",
"opentelemetry-appender-tracing",
"opentelemetry-http",
@ -5820,7 +5897,7 @@ dependencies = [
[[package]]
name = "logos-blockchain-utils"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"blake2",
@ -5831,13 +5908,15 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_with",
"serde_yaml",
"thiserror 2.0.18",
"time",
]
[[package]]
name = "logos-blockchain-utxotree"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"ark-ff 0.4.2",
"logos-blockchain-groth16",
@ -5845,13 +5924,13 @@ dependencies = [
"num-bigint 0.4.6",
"rpds",
"serde",
"thiserror 1.0.69",
"thiserror 2.0.18",
]
[[package]]
name = "logos-blockchain-witness-generator"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"tempfile",
]
@ -5859,12 +5938,13 @@ dependencies = [
[[package]]
name = "logos-blockchain-zksign"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"logos-blockchain-circuits-prover",
"logos-blockchain-circuits-utils",
"logos-blockchain-groth16",
"logos-blockchain-poseidon2",
"logos-blockchain-proofs-error",
"logos-blockchain-witness-generator",
"num-bigint 0.4.6",
"serde",
@ -5876,13 +5956,15 @@ dependencies = [
[[package]]
name = "logos-blockchain-zone-sdk"
version = "0.1.2"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf"
source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=dd055cc1ef7c130f710a52a190edd97bc7b0f71b#dd055cc1ef7c130f710a52a190edd97bc7b0f71b"
dependencies = [
"async-trait",
"futures",
"hex",
"logos-blockchain-common-http-client",
"logos-blockchain-core",
"logos-blockchain-groth16",
"logos-blockchain-http-api-common",
"logos-blockchain-key-management-system-service",
"rand 0.8.5",
"reqwest",
@ -6114,7 +6196,7 @@ dependencies = [
"bitflags 2.11.0",
"block",
"core-graphics-types",
"foreign-types",
"foreign-types 0.5.0",
"log",
"objc",
"paste",
@ -6253,6 +6335,23 @@ dependencies = [
"unsigned-varint 0.7.2",
]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "natpmp"
version = "0.5.0"
@ -6460,6 +6559,7 @@ dependencies = [
"anyhow",
"authenticated_transfer_core",
"borsh",
"bridge_core",
"clock_core",
"env_logger",
"faucet_core",
@ -6718,12 +6818,49 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "opentelemetry"
version = "0.31.0"
@ -7157,6 +7294,30 @@ dependencies = [
"toml_edit 0.25.4+spec-1.1.0",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@ -7241,6 +7402,7 @@ dependencies = [
"ata_core",
"ata_program",
"authenticated_transfer_core",
"bridge_core",
"clock_core",
"faucet_core",
"nssa_core",
@ -7820,6 +7982,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
@ -7828,9 +7991,12 @@ dependencies = [
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"quinn",
@ -7841,6 +8007,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower",
@ -8418,7 +8585,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -8686,11 +8853,13 @@ version = "0.1.0"
dependencies = [
"anyhow",
"borsh",
"bridge_core",
"bytesize",
"chrono",
"common",
"faucet_core",
"futures",
"hex",
"humantime-serde",
"log",
"logos-blockchain-core",
@ -9249,6 +9418,12 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "symlink"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
[[package]]
name = "syn"
version = "1.0.109"
@ -9395,7 +9570,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -9700,6 +9875,16 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
@ -9962,11 +10147,12 @@ dependencies = [
[[package]]
name = "tracing-appender"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
dependencies = [
"crossbeam-channel",
"symlink",
"thiserror 2.0.18",
"time",
"tracing-subscriber 0.3.23",
@ -10380,6 +10566,30 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utoipa"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
"indexmap 2.13.0",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "uuid"
version = "1.22.0"
@ -10391,6 +10601,36 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "validator"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
dependencies = [
"darling 0.20.11",
"once_cell",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "valuable"
version = "0.1.1"
@ -10746,7 +10986,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]

View File

@ -22,6 +22,7 @@ members = [
"programs/associated_token_account",
"programs/authenticated_transfer/core",
"programs/faucet/core",
"programs/bridge/core",
"programs/vault/core",
"sequencer/core",
"sequencer/service",
@ -75,6 +76,7 @@ ata_core = { path = "programs/associated_token_account/core" }
ata_program = { path = "programs/associated_token_account" }
authenticated_transfer_core = { path = "programs/authenticated_transfer/core" }
faucet_core = { path = "programs/faucet/core" }
bridge_core = { path = "programs/bridge/core" }
vault_core = { path = "programs/vault/core" }
test_program_methods = { path = "test_program_methods" }
testnet_initial_state = { path = "testnet_initial_state" }
@ -135,12 +137,13 @@ schemars = "1.2"
async-stream = "0.3.6"
criterion = { version = "0.8", features = ["html_reports"] }
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
logos-blockchain-http-api-common = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "dd055cc1ef7c130f710a52a190edd97bc7b0f71b" }
rocksdb = { version = "0.24.0", default-features = false, features = [
"snappy",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -67,7 +67,9 @@ cryptarchia:
- opcode: 17
payload:
channel_id: '0000000000000000000000000000000000000000000000000000000000000000'
inscription: '67656e65736973'
# chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8),
# genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32]
inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000'
parent: '0000000000000000000000000000000000000000000000000000000000000000'
signer: '0000000000000000000000000000000000000000000000000000000000000000'
execution_gas_price: 0

View File

@ -1,7 +1,7 @@
services:
logos-blockchain-node-0:
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:9f1829dea335c56f6ff68ae37ea872ed5313b96b69e8ffe143c02b7217de85fc
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:f160cfbf898a06554451cc066d84cfd0f8ab62d59bd3e62d9cde3bd5582c12ab
ports:
- "${PORT:-8080}:18080/tcp"
volumes:

View File

@ -67,7 +67,7 @@ impl NSSATransaction {
}
/// Validates the transaction against the current state and returns the resulting diff
/// without applying it. Rejects transactions that modify clock or faucet system accounts,
/// without applying it. Rejects transactions that modify clock, faucet or bridge accounts,
/// whether directly or indirectly via chain calls.
///
/// This check is required for all user transactions. Only sequencer transactions may bypass
@ -90,26 +90,12 @@ impl NSSATransaction {
}
}?;
let public_diff = diff.public_diff();
let touches_clock = nssa::CLOCK_PROGRAM_ACCOUNT_IDS.iter().any(|id| {
public_diff
.get(id)
.is_some_and(|post| *post != state.get_account_by_id(*id))
});
if touches_clock {
return Err(nssa::error::NssaError::InvalidInput(
"Transaction modifies system clock accounts".into(),
));
}
let faucet_id = nssa::system_faucet_account_id();
if public_diff
.get(&faucet_id)
.is_some_and(|post| *post != state.get_account_by_id(faucet_id))
{
return Err(nssa::error::NssaError::InvalidInput(
"Transaction modifies system faucet account".into(),
));
let system_accounts = nssa::CLOCK_PROGRAM_ACCOUNT_IDS.iter().copied().chain([
nssa::system_faucet_account_id(),
nssa::system_bridge_account_id(),
]);
for account_id in system_accounts {
validate_doesnt_modify_account(state, &diff, account_id)?;
}
Ok(diff)
@ -184,3 +170,21 @@ pub fn clock_invocation(timestamp: clock_core::Instruction) -> nssa::PublicTrans
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
)
}
fn validate_doesnt_modify_account(
state: &V03State,
diff: &ValidatedStateDiff,
account_id: AccountId,
) -> Result<(), nssa::error::NssaError> {
if diff
.public_diff()
.get(&account_id)
.is_some_and(|post| *post != state.get_account_by_id(account_id))
{
Err(nssa::error::NssaError::InvalidInput(format!(
"Transaction modifies restricted system account {account_id}"
)))
} else {
Ok(())
}
}

View File

@ -15,6 +15,11 @@
},
"indexer_rpc_url": "ws://indexer_service:8779",
"genesis": [
{
"supply_bridge_account": {
"balance": 1000000
}
},
{
"supply_account": {
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",

View File

@ -6,7 +6,7 @@ use common::{
transaction::{NSSATransaction, clock_invocation},
};
use log::info;
use logos_blockchain_core::{header::HeaderId, mantle::ops::channel::MsgId};
use logos_blockchain_core::header::HeaderId;
use logos_blockchain_zone_sdk::Slot;
use nssa::{Account, AccountId, V03State};
use nssa_core::BlockId;
@ -97,16 +97,16 @@ impl IndexerStore {
Ok(self.dbio.calculate_state_for_id(block_id)?)
}
pub fn get_zone_cursor(&self) -> Result<Option<(MsgId, Slot)>> {
pub fn get_zone_cursor(&self) -> Result<Option<Slot>> {
let Some(bytes) = self.dbio.get_zone_sdk_indexer_cursor_bytes()? else {
return Ok(None);
};
let cursor: (MsgId, Slot) = serde_json::from_slice(&bytes)
let cursor: Slot = serde_json::from_slice(&bytes)
.context("Failed to deserialize stored zone-sdk indexer cursor")?;
Ok(Some(cursor))
}
pub fn set_zone_cursor(&self, cursor: &(MsgId, Slot)) -> Result<()> {
pub fn set_zone_cursor(&self, cursor: &Slot) -> Result<()> {
let bytes =
serde_json::to_vec(cursor).context("Failed to serialize zone-sdk indexer cursor")?;
self.dbio.put_zone_sdk_indexer_cursor_bytes(&bytes)?;

View File

@ -81,8 +81,8 @@ impl IndexerCore {
error!("Failed to deserialize L2 block from zone-sdk: {e}");
// Advance past the broken inscription so we don't
// re-process it on restart.
cursor = Some((zone_block.id, slot));
if let Err(err) = self.store.set_zone_cursor(&(zone_block.id, slot)) {
cursor = Some(slot);
if let Err(err) = self.store.set_zone_cursor(&slot) {
warn!("Failed to persist indexer cursor: {err:#}");
}
continue;
@ -98,8 +98,8 @@ impl IndexerCore {
error!("Failed to store block {}: {err:#}", block.header.block_id);
}
cursor = Some((zone_block.id, slot));
if let Err(err) = self.store.set_zone_cursor(&(zone_block.id, slot)) {
cursor = Some(slot);
if let Err(err) = self.store.set_zone_cursor(&slot) {
warn!("Failed to persist indexer cursor: {err:#}");
}
yield Ok(block);

View File

@ -22,6 +22,7 @@ token_core.workspace = true
ata_core.workspace = true
vault_core.workspace = true
faucet_core.workspace = true
bridge_core.workspace = true
indexer_service_rpc = { workspace = true, features = ["client"] }
sequencer_service_rpc = { workspace = true, features = ["client"] }
wallet-ffi.workspace = true
@ -34,3 +35,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
hex.workspace = true
tempfile.workspace = true
bytesize.workspace = true
reqwest.workspace = true
borsh.workspace = true
logos-blockchain-http-api-common.workspace = true
logos-blockchain-core.workspace = true

View File

@ -7,8 +7,14 @@ use integration_tests::{
public_mention, verify_commitment_is_in_state,
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use nssa::{
AccountId, SharedSecretKey, execute_and_prove,
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
};
use nssa_core::{
InputAccountIdentity, NullifierPublicKey, account::AccountWithMetadata,
encryption::shared_key_derivation::Secp256k1Point,
};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::{
@ -626,13 +632,7 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
}
#[test]
async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
use nssa::{
EphemeralPublicKey, SharedSecretKey, execute_and_prove,
privacy_preserving_transaction::{self, circuit::ProgramWithDependencies},
};
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
async fn ppt_cant_chain_call_faucet() -> Result<()> {
let ctx = TestContext::new().await?;
let binary = std::fs::read(
@ -656,7 +656,6 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
let npk = NullifierPublicKey::from(&nsk);
let vpk = Secp256k1Point::from_scalar([4; 32]);
let ssk = SharedSecretKey::new([55; 32], &vpk);
let epk = EphemeralPublicKey::from_scalar([55; 32]);
let attacker_vault_id = {
let seed = vault_core::compute_vault_seed(attacker_id);
AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337)
@ -695,7 +694,7 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
let instruction =
Program::serialize_instruction((faucet_program_id, vault_program_id, attacker_id, amount))?;
let (output, proof) = execute_and_prove(
let res = execute_and_prove(
vec![faucet_pre, vault_pda_pre],
instruction,
vec![
@ -708,47 +707,9 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
},
],
&program_with_deps,
)?;
);
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
vec![faucet_account_id],
vec![],
vec![(npk, vpk, epk)],
output,
)?;
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
let attack_ppt = NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new(
message,
witness_set,
));
let faucet_balance_before = ctx
.sequencer_client()
.get_account_balance(faucet_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(attacker_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_ppt).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let faucet_balance_after = ctx
.sequencer_client()
.get_account_balance(faucet_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(attacker_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(faucet_balance_after, faucet_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(tx_on_chain.is_none());
assert!(res.is_err());
Ok(())
}

View File

@ -420,7 +420,7 @@ async fn cannot_execute_faucet_program() -> Result<()> {
Program::faucet().id(),
vec![faucet_account_id, recipient_vault_id],
vec![],
faucet_core::Instruction::Transfer {
faucet_core::Instruction::GenesisTransferVault {
vault_program_id,
recipient_id: recipient,
amount,

View File

@ -0,0 +1,450 @@
#![expect(
clippy::tests_outside_test_module,
clippy::arithmetic_side_effects,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::Context as _;
use borsh::BorshSerialize;
use common::transaction::NSSATransaction;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext};
use log::info;
use logos_blockchain_core::mantle::{Value, ledger::Inputs, ops::channel::deposit::DepositOp};
use logos_blockchain_http_api_common::bodies::{
channel::ChannelDepositRequestBody,
wallet::{
balance::WalletBalanceResponseBody,
transfer_funds::{WalletTransferFundsRequestBody, WalletTransferFundsResponseBody},
},
};
use nssa::{
AccountId, execute_and_prove, privacy_preserving_transaction, program::Program,
public_transaction,
};
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
const TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK: Duration = Duration::from_mins(2);
#[test]
async fn public_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let bridge_account_id = nssa::system_bridge_account_id();
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let message = public_transaction::Message::try_new(
Program::bridge().id(),
vec![bridge_account_id, recipient_vault_id],
vec![],
bridge_core::Instruction::Deposit {
vault_program_id,
recipient_id,
amount: 1,
},
)
.context("Failed to build public bridge deposit transaction")?;
let attack_tx = NSSATransaction::Public(nssa::PublicTransaction::new(
message,
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
));
let bridge_balance_before = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let bridge_balance_after = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(bridge_balance_after, bridge_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(
tx_on_chain.is_none(),
"Direct public bridge::Deposit invocation should be rejected"
);
Ok(())
}
#[test]
async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let bridge_account_id = nssa::system_bridge_account_id();
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
// Get pre-state of bridge and vault accounts
let bridge_pre = AccountWithMetadata::new(
ctx.sequencer_client()
.get_account(bridge_account_id)
.await?,
false,
bridge_account_id,
);
let vault_pre = AccountWithMetadata::new(
ctx.sequencer_client()
.get_account(recipient_vault_id)
.await?,
false,
recipient_vault_id,
);
// Create program with dependencies
let program_with_deps =
nssa::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
Program::bridge(),
[
(vault_program_id, Program::vault()),
(
Program::authenticated_transfer_program().id(),
Program::authenticated_transfer_program(),
),
]
.into(),
);
// Serialize the bridge deposit instruction
let instruction = Program::serialize_instruction(bridge_core::Instruction::Deposit {
vault_program_id,
recipient_id,
amount: 1,
})
.context("Failed to serialize bridge deposit instruction")?;
// Execute and prove the bridge deposit
let (output, proof) = execute_and_prove(
vec![bridge_pre.clone(), vault_pre.clone()],
instruction,
vec![InputAccountIdentity::Public, InputAccountIdentity::Public],
&program_with_deps,
)
.context("Failed to execute/prove bridge deposit")?;
// Create privacy-preserving transaction from circuit output
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
vec![bridge_account_id, recipient_vault_id],
vec![bridge_pre.account.nonce, vault_pre.account.nonce],
vec![],
output,
)
.context("Failed to build privacy-preserving bridge deposit message")?;
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
let attack_tx = NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new(
message,
witness_set,
));
let bridge_balance_before = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let bridge_balance_after = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(bridge_balance_after, bridge_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(
tx_on_chain.is_none(),
"Privacy-preserving bridge::Deposit invocation should be rejected"
);
Ok(())
}
async fn submit_bedrock_deposit(
bedrock_addr: std::net::SocketAddr,
recipient_id: AccountId,
amount: u128,
) -> anyhow::Result<()> {
#[derive(BorshSerialize)]
struct DepositMetadata {
recipient_id: AccountId,
}
// Encode deposit metadata
let metadata = borsh::to_vec(&DepositMetadata { recipient_id })
.context("Failed to encode deposit metadata")?;
let funding_key = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26";
let amount: Value = amount
.try_into()
.context("Deposit amount does not fit Bedrock Value type")?;
let channel_id = integration_tests::config::bedrock_channel_id();
let client = reqwest::Client::new();
let query_balance = || async {
let balance_response = client
.get(format!(
"http://{bedrock_addr}/wallet/{funding_key}/balance"
))
.send()
.await
.context("Failed to query Bedrock wallet balance")?;
let balance_response = check_response_success(balance_response).await?;
balance_response
.json::<WalletBalanceResponseBody>()
.await
.context("Failed to decode Bedrock balance response")
};
let mut balance = query_balance().await?;
info!(
"Queried Bedrock balance for key {funding_key}: {:?}",
balance.balance
);
if balance.balance < amount {
anyhow::bail!(
"Bedrock wallet with key {funding_key} has insufficient balance {:?} for deposit amount {:?}",
balance.balance,
amount
);
}
let mut selected_note_id = balance
.notes
.iter()
.find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
if selected_note_id.is_none() {
let transfer_body = WalletTransferFundsRequestBody {
tip: None,
change_public_key: balance.address,
funding_public_keys: vec![balance.address],
recipient_public_key: balance.address,
amount,
};
let transfer_response = client
.post(format!(
"http://{bedrock_addr}/wallet/transactions/transfer-funds"
))
.json(&transfer_body)
.send()
.await
.context("Failed to submit Bedrock transfer-funds request")?;
let transfer_response = check_response_success(transfer_response).await?;
let transfer: WalletTransferFundsResponseBody = transfer_response
.json()
.await
.context("Failed to decode Bedrock transfer-funds response")?;
info!(
"Submitted transfer-funds to create exact deposit note, tx hash {:?}",
transfer.hash
);
let mut found_note = None;
for _ in 0..20 {
tokio::time::sleep(Duration::from_millis(500)).await;
balance = query_balance().await?;
found_note = balance
.notes
.iter()
.find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
if found_note.is_some() {
break;
}
}
selected_note_id = found_note;
}
let Some(selected_note_id) = selected_note_id else {
anyhow::bail!(
"Failed to locate exact-value note {amount:?} for Bedrock deposit; available notes: {:?}",
balance.notes,
);
};
let body = ChannelDepositRequestBody {
tip: None,
deposit: DepositOp {
channel_id,
inputs: Inputs::new(vec![selected_note_id]),
metadata,
},
change_public_key: balance.address,
funding_public_keys: vec![balance.address],
max_tx_fee: 1_000_u64.into(),
};
let response = client
.post(format!("http://{bedrock_addr}/channel/deposit"))
.json(&body)
.send()
.await
.context("Failed to submit Bedrock deposit request")?;
let response = check_response_success(response).await?;
let body_text = response
.text()
.await
.unwrap_or_else(|_| "<failed to decode>".to_owned());
info!(
"Successfully submitted Bedrock deposit request for recipient {recipient_id} and amount {amount}, response body: {body_text}",
);
Ok(())
}
async fn check_response_success(response: reqwest::Response) -> anyhow::Result<reqwest::Response> {
if response.status().is_success() {
Ok(response)
} else {
let status = response.status();
let body_text = response.text().await.unwrap_or_default();
anyhow::bail!("Request failed with status {status} and body {body_text}");
}
}
async fn wait_for_vault_balance(
ctx: &TestContext,
vault_id: AccountId,
expected_balance: u128,
) -> anyhow::Result<()> {
let timeout = TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK
+ Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS);
tokio::time::timeout(timeout, async {
loop {
let balance = ctx.sequencer_client().get_account_balance(vault_id).await?;
if balance == expected_balance {
return Ok(());
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
})
.await
.with_context(|| {
format!("Timed out waiting for vault {vault_id} balance to reach {expected_balance}")
})?
}
#[test]
async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let recipient_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_id)
.await?;
// Submit deposit to Bedrock
submit_bedrock_deposit(ctx.bedrock_addr(), recipient_id, 1).await?;
// Wait for vault to receive the deposit (minted from bridge to vault)
wait_for_vault_balance(&ctx, recipient_vault_id, vault_balance_before + 1).await?;
// Now claim funds from vault back to recipient
let nonces = ctx
.wallet()
.get_accounts_nonces(vec![recipient_id])
.await
.context("Failed to get nonce for vault claim")?;
let signing_key = ctx
.wallet()
.storage()
.key_chain()
.pub_account_signing_key(recipient_id)
.with_context(|| format!("Missing signing key for account {recipient_id}"))?;
let claim_message = public_transaction::Message::try_new(
vault_program_id,
vec![recipient_id, recipient_vault_id],
nonces,
vault_core::Instruction::Claim { amount: 1 },
)
.context("Failed to build vault claim message")?;
let claim_witness_set =
public_transaction::WitnessSet::for_message(&claim_message, &[signing_key]);
let claim_tx = NSSATransaction::Public(nssa::PublicTransaction::new(
claim_message,
claim_witness_set,
));
let claim_hash = ctx.sequencer_client().send_transaction(claim_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let claim_on_chain = ctx.sequencer_client().get_transaction(claim_hash).await?;
let vault_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let recipient_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient_id)
.await?;
assert!(
claim_on_chain.is_some(),
"Vault claim transaction must be included on-chain"
);
assert_eq!(
vault_balance_after_claim, vault_balance_before,
"Vault balance should return to initial state after claim"
);
assert_eq!(
recipient_balance_after_claim,
recipient_balance_before + 1,
"Recipient balance should increase by claimed amount"
);
Ok(())
}

View File

@ -20,13 +20,12 @@ use wallet::{
};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6);
/// Poll the indexer until its last finalized block id reaches the sequencer's
/// current last block id or until [`L2_TO_L1_TIMEOUT_MILLIS`] elapses.
/// Returns the last indexer block id observed.
async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result<u64> {
let timeout = Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS);
let block_id_to_catch_up =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
let mut last_ind: u64 = 1;
@ -50,11 +49,11 @@ async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result<u64> {
tokio::time::sleep(Duration::from_secs(2)).await;
}
};
tokio::time::timeout(timeout, inner)
tokio::time::timeout(L2_TO_L1_TIMEOUT, inner)
.await
.with_context(|| {
format!(
"Indexer failed to catch up within {L2_TO_L1_TIMEOUT_MILLIS} milliseconds. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}"
"Indexer failed to catch up within {L2_TO_L1_TIMEOUT:?}. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}"
)
})?
}

View File

@ -10,6 +10,7 @@ use std::{
fs::File,
io::Write as _,
net::SocketAddr,
time::Duration,
};
use anyhow::{Context as _, Result};
@ -34,7 +35,7 @@ use wallet::{
};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6);
unsafe extern "C" {
unsafe fn query_last_block(
@ -114,7 +115,7 @@ fn indexer_test_run_ffi() -> Result<()> {
let (ctx, indexer_ffi, _indexer_dir) = setup()?;
// RUN OBSERVATION
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
@ -138,7 +139,7 @@ fn indexer_ffi_block_batching() -> Result<()> {
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
@ -265,7 +266,7 @@ fn indexer_ffi_state_consistency() -> Result<()> {
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
@ -371,7 +372,7 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> {
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };

View File

@ -48,6 +48,14 @@ pub struct MemPoolHandle<T> {
sender: Sender<T>,
}
impl<T> Clone for MemPoolHandle<T> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
}
}
}
impl<T> MemPoolHandle<T> {
const fn new(sender: Sender<T>) -> Self {
Self { sender }

View File

@ -11,6 +11,7 @@ workspace = true
nssa_core = { workspace = true, features = ["host"] }
clock_core.workspace = true
faucet_core.workspace = true
bridge_core.workspace = true
anyhow.workspace = true
thiserror.workspace = true

View File

@ -18,7 +18,7 @@ pub use public_transaction::PublicTransaction;
pub use signature::{PrivateKey, PublicKey, Signature};
pub use state::{
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
CLOCK_PROGRAM_ACCOUNT_IDS, V03State, system_faucet_account_id,
CLOCK_PROGRAM_ACCOUNT_IDS, V03State, system_bridge_account_id, system_faucet_account_id,
};
pub use validated_state_diff::ValidatedStateDiff;

View File

@ -10,8 +10,9 @@ use crate::{
error::NssaError,
program_methods::{
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, FAUCET_ELF,
FAUCET_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID,
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID, CLOCK_ELF,
CLOCK_ID, FAUCET_ELF, FAUCET_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF,
VAULT_ID,
},
};
@ -164,6 +165,14 @@ impl Program {
elf: FAUCET_ELF.to_vec(),
}
}
#[must_use]
pub fn bridge() -> Self {
Self {
id: BRIDGE_ID,
elf: BRIDGE_ELF.to_vec(),
}
}
}
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
@ -194,9 +203,9 @@ mod tests {
program::Program,
program_methods::{
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, FAUCET_ELF,
FAUCET_ID, PINATA_ELF, PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, TOKEN_ELF,
TOKEN_ID, VAULT_ELF, VAULT_ID,
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID,
CLOCK_ELF, CLOCK_ID, FAUCET_ELF, FAUCET_ID, PINATA_ELF, PINATA_ID, PINATA_TOKEN_ELF,
PINATA_TOKEN_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID,
},
};
@ -529,6 +538,7 @@ mod tests {
let token_program = Program::token();
let vault_program = Program::vault();
let faucet_program = Program::faucet();
let bridge_program = Program::bridge();
let pinata_program = Program::pinata();
assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID);
@ -539,6 +549,8 @@ mod tests {
assert_eq!(vault_program.elf, VAULT_ELF);
assert_eq!(faucet_program.id, FAUCET_ID);
assert_eq!(faucet_program.elf, FAUCET_ELF);
assert_eq!(bridge_program.id, BRIDGE_ID);
assert_eq!(bridge_program.elf, BRIDGE_ELF);
assert_eq!(pinata_program.id, PINATA_ID);
assert_eq!(pinata_program.elf, PINATA_ELF);
}
@ -551,6 +563,7 @@ mod tests {
(ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID),
(CLOCK_ELF, CLOCK_ID),
(FAUCET_ELF, FAUCET_ID),
(BRIDGE_ELF, BRIDGE_ID),
(PINATA_ELF, PINATA_ID),
(PINATA_TOKEN_ELF, PINATA_TOKEN_ID),
(TOKEN_ELF, TOKEN_ID),

View File

@ -126,8 +126,11 @@ impl Default for V03State {
fn default() -> Self {
let faucet_account_id = system_faucet_account_id();
let faucet_account = system_faucet_account();
let bridge_account_id = system_bridge_account_id();
let bridge_account = system_bridge_account();
let mut public_state = HashMap::new();
public_state.insert(faucet_account_id, faucet_account);
public_state.insert(bridge_account_id, bridge_account);
Self {
public_state,
@ -150,6 +153,7 @@ impl V03State {
genesis_timestamp: nssa_core::Timestamp,
) -> Self {
let faucet_account_id = system_faucet_account_id();
let bridge_account_id = system_bridge_account_id();
let authenticated_transfer_program = Program::authenticated_transfer_program();
let mut public_state: HashMap<_, _> = initial_data
.iter()
@ -164,7 +168,9 @@ impl V03State {
})
.collect();
let faucet_account = system_faucet_account();
let bridge_account = system_bridge_account();
public_state.insert(faucet_account_id, faucet_account);
public_state.insert(bridge_account_id, bridge_account);
let mut commitment_set = CommitmentSet::with_capacity(32);
commitment_set.extend(&[DUMMY_COMMITMENT]);
@ -190,6 +196,7 @@ impl V03State {
this.insert_program(Program::ata());
this.insert_program(Program::vault());
this.insert_program(Program::faucet());
this.insert_program(Program::bridge());
this
}
@ -384,11 +391,23 @@ fn system_faucet_account() -> Account {
}
}
fn system_bridge_account() -> Account {
Account {
program_owner: Program::authenticated_transfer_program().id(),
..Account::default()
}
}
#[must_use]
pub fn system_faucet_account_id() -> AccountId {
faucet_core::compute_faucet_account_id(Program::faucet().id())
}
#[must_use]
pub fn system_bridge_account_id() -> AccountId {
bridge_core::compute_bridge_account_id(Program::bridge().id())
}
#[cfg(test)]
pub mod tests {
#![expect(
@ -426,9 +445,10 @@ pub mod tests {
signature::PrivateKey,
state::{
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, system_faucet_account,
CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, system_bridge_account,
system_faucet_account,
},
system_faucet_account_id,
system_bridge_account_id, system_faucet_account_id,
};
impl V03State {
@ -622,6 +642,7 @@ pub mod tests {
},
);
this.insert(system_faucet_account_id(), system_faucet_account());
this.insert(system_bridge_account_id(), system_bridge_account());
for account_id in CLOCK_PROGRAM_ACCOUNT_IDS {
this.insert(
account_id,
@ -646,6 +667,7 @@ pub mod tests {
this.insert(Program::ata().id(), Program::ata());
this.insert(Program::vault().id(), Program::vault());
this.insert(Program::faucet().id(), Program::faucet());
this.insert(Program::bridge().id(), Program::bridge());
this
};

View File

@ -18,6 +18,7 @@ amm_program.workspace = true
ata_core.workspace = true
ata_program.workspace = true
faucet_core.workspace = true
bridge_core.workspace = true
vault_core.workspace = true
risc0-zkvm.workspace = true
serde = { workspace = true, default-features = false }

View File

@ -0,0 +1,82 @@
use bridge_core::Instruction;
use nssa_core::program::{
AccountPostState, ChainedCall, ProgramInput, ProgramOutput, read_nssa_inputs,
};
fn unchanged_post_states(
pre_states: &[nssa_core::account::AccountWithMetadata],
) -> Vec<AccountPostState> {
pre_states
.iter()
.map(|pre_state| AccountPostState::new(pre_state.account.clone()))
.collect()
}
fn main() {
let (
ProgramInput {
self_program_id,
caller_program_id,
pre_states,
instruction,
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
assert!(
caller_program_id.is_none(),
"Bridge cannot be invoked through chain calls"
);
let pre_states_clone = pre_states.clone();
let post_states = unchanged_post_states(&pre_states_clone);
let chained_calls = match instruction {
Instruction::Deposit {
vault_program_id,
recipient_id,
amount,
} => {
let [bridge, recipient_vault] = pre_states
.try_into()
.expect("Deposit requires exactly 2 accounts");
assert_eq!(
bridge.account_id,
bridge_core::compute_bridge_account_id(self_program_id),
"First account must be bridge PDA"
);
assert_eq!(
recipient_vault.account_id,
vault_core::compute_vault_account_id(vault_program_id, recipient_id),
"Second account must be recipient vault PDA"
);
let mut bridge_for_vault = bridge;
bridge_for_vault.is_authorized = true;
vec![
ChainedCall::new(
vault_program_id,
vec![bridge_for_vault, recipient_vault],
&vault_core::Instruction::Transfer {
recipient_id,
amount,
},
)
.with_pda_seeds(vec![bridge_core::compute_bridge_seed()]),
]
}
};
ProgramOutput::new(
self_program_id,
caller_program_id,
instruction_words,
pre_states_clone,
post_states,
)
.with_chained_calls(chained_calls)
.write();
}

View File

@ -23,11 +23,16 @@ fn main() {
instruction_words,
) = read_nssa_inputs::<Instruction>();
assert!(
caller_program_id.is_none(),
"Faucet cannot be invoked through chain calls"
);
let pre_states_clone = pre_states.clone();
let post_states = unchanged_post_states(&pre_states_clone);
let chained_calls = match instruction {
Instruction::Transfer {
Instruction::GenesisTransferVault {
vault_program_id,
recipient_id,
amount,
@ -57,6 +62,29 @@ fn main() {
.with_pda_seeds(vec![faucet_core::compute_faucet_seed()]),
]
}
Instruction::GenesisTransferDirect { amount } => {
let [faucet, recipient] = pre_states
.try_into()
.expect("TransferDirect requires exactly 2 accounts");
assert_eq!(
faucet.account_id,
faucet_core::compute_faucet_account_id(self_program_id),
"First account must be faucet PDA"
);
let mut faucet_for_transfer = faucet;
faucet_for_transfer.is_authorized = true;
vec![
ChainedCall::new(
faucet_for_transfer.account.program_owner,
vec![faucet_for_transfer, recipient],
&authenticated_transfer_core::Instruction::Transfer { amount },
)
.with_pda_seeds(vec![faucet_core::compute_faucet_seed()]),
]
}
};
ProgramOutput::new(

View File

@ -0,0 +1,12 @@
[package]
name = "bridge_core"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[lints]
workspace = true
[dependencies]
nssa_core.workspace = true
serde = { workspace = true, default-features = false }

View File

@ -0,0 +1,29 @@
pub use nssa_core::program::PdaSeed;
use nssa_core::{account::AccountId, program::ProgramId};
use serde::{Deserialize, Serialize};
const BRIDGE_SEED_DOMAIN_SEPARATOR: [u8; 32] = *b"/LEZ/v0.3/BridgeSeed/0000000000/";
#[derive(Serialize, Deserialize)]
pub enum Instruction {
/// Transfers native tokens from the bridge PDA account to a recipient vault.
///
/// Required accounts (2):
/// - Bridge PDA account
/// - Recipient vault PDA account
Deposit {
vault_program_id: ProgramId,
recipient_id: AccountId,
amount: u128,
},
}
#[must_use]
pub const fn compute_bridge_seed() -> PdaSeed {
PdaSeed::new(BRIDGE_SEED_DOMAIN_SEPARATOR)
}
#[must_use]
pub fn compute_bridge_account_id(bridge_program_id: ProgramId) -> AccountId {
AccountId::for_public_pda(&bridge_program_id, &compute_bridge_seed())
}

View File

@ -8,14 +8,25 @@ const FAUCET_SEED_DOMAIN_SEPARATOR: [u8; 32] = *b"/LEZ/v0.3/FaucetSeed/000000000
pub enum Instruction {
/// Transfers native tokens from system faucet to recipient's vault.
///
/// Executed only in genesis block by sequencer it-self. User transactions will be denied.
///
/// Required accounts (2):
/// - Faucet PDA account
/// - Recipient vault PDA account
Transfer {
GenesisTransferVault {
vault_program_id: ProgramId,
recipient_id: AccountId,
amount: u128,
},
/// Transfers native tokens from system faucet directly to a recipient account.
///
/// Executed only in genesis block by sequencer it-self. User transactions will be denied.
///
/// Required accounts (2):
/// - Faucet PDA account
/// - Recipient account
GenesisTransferDirect { amount: u128 },
}
#[must_use]

View File

@ -16,6 +16,7 @@ mempool.workspace = true
logos-blockchain-zone-sdk.workspace = true
testnet_initial_state.workspace = true
faucet_core.workspace = true
bridge_core.workspace = true
vault_core.workspace = true
anyhow.workspace = true
@ -31,6 +32,7 @@ logos-blockchain-core.workspace = true
rand.workspace = true
borsh.workspace = true
bytesize.workspace = true
hex.workspace = true
url.workspace = true
[features]

View File

@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration};
use std::{pin::Pin, sync::Arc, time::Duration};
use anyhow::{Context as _, Result, anyhow};
use anyhow::{Context as _, Result};
use common::block::Block;
use log::warn;
pub use logos_blockchain_core::mantle::ops::channel::MsgId;
@ -10,7 +10,7 @@ use logos_blockchain_zone_sdk::{
CommonHttpClient,
adapter::NodeHttpClient,
sequencer::{Event, SequencerConfig as ZoneSdkSequencerConfig, SequencerHandle, ZoneSequencer},
state::InscriptionInfo,
state::{DepositInfo, FinalizedOp, InscriptionInfo},
};
use tokio::task::JoinHandle;
@ -18,12 +18,16 @@ use crate::config::BedrockConfig;
/// Sink for `Event::Published` checkpoints emitted by the drive task.
/// Caller is responsible for persistence (e.g. writing to rocksdb).
pub type CheckpointSink = Box<dyn Fn(SequencerCheckpoint) + Send + Sync + 'static>;
pub type CheckpointSink = Box<dyn Fn(SequencerCheckpoint) + Send + 'static>;
/// Sink for finalized L2 block ids derived from `Event::TxsFinalized` and
/// `Event::FinalizedInscriptions`. Caller is responsible for cleanup
/// (e.g. marking pending blocks as finalized in storage).
pub type FinalizedBlockSink = Box<dyn Fn(u64) + Send + Sync + 'static>;
pub type FinalizedBlockSink = Box<dyn Fn(u64) + Send + 'static>;
/// Sink for finalized Bedrock deposit events.
pub type OnDepositEventSink =
Box<dyn Fn(DepositInfo) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
#[expect(async_fn_in_trait, reason = "We don't care about Send/Sync here")]
pub trait BlockPublisherTrait: Clone {
@ -34,6 +38,7 @@ pub trait BlockPublisherTrait: Clone {
initial_checkpoint: Option<SequencerCheckpoint>,
on_checkpoint: CheckpointSink,
on_finalized_block: FinalizedBlockSink,
on_deposit_event: OnDepositEventSink,
) -> Result<Self>;
/// Fire-and-forget publish. Zone-sdk drives the actual submission and
@ -65,6 +70,7 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
initial_checkpoint: Option<SequencerCheckpoint>,
on_checkpoint: CheckpointSink,
on_finalized_block: FinalizedBlockSink,
on_deposit_event: OnDepositEventSink,
) -> Result<Self> {
let basic_auth = config.auth.clone().map(Into::into);
let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), config.node_url.clone());
@ -89,10 +95,20 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
};
match event {
Event::Published { checkpoint, .. } => on_checkpoint(checkpoint),
Event::TxsFinalized { inscriptions, .. }
| Event::FinalizedInscriptions { inscriptions } => {
if let Some(max_block_id) = max_block_id_from_inscriptions(&inscriptions) {
on_finalized_block(max_block_id);
Event::TxsFinalized { items } => {
for op in items.into_iter().flat_map(|item| item.ops) {
match op {
FinalizedOp::Inscription(inscription) => {
if let Some(block_id) = block_id_from_inscription(&inscription)
{
on_finalized_block(block_id);
}
}
FinalizedOp::Deposit(deposit) => {
on_deposit_event(deposit).await;
}
FinalizedOp::Withdraw(_) => {}
}
}
}
Event::ChannelUpdate { .. } | Event::Ready => {}
@ -110,27 +126,26 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
async fn publish_block(&self, block: &Block) -> Result<()> {
let data = borsh::to_vec(block).context("Failed to serialize block")?;
let data_bounded = data
.try_into()
.context("Block data exceeds maximum allowed size")?;
self.handle
.publish_message(data)
.publish_message(data_bounded)
.await
.map_err(|e| anyhow!("zone-sdk publish failed: {e}"))?;
.context("Failed to publish block")?;
Ok(())
}
}
/// Deserialize each inscription payload as a `Block` and return the highest
/// `block_id`. Bad payloads are logged and skipped.
fn max_block_id_from_inscriptions(inscriptions: &[InscriptionInfo]) -> Option<u64> {
inscriptions
.iter()
.filter_map(
|inscription| match borsh::from_slice::<Block>(&inscription.payload) {
Ok(block) => Some(block.header.block_id),
Err(err) => {
warn!("Failed to deserialize finalized inscription as Block: {err:#}");
None
}
},
)
.max()
/// Deserialize inscription payload as a `Block` and return it's`block_id`.
/// Bad payloads are logged and skipped.
fn block_id_from_inscription(inscription: &InscriptionInfo) -> Option<u64> {
borsh::from_slice::<Block>(&inscription.payload)
.inspect_err(|err| {
warn!("Failed to deserialize block from inscription: {err:?}");
})
.ok()
.map(|block| block.header.block_id)
}

View File

@ -22,6 +22,9 @@ pub enum GenesisAction {
account_id: AccountId,
balance: u128,
},
SupplyBridgeAccount {
balance: u128,
},
}
// TODO: Provide default values

View File

@ -1,6 +1,7 @@
use std::{path::Path, time::Instant};
use anyhow::{Context as _, Result, anyhow};
use borsh::BorshDeserialize;
use common::{
HashType,
block::{BedrockStatus, Block, HashableBlockData},
@ -28,10 +29,29 @@ pub mod config;
#[cfg(feature = "mock")]
pub mod mock;
/// The origin of a transaction.
pub enum TransactionOrigin {
/// Basic transactions submitted by users via RPC.
User,
/// Transactions generated by the sequencer itself.
Sequencer,
}
#[derive(Clone, Debug, BorshDeserialize)]
struct DepositMetadata {
recipient_id: nssa::AccountId,
}
impl DepositMetadata {
fn decode(bytes: &[u8]) -> Result<Self, std::io::Error> {
Self::try_from_slice(bytes)
}
}
pub struct SequencerCore<BP: BlockPublisherTrait = ZoneSdkPublisher> {
state: nssa::V03State,
store: SequencerStore,
mempool: MemPool<NSSATransaction>,
mempool: MemPool<(TransactionOrigin, NSSATransaction)>,
sequencer_config: SequencerConfig,
chain_height: u64,
block_publisher: BP,
@ -45,7 +65,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
/// initializing its state with the accounts defined in the configuration file.
pub async fn start_from_config(
config: SequencerConfig,
) -> (Self, MemPoolHandle<NSSATransaction>) {
) -> (Self, MemPoolHandle<(TransactionOrigin, NSSATransaction)>) {
let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap();
let bedrock_signing_key =
@ -132,6 +152,37 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
}
});
let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size);
let mempool_handle_for_deposit = mempool_handle.clone();
let on_deposit_event: block_publisher::OnDepositEventSink = Box::new(move |deposit| {
let mempool_handle_for_deposit = mempool_handle_for_deposit.clone();
Box::pin(async move {
info!(
"Observed Bedrock Deposit event with id: {:?}",
hex::encode(deposit.op_id)
);
let tx = match build_bridge_deposit_tx(&deposit) {
Ok(tx) => tx,
Err(err) => {
warn!(
"Skipping finalized Bedrock deposit event due to tx build failure: {err:#}"
);
return;
}
};
if let Err(err) = mempool_handle_for_deposit
.push((TransactionOrigin::Sequencer, tx))
.await
{
error!(
"Failed to queue sequencer transaction built from finalized Bedrock event: {err:#}"
);
}
})
});
let block_publisher = BP::new(
&config.bedrock_config,
bedrock_signing_key,
@ -139,6 +190,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
initial_checkpoint,
on_checkpoint,
on_finalized_block,
on_deposit_event,
)
.await
.expect("Failed to initialize Block Publisher");
@ -151,8 +203,6 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
error!("Failed to publish genesis block: {err:#}");
}
let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size);
let sequencer_core = Self {
state,
store,
@ -207,7 +257,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
let clock_tx = clock_invocation(new_block_timestamp);
let clock_nssa_tx = NSSATransaction::Public(clock_tx.clone());
while let Some(tx) = self.mempool.pop() {
while let Some((origin, tx)) = self.mempool.pop() {
let tx_hash = tx.hash();
// Check if block size exceeds limit (including the mandatory clock tx).
@ -235,25 +285,41 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
block size {block_size} bytes would exceed limit of {max_block_size} bytes",
);
self.mempool.push_front(tx);
self.mempool.push_front((origin, tx));
break;
}
let validated_diff = match tx.validate_on_state(
&self.state,
new_block_height,
new_block_timestamp,
) {
Ok(diff) => diff,
Err(err) => {
error!(
"Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it",
);
continue;
}
};
match origin {
TransactionOrigin::User => {
let validated_diff = match tx.validate_on_state(
&self.state,
new_block_height,
new_block_timestamp,
) {
Ok(diff) => diff,
Err(err) => {
error!(
"Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it",
);
continue;
}
};
self.state.apply_state_diff(validated_diff);
self.state.apply_state_diff(validated_diff);
}
TransactionOrigin::Sequencer => {
let NSSATransaction::Public(public_tx) = &tx else {
panic!("Sequencer may only generate Public transactions, found {tx:#?}");
};
self.state
.transition_from_public_transaction(
public_tx,
new_block_height,
new_block_timestamp,
)
.context("Failed to execute sequencer-generated transaction")?;
}
}
valid_transactions.push(tx);
info!("Validated transaction with hash {tx_hash}, including it in block");
@ -363,6 +429,9 @@ fn build_genesis_state(config: &SequencerConfig) -> (nssa::V03State, Vec<NSSATra
account_id,
balance,
} => build_supply_account_genesis_transaction(account_id, *balance),
GenesisAction::SupplyBridgeAccount { balance } => {
build_supply_bridge_account_genesis_transaction(*balance)
}
})
.chain(std::iter::once(clock_invocation(0)))
.inspect(|tx| {
@ -388,7 +457,7 @@ fn build_supply_account_genesis_transaction(
faucet_program_id,
vec![nssa::system_faucet_account_id(), recipient_vault_id],
vec![],
faucet_core::Instruction::Transfer {
faucet_core::Instruction::GenesisTransferVault {
vault_program_id,
recipient_id: *account_id,
amount: balance,
@ -400,6 +469,52 @@ fn build_supply_account_genesis_transaction(
PublicTransaction::new(message, witness_set)
}
fn build_supply_bridge_account_genesis_transaction(balance: u128) -> PublicTransaction {
let faucet_program_id = Program::faucet().id();
let bridge_account_id = nssa::system_bridge_account_id();
let message = Message::try_new(
faucet_program_id,
vec![nssa::system_faucet_account_id(), bridge_account_id],
vec![],
faucet_core::Instruction::GenesisTransferDirect { amount: balance },
)
.expect("Failed to serialize bridge genesis transfer instruction");
let witness_set = nssa::public_transaction::WitnessSet::from_raw_parts(vec![]);
PublicTransaction::new(message, witness_set)
}
fn build_bridge_deposit_tx(
deposit: &logos_blockchain_zone_sdk::state::DepositInfo,
) -> Result<NSSATransaction> {
let metadata = DepositMetadata::decode(&deposit.metadata)
.context("Failed to decode finalized Bedrock deposit metadata")?;
let bridge_program_id = Program::bridge().id();
let vault_program_id = Program::vault().id();
let recipient_vault_id =
vault_core::compute_vault_account_id(vault_program_id, metadata.recipient_id);
let message = Message::try_new(
bridge_program_id,
vec![nssa::system_bridge_account_id(), recipient_vault_id],
vec![],
bridge_core::Instruction::Deposit {
vault_program_id,
recipient_id: metadata.recipient_id,
amount: u128::from(deposit.amount),
},
)
.context("Failed to build bridge deposit message")?;
let witness_set = nssa::public_transaction::WitnessSet::from_raw_parts(vec![]);
Ok(NSSATransaction::Public(PublicTransaction::new(
message,
witness_set,
)))
}
/// Load signing key from file or generate a new one if it doesn't exist.
fn load_or_create_signing_key(path: &Path) -> Result<Ed25519Key> {
if path.exists() {
@ -441,6 +556,7 @@ mod tests {
use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys};
use crate::{
TransactionOrigin,
block_store::SequencerStore,
build_genesis_state,
config::{BedrockConfig, SequencerConfig},
@ -476,19 +592,28 @@ mod tests {
initial_pub_accounts_private_keys()[1].pub_sign_key.clone()
}
async fn common_setup() -> (SequencerCoreWithMockClients, MemPoolHandle<NSSATransaction>) {
async fn common_setup() -> (
SequencerCoreWithMockClients,
MemPoolHandle<(TransactionOrigin, NSSATransaction)>,
) {
let config = setup_sequencer_config();
common_setup_with_config(config).await
}
async fn common_setup_with_config(
config: SequencerConfig,
) -> (SequencerCoreWithMockClients, MemPoolHandle<NSSATransaction>) {
) -> (
SequencerCoreWithMockClients,
MemPoolHandle<(TransactionOrigin, NSSATransaction)>,
) {
let (mut sequencer, mempool_handle) =
SequencerCoreWithMockClients::start_from_config(config).await;
let tx = common::test_utils::produce_dummy_empty_transaction();
mempool_handle.push(tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
@ -678,10 +803,13 @@ mod tests {
let tx = common::test_utils::produce_dummy_empty_transaction();
// Fill the mempool
mempool_handle.push(tx.clone()).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx.clone()))
.await
.unwrap();
// Check that pushing another transaction will block
let mut push_fut = pin!(mempool_handle.push(tx.clone()));
let mut push_fut = pin!(mempool_handle.push((TransactionOrigin::User, tx.clone())));
let poll = futures::poll!(push_fut.as_mut());
assert!(poll.is_pending());
@ -698,7 +826,10 @@ mod tests {
let genesis_height = sequencer.chain_height;
let tx = common::test_utils::produce_dummy_empty_transaction();
mempool_handle.push(tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx))
.await
.unwrap();
let result = sequencer.build_block_from_mempool();
assert!(result.is_ok());
@ -721,8 +852,14 @@ mod tests {
let tx_original = tx.clone();
let tx_replay = tx.clone();
// Pushing two copies of the same tx to the mempool
mempool_handle.push(tx_original).await.unwrap();
mempool_handle.push(tx_replay).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx_original))
.await
.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx_replay))
.await
.unwrap();
// Create block
sequencer.produce_new_block().await.unwrap();
@ -756,7 +893,10 @@ mod tests {
);
// The transaction should be included the first time
mempool_handle.push(tx.clone()).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx.clone()))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
let block = sequencer
.store
@ -772,7 +912,10 @@ mod tests {
);
// Add same transaction should fail
mempool_handle.push(tx.clone()).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx.clone()))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
let block = sequencer
.store
@ -811,7 +954,10 @@ mod tests {
&signing_key,
);
mempool_handle.push(tx.clone()).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx.clone()))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
let block = sequencer
.store
@ -895,7 +1041,10 @@ mod tests {
&signing_key,
);
mempool_handle.push(tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
// Get the metadata of the last block produced
@ -916,7 +1065,10 @@ mod tests {
&signing_key,
);
mempool_handle.push(tx.clone()).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx.clone()))
.await
.unwrap();
// Step 4: Produce new block
sequencer.produce_new_block().await.unwrap();
@ -962,10 +1114,16 @@ mod tests {
))
};
mempool_handle
.push(NSSATransaction::Public(clock_invocation(0)))
.push((
TransactionOrigin::User,
NSSATransaction::Public(clock_invocation(0)),
))
.await
.unwrap();
mempool_handle
.push((TransactionOrigin::User, crafted_clock_tx))
.await
.unwrap();
mempool_handle.push(crafted_clock_tx).await.unwrap();
sequencer.produce_new_block().await.unwrap();
let block = sequencer
@ -994,7 +1152,10 @@ mod tests {
test_program_methods::CLOCK_CHAIN_CALLER_ELF.to_vec(),
),
));
mempool_handle.push(deploy_tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, deploy_tx))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
// Build a user transaction that invokes clock_chain_caller, which in turn chain-calls the
@ -1019,7 +1180,10 @@ mod tests {
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
));
mempool_handle.push(user_tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, user_tx))
.await
.unwrap();
sequencer.produce_new_block().await.unwrap();
let block = sequencer
@ -1051,7 +1215,10 @@ mod tests {
// Push a dummy transaction so the mempool is non-empty.
let tx = common::test_utils::produce_dummy_empty_transaction();
mempool_handle.push(tx).await.unwrap();
mempool_handle
.push((TransactionOrigin::User, tx))
.await
.unwrap();
// Block production must fail because the appended clock tx cannot execute.
let result = sequencer.produce_new_block().await;

View File

@ -6,7 +6,8 @@ use logos_blockchain_key_management_system_service::keys::Ed25519Key;
use crate::{
block_publisher::{
BlockPublisherTrait, CheckpointSink, FinalizedBlockSink, SequencerCheckpoint,
BlockPublisherTrait, CheckpointSink, FinalizedBlockSink, OnDepositEventSink,
SequencerCheckpoint,
},
config::BedrockConfig,
};
@ -24,6 +25,7 @@ impl BlockPublisherTrait for MockBlockPublisher {
_initial_checkpoint: Option<SequencerCheckpoint>,
_on_checkpoint: CheckpointSink,
_on_finalized_block: FinalizedBlockSink,
_on_deposit_event: OnDepositEventSink,
) -> Result<Self> {
Ok(Self)
}

View File

@ -15,6 +15,11 @@
},
"indexer_rpc_url": "ws://localhost:8779",
"genesis": [
{
"supply_bridge_account": {
"balance": 1000000
}
},
{
"supply_account": {
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",

View File

@ -15,6 +15,11 @@
},
"indexer_rpc_url": "ws://localhost:8779",
"genesis": [
{
"supply_bridge_account": {
"balance": 1000000
}
},
{
"supply_account": {
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",

View File

@ -11,6 +11,7 @@ use mempool::MemPoolHandle;
use sequencer_core::SequencerCore;
#[cfg(feature = "standalone")]
use sequencer_core::SequencerCoreWithMockClients as SequencerCore;
use sequencer_core::TransactionOrigin;
pub use sequencer_core::config::*;
use sequencer_service_rpc::RpcServer as _;
use tokio::{sync::Mutex, task::JoinHandle};
@ -55,15 +56,14 @@ impl SequencerHandle {
} = &mut self;
let server_handle = server_handle.take().expect("Server handle is set");
tokio::select! {
() = server_handle.stopped() => {
Err(anyhow!("RPC Server stopped"))
}
res = main_loop_handle => {
res
.context("Main loop task panicked")?
.context("Main loop exited unexpectedly")
.context("Main loop task panicked")?
.context("Main loop exited unexpectedly")
}
}
}
@ -120,10 +120,11 @@ pub async fn run(config: SequencerConfig, port: u16) -> Result<SequencerHandle>
info!("Sequencer core set up");
let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core));
let mempool_handle_for_server = mempool_handle.clone();
let (server_handle, addr) = run_server(
Arc::clone(&seq_core_wrapped),
mempool_handle,
mempool_handle_for_server,
port,
max_block_size.as_u64(),
)
@ -133,12 +134,14 @@ pub async fn run(config: SequencerConfig, port: u16) -> Result<SequencerHandle>
info!("Starting main sequencer loop");
let main_loop_handle = tokio::spawn(main_loop(seq_core_wrapped, block_timeout));
let _ = mempool_handle;
Ok(SequencerHandle::new(addr, server_handle, main_loop_handle))
}
async fn run_server(
sequencer: Arc<Mutex<SequencerCore>>,
mempool_handle: MemPoolHandle<NSSATransaction>,
mempool_handle: MemPoolHandle<(TransactionOrigin, NSSATransaction)>,
port: u16,
max_block_size: u64,
) -> Result<(ServerHandle, SocketAddr)> {

View File

@ -8,7 +8,9 @@ use jsonrpsee::{
use log::warn;
use mempool::MemPoolHandle;
use nssa::{self, program::Program};
use sequencer_core::{DbError, SequencerCore, block_publisher::BlockPublisherTrait};
use sequencer_core::{
DbError, SequencerCore, TransactionOrigin, block_publisher::BlockPublisherTrait,
};
use sequencer_service_protocol::{
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, Nonce, ProgramId,
};
@ -18,14 +20,14 @@ const NOT_FOUND_ERROR_CODE: i32 = -31999;
pub struct SequencerService<BC: BlockPublisherTrait> {
sequencer: Arc<Mutex<SequencerCore<BC>>>,
mempool_handle: MemPoolHandle<NSSATransaction>,
mempool_handle: MemPoolHandle<(TransactionOrigin, NSSATransaction)>,
max_block_size: u64,
}
impl<BC: BlockPublisherTrait> SequencerService<BC> {
pub const fn new(
sequencer: Arc<Mutex<SequencerCore<BC>>>,
mempool_handle: MemPoolHandle<NSSATransaction>,
mempool_handle: MemPoolHandle<(TransactionOrigin, NSSATransaction)>,
max_block_size: u64,
) -> Self {
Self {
@ -72,7 +74,7 @@ impl<BC: BlockPublisherTrait + Send + 'static> sequencer_service_rpc::RpcServer
})?;
self.mempool_handle
.push(authenticated_tx)
.push((TransactionOrigin::User, authenticated_tx))
.await
.expect("Mempool is closed, this is a bug");

View File

@ -143,7 +143,12 @@ pub fn genesis_from_accounts(
balance: account.balance,
});
public_genesis.chain(private_genesis).collect()
let supply_bridge_account = GenesisAction::SupplyBridgeAccount { balance: 1_000_000 };
public_genesis
.chain(private_genesis)
.chain(std::iter::once(supply_bridge_account))
.collect()
}
pub fn wallet_config(sequencer_addr: SocketAddr) -> Result<WalletConfig> {
@ -184,7 +189,8 @@ pub fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result<Url> {
url_string.parse().map_err(Into::into)
}
fn bedrock_channel_id() -> ChannelId {
#[must_use]
pub fn bedrock_channel_id() -> ChannelId {
let channel_id: [u8; 32] = [0_u8, 1]
.repeat(16)
.try_into()

View File

@ -30,7 +30,7 @@ fn main() {
let chained_calls = vec![ChainedCall {
program_id: faucet_program_id,
instruction_data: to_vec(&faucet_core::Instruction::Transfer {
instruction_data: to_vec(&faucet_core::Instruction::GenesisTransferVault {
vault_program_id,
recipient_id,
amount,