Compare commits
2 Commits
win-servic
...
b77f6ed656
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b77f6ed656 | ||
|
|
6987e5f70f |
62
mise.lock
62
mise.lock
@@ -8,18 +8,10 @@ backend = "aqua:ast-grep/ast-grep"
|
||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
||||
|
||||
[tools.ast-grep."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
||||
|
||||
[tools.ast-grep."platforms.linux-x64"]
|
||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
||||
|
||||
[tools.ast-grep."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
||||
|
||||
[tools.ast-grep."platforms.macos-arm64"]
|
||||
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
||||
@@ -40,6 +32,10 @@ backend = "cargo:cargo-audit"
|
||||
version = "0.13.9"
|
||||
backend = "cargo:cargo-edit"
|
||||
|
||||
[[tools."cargo:cargo-features"]]
|
||||
version = "1.0.0"
|
||||
backend = "cargo:cargo-features"
|
||||
|
||||
[[tools."cargo:cargo-features-manager"]]
|
||||
version = "0.11.1"
|
||||
backend = "cargo:cargo-features-manager"
|
||||
@@ -53,13 +49,21 @@ version = "0.9.126"
|
||||
backend = "cargo:cargo-nextest"
|
||||
|
||||
[[tools."cargo:cargo-shear"]]
|
||||
version = "1.11.2"
|
||||
version = "1.9.1"
|
||||
backend = "cargo:cargo-shear"
|
||||
|
||||
[[tools."cargo:cargo-vet"]]
|
||||
version = "0.10.2"
|
||||
backend = "cargo:cargo-vet"
|
||||
|
||||
[[tools."cargo:diesel-cli"]]
|
||||
version = "2.3.6"
|
||||
backend = "cargo:diesel-cli"
|
||||
|
||||
[tools."cargo:diesel-cli".options]
|
||||
default-features = "false"
|
||||
features = "sqlite,sqlite-bundled"
|
||||
|
||||
[[tools."cargo:diesel_cli"]]
|
||||
version = "2.3.6"
|
||||
backend = "cargo:diesel_cli"
|
||||
@@ -68,6 +72,10 @@ backend = "cargo:diesel_cli"
|
||||
default-features = "false"
|
||||
features = "sqlite,sqlite-bundled"
|
||||
|
||||
[[tools."cargo:rinf_cli"]]
|
||||
version = "8.9.1"
|
||||
backend = "cargo:rinf_cli"
|
||||
|
||||
[[tools.flutter]]
|
||||
version = "3.38.9-stable"
|
||||
backend = "asdf:flutter"
|
||||
@@ -80,18 +88,10 @@ backend = "aqua:protocolbuffers/protobuf/protoc"
|
||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
||||
|
||||
[tools.protoc."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
||||
|
||||
[tools.protoc."platforms.linux-x64"]
|
||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
||||
|
||||
[tools.protoc."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
||||
|
||||
[tools.protoc."platforms.macos-arm64"]
|
||||
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
||||
@@ -109,32 +109,24 @@ version = "3.14.3"
|
||||
backend = "core:python"
|
||||
|
||||
[tools.python."platforms.linux-arm64"]
|
||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.linux-x64"]
|
||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.macos-arm64"]
|
||||
checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||
checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.macos-x64"]
|
||||
checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||
checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||
|
||||
[tools.python."platforms.windows-x64"]
|
||||
checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||
checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0"
|
||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||
|
||||
[[tools.rust]]
|
||||
version = "1.93.0"
|
||||
|
||||
@@ -49,6 +49,7 @@ message ClientRequest {
|
||||
AuthChallengeRequest auth_challenge_request = 1;
|
||||
AuthChallengeSolution auth_challenge_solution = 2;
|
||||
google.protobuf.Empty query_vault_state = 3;
|
||||
arbiter.evm.EvmSignTransactionRequest evm_sign_transaction = 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,11 @@ message ListWalletAccessResponse {
|
||||
repeated SdkClientWalletAccess accesses = 1;
|
||||
}
|
||||
|
||||
message UserAgentEvmSignTransactionRequest {
|
||||
int32 client_id = 1;
|
||||
arbiter.evm.EvmSignTransactionRequest request = 2;
|
||||
}
|
||||
|
||||
message UserAgentRequest {
|
||||
int32 id = 16;
|
||||
oneof payload {
|
||||
@@ -174,6 +179,7 @@ message UserAgentRequest {
|
||||
SdkClientGrantWalletAccess grant_wallet_access = 15;
|
||||
SdkClientRevokeWalletAccess revoke_wallet_access = 17;
|
||||
google.protobuf.Empty list_wallet_access = 18;
|
||||
UserAgentEvmSignTransactionRequest evm_sign_transaction = 19;
|
||||
}
|
||||
}
|
||||
message UserAgentResponse {
|
||||
@@ -195,5 +201,6 @@ message UserAgentResponse {
|
||||
SdkClientListResponse sdk_client_list_response = 14;
|
||||
BootstrapResult bootstrap_result = 15;
|
||||
ListWalletAccessResponse list_wallet_access_response = 17;
|
||||
arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 18;
|
||||
}
|
||||
}
|
||||
|
||||
143
server/Cargo.lock
generated
143
server/Cargo.lock
generated
@@ -669,56 +669,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
@@ -780,7 +730,6 @@ dependencies = [
|
||||
"async-trait",
|
||||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dashmap",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
@@ -812,7 +761,6 @@ dependencies = [
|
||||
"tonic",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"windows-service",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1485,46 +1433,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
@@ -1534,12 +1442,6 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.11"
|
||||
@@ -2149,7 +2051,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]]
|
||||
@@ -2951,12 +2853,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -3290,7 +3186,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3424,12 +3320,6 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
@@ -4395,7 +4285,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4811,7 +4701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5005,7 +4895,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5579,12 +5469,6 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.22.0"
|
||||
@@ -5791,12 +5675,6 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -5869,17 +5747,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-service"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"widestring",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use arbiter_proto::proto::{
|
||||
client::{ClientRequest, ClientResponse},
|
||||
};
|
||||
use arbiter_proto::proto::client::{ClientRequest, ClientResponse};
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -36,9 +34,7 @@ impl ClientTransport {
|
||||
.map_err(|_| ClientSignError::ChannelClosed)
|
||||
}
|
||||
|
||||
pub(crate) async fn recv(
|
||||
&mut self,
|
||||
) -> std::result::Result<ClientResponse, ClientSignError> {
|
||||
pub(crate) async fn recv(&mut self) -> std::result::Result<ClientResponse, ClientSignError> {
|
||||
match self.receiver.message().await {
|
||||
Ok(Some(resp)) => Ok(resp),
|
||||
Ok(None) => Err(ClientSignError::ConnectionClosed),
|
||||
|
||||
@@ -8,7 +8,15 @@ use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::transport::ClientTransport;
|
||||
use arbiter_proto::proto::{
|
||||
client::{
|
||||
ClientRequest, client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
},
|
||||
evm::evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
||||
};
|
||||
|
||||
use crate::transport::{ClientTransport, next_request_id};
|
||||
|
||||
pub struct ArbiterEvmWallet {
|
||||
transport: Arc<Mutex<ClientTransport>>,
|
||||
@@ -79,11 +87,61 @@ impl TxSigner<Signature> for ArbiterEvmWallet {
|
||||
&self,
|
||||
tx: &mut dyn SignableTransaction<Signature>,
|
||||
) -> Result<Signature> {
|
||||
let _transport = self.transport.lock().await;
|
||||
self.validate_chain_id(tx)?;
|
||||
|
||||
Err(Error::other(
|
||||
"transaction signing is not supported by current arbiter.client protocol",
|
||||
))
|
||||
let mut transport = self.transport.lock().await;
|
||||
let request_id = next_request_id();
|
||||
let rlp_transaction = tx.encoded_for_signing();
|
||||
|
||||
transport
|
||||
.send(ClientRequest {
|
||||
request_id,
|
||||
payload: Some(ClientRequestPayload::EvmSignTransaction(
|
||||
arbiter_proto::proto::evm::EvmSignTransactionRequest {
|
||||
wallet_address: self.address.to_vec(),
|
||||
rlp_transaction,
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::other("failed to send evm sign transaction request"))?;
|
||||
|
||||
let response = transport
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| Error::other("failed to receive evm sign transaction response"))?;
|
||||
|
||||
if response.request_id != Some(request_id) {
|
||||
return Err(Error::other(
|
||||
"received mismatched response id for evm sign transaction",
|
||||
));
|
||||
}
|
||||
|
||||
let payload = response
|
||||
.payload
|
||||
.ok_or_else(|| Error::other("missing evm sign transaction response payload"))?;
|
||||
|
||||
let ClientResponsePayload::EvmSignTransaction(response) = payload else {
|
||||
return Err(Error::other(
|
||||
"unexpected response payload for evm sign transaction request",
|
||||
));
|
||||
};
|
||||
|
||||
let result = response
|
||||
.result
|
||||
.ok_or_else(|| Error::other("missing evm sign transaction result"))?;
|
||||
|
||||
match result {
|
||||
EvmSignTransactionResult::Signature(signature) => {
|
||||
Signature::try_from(signature.as_slice())
|
||||
.map_err(|_| Error::other("invalid signature returned by server"))
|
||||
}
|
||||
EvmSignTransactionResult::EvalError(eval_error) => Err(Error::other(format!(
|
||||
"transaction rejected by policy: {eval_error:?}"
|
||||
))),
|
||||
EvmSignTransactionResult::Error(code) => Err(Error::other(format!(
|
||||
"server failed to sign transaction with error code {code}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@ pub mod transport;
|
||||
pub mod url;
|
||||
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{LazyLock, RwLock},
|
||||
};
|
||||
|
||||
pub mod proto {
|
||||
tonic::include_proto!("arbiter");
|
||||
@@ -31,27 +27,8 @@ pub struct ClientMetadata {
|
||||
}
|
||||
|
||||
pub static BOOTSTRAP_PATH: &str = "bootstrap_token";
|
||||
pub const DEFAULT_SERVER_PORT: u16 = 50051;
|
||||
static HOME_OVERRIDE: LazyLock<RwLock<Option<PathBuf>>> = LazyLock::new(|| RwLock::new(None));
|
||||
|
||||
pub fn set_home_path_override(path: Option<PathBuf>) -> Result<(), std::io::Error> {
|
||||
let mut lock = HOME_OVERRIDE
|
||||
.write()
|
||||
.map_err(|_| std::io::Error::other("home path override lock poisoned"))?;
|
||||
*lock = path;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
||||
if let Some(path) = HOME_OVERRIDE
|
||||
.read()
|
||||
.map_err(|_| std::io::Error::other("home path override lock poisoned"))?
|
||||
.clone()
|
||||
{
|
||||
std::fs::create_dir_all(&path)?;
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
static ARBITER_HOME: &str = ".arbiter";
|
||||
let home_dir = std::env::home_dir().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::PermissionDenied,
|
||||
|
||||
@@ -53,11 +53,7 @@ spki.workspace = true
|
||||
alloy.workspace = true
|
||||
prost-types.workspace = true
|
||||
arbiter-tokens-registry.path = "../arbiter-tokens-registry"
|
||||
clap = { version = "4.6", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.46.3"
|
||||
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-service = "0.8"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use arbiter_proto::{
|
||||
ClientMetadata, format_challenge, transport::{Bi, expect_message}
|
||||
ClientMetadata, format_challenge,
|
||||
transport::{Bi, expect_message},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use diesel::{
|
||||
@@ -83,7 +84,6 @@ async fn get_client_and_nonce(
|
||||
})?;
|
||||
|
||||
conn.exclusive_transaction(|conn| {
|
||||
let pubkey_bytes = pubkey_bytes.clone();
|
||||
Box::pin(async move {
|
||||
let Some((client_id, current_nonce)) = program_client::table
|
||||
.filter(program_client::public_key.eq(&pubkey_bytes))
|
||||
@@ -290,7 +290,7 @@ where
|
||||
pub async fn authenticate<T>(
|
||||
props: &mut ClientConnection,
|
||||
transport: &mut T,
|
||||
) -> Result<VerifyingKey, Error>
|
||||
) -> Result<i32, Error>
|
||||
where
|
||||
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
||||
{
|
||||
@@ -318,7 +318,6 @@ where
|
||||
};
|
||||
|
||||
sync_client_metadata(&props.db, info.id, &metadata).await?;
|
||||
|
||||
challenge_client(transport, pubkey, info.current_nonce).await?;
|
||||
|
||||
transport
|
||||
@@ -329,5 +328,5 @@ where
|
||||
Error::Transport
|
||||
})?;
|
||||
|
||||
Ok(pubkey)
|
||||
Ok(info.id)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use kameo::actor::Spawn;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
actors::{GlobalActors, client::{ session::ClientSession}},
|
||||
actors::{GlobalActors, client::session::ClientSession},
|
||||
db,
|
||||
};
|
||||
|
||||
@@ -20,7 +20,10 @@ pub struct ClientConnection {
|
||||
|
||||
impl ClientConnection {
|
||||
pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||
Self { db, actors }
|
||||
Self {
|
||||
db,
|
||||
actors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +35,8 @@ where
|
||||
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send + ?Sized,
|
||||
{
|
||||
match auth::authenticate(&mut props, transport).await {
|
||||
Ok(_pubkey) => {
|
||||
ClientSession::spawn(ClientSession::new(props));
|
||||
Ok(client_id) => {
|
||||
ClientSession::spawn(ClientSession::new(props, client_id));
|
||||
info!("Client authenticated, session started");
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
use ed25519_dalek::VerifyingKey;
|
||||
use kameo::{Actor, messages};
|
||||
use tracing::error;
|
||||
|
||||
use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
|
||||
|
||||
use crate::{
|
||||
actors::{
|
||||
GlobalActors, client::ClientConnection, flow_coordinator::RegisterClient,
|
||||
GlobalActors,
|
||||
client::ClientConnection, flow_coordinator::RegisterClient,
|
||||
|
||||
evm::{ClientSignTransaction, SignTransactionError},
|
||||
keyholder::KeyHolderState,
|
||||
|
||||
},
|
||||
db,
|
||||
evm::VetError,
|
||||
};
|
||||
|
||||
pub struct ClientSession {
|
||||
props: ClientConnection,
|
||||
client_id: i32,
|
||||
}
|
||||
|
||||
impl ClientSession {
|
||||
pub(crate) fn new(props: ClientConnection) -> Self {
|
||||
Self { props }
|
||||
pub(crate) fn new(props: ClientConnection, client_id: i32) -> Self {
|
||||
Self { props, client_id }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +44,34 @@ impl ClientSession {
|
||||
|
||||
Ok(vault_state)
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub(crate) async fn handle_sign_transaction(
|
||||
&mut self,
|
||||
wallet_address: Address,
|
||||
transaction: TxEip1559,
|
||||
) -> Result<Signature, SignTransactionRpcError> {
|
||||
match self
|
||||
.props
|
||||
.actors
|
||||
.evm
|
||||
.ask(ClientSignTransaction {
|
||||
client_id: self.client_id,
|
||||
wallet_address,
|
||||
transaction,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(signature) => Ok(signature),
|
||||
Err(kameo::error::SendError::HandlerError(SignTransactionError::Vet(vet_error))) => {
|
||||
Err(SignTransactionRpcError::Vet(vet_error))
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to sign EVM transaction in client session");
|
||||
Err(SignTransactionRpcError::Internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for ClientSession {
|
||||
@@ -59,7 +96,7 @@ impl Actor for ClientSession {
|
||||
impl ClientSession {
|
||||
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||
let props = ClientConnection::new(db, actors);
|
||||
Self { props }
|
||||
Self { props, client_id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,3 +107,12 @@ pub enum Error {
|
||||
#[error("Internal error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SignTransactionRpcError {
|
||||
#[error("Policy evaluation failed")]
|
||||
Vet(#[from] VetError),
|
||||
|
||||
#[error("Internal error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use alloy::primitives::Address;
|
||||
use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
|
||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||
use diesel::sql_types::ops::Add;
|
||||
use diesel::{BoolExpressionMethods as _, ExpressionMethods as _, QueryDsl as _, SelectableHelper};
|
||||
@@ -23,7 +23,8 @@ use crate::safe_cell::SafeCell;
|
||||
use crate::{
|
||||
actors::{
|
||||
evm::{
|
||||
Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants,
|
||||
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
|
||||
UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants,
|
||||
},
|
||||
keyholder::{self, Bootstrap, TryUnseal},
|
||||
user_agent::session::{
|
||||
@@ -112,6 +113,15 @@ pub enum BootstrapError {
|
||||
General(#[from] super::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SignTransactionError {
|
||||
#[error("Policy evaluation failed")]
|
||||
Vet(#[from] crate::evm::VetError),
|
||||
|
||||
#[error("Internal signing error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[messages]
|
||||
impl UserAgentSession {
|
||||
#[message]
|
||||
@@ -356,6 +366,35 @@ impl UserAgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub(crate) async fn handle_sign_transaction(
|
||||
&mut self,
|
||||
client_id: i32,
|
||||
wallet_address: Address,
|
||||
transaction: TxEip1559,
|
||||
) -> Result<Signature, SignTransactionError> {
|
||||
match self
|
||||
.props
|
||||
.actors
|
||||
.evm
|
||||
.ask(ClientSignTransaction {
|
||||
client_id,
|
||||
wallet_address,
|
||||
transaction,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(signature) => Ok(signature),
|
||||
Err(SendError::HandlerError(EvmSignError::Vet(vet_error))) => {
|
||||
Err(SignTransactionError::Vet(vet_error))
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?err, "EVM sign transaction failed in user-agent session");
|
||||
Err(SignTransactionError::Internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub(crate) async fn handle_grant_evm_wallet_access(
|
||||
&mut self,
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
const DEFAULT_LISTEN_ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
arbiter_proto::DEFAULT_SERVER_PORT,
|
||||
));
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "arbiter-server")]
|
||||
#[command(about = "Arbiter gRPC server")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Run server in foreground mode.
|
||||
Run(RunArgs),
|
||||
/// Manage service lifecycle.
|
||||
Service {
|
||||
#[command(subcommand)]
|
||||
command: ServiceCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct RunArgs {
|
||||
#[arg(long, default_value_t = DEFAULT_LISTEN_ADDR)]
|
||||
pub listen_addr: SocketAddr,
|
||||
#[arg(long)]
|
||||
pub data_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for RunArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
listen_addr: DEFAULT_LISTEN_ADDR,
|
||||
data_dir: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum ServiceCommand {
|
||||
/// Install Windows service in Service Control Manager.
|
||||
Install(ServiceInstallArgs),
|
||||
/// Internal service entrypoint. SCM only.
|
||||
#[command(hide = true)]
|
||||
Run(ServiceRunArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct ServiceInstallArgs {
|
||||
#[arg(long)]
|
||||
pub start: bool,
|
||||
#[arg(long)]
|
||||
pub data_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct ServiceRunArgs {
|
||||
#[arg(long, default_value_t = DEFAULT_LISTEN_ADDR)]
|
||||
pub listen_addr: SocketAddr,
|
||||
#[arg(long)]
|
||||
pub data_dir: Option<PathBuf>,
|
||||
}
|
||||
@@ -32,7 +32,7 @@ mod utils;
|
||||
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||
pub enum PolicyError {
|
||||
#[error("Database error")]
|
||||
Error(#[from] crate::db::DatabaseError),
|
||||
Database(#[from] crate::db::DatabaseError),
|
||||
#[error("Transaction violates policy: {0:?}")]
|
||||
#[diagnostic(code(arbiter_server::evm::policy_error::violation))]
|
||||
Violations(Vec<EvalViolation>),
|
||||
|
||||
@@ -36,8 +36,8 @@ use super::{DatabaseID, EvalContext, EvalViolation};
|
||||
// Plain ether transfer
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Meaning {
|
||||
to: Address,
|
||||
value: U256,
|
||||
pub(crate) to: Address,
|
||||
pub(crate) value: U256,
|
||||
}
|
||||
impl Display for Meaning {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -38,9 +38,9 @@ fn grant_join() -> _ {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Meaning {
|
||||
token: &'static TokenInfo,
|
||||
to: Address,
|
||||
value: U256,
|
||||
pub(crate) token: &'static TokenInfo,
|
||||
pub(crate) to: Address,
|
||||
pub(crate) value: U256,
|
||||
}
|
||||
impl std::fmt::Display for Meaning {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
use alloy::primitives::Address;
|
||||
use arbiter_proto::{
|
||||
proto::client::{
|
||||
ClientRequest, ClientResponse, VaultState as ProtoVaultState,
|
||||
client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
proto::{
|
||||
client::{
|
||||
ClientRequest, ClientResponse, VaultState as ProtoVaultState,
|
||||
client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
},
|
||||
evm::{
|
||||
EvmError as ProtoEvmError, EvmSignTransactionResponse,
|
||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
||||
},
|
||||
},
|
||||
transport::{Receiver, Sender, grpc::GrpcBi},
|
||||
};
|
||||
@@ -17,11 +24,18 @@ use crate::{
|
||||
actors::{
|
||||
client::{
|
||||
self, ClientConnection,
|
||||
session::{ClientSession, Error, HandleQueryVaultState},
|
||||
session::{
|
||||
ClientSession, Error, HandleQueryVaultState, HandleSignTransaction,
|
||||
SignTransactionRpcError,
|
||||
},
|
||||
},
|
||||
keyholder::KeyHolderState,
|
||||
},
|
||||
grpc::request_tracker::RequestTracker,
|
||||
grpc::{
|
||||
Convert, TryConvert,
|
||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||
request_tracker::RequestTracker,
|
||||
},
|
||||
};
|
||||
|
||||
mod auth;
|
||||
@@ -34,7 +48,9 @@ async fn dispatch_loop(
|
||||
mut request_tracker: RequestTracker,
|
||||
) {
|
||||
loop {
|
||||
let Some(message) = bi.recv().await else { return };
|
||||
let Some(message) = bi.recv().await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let conn = match message {
|
||||
Ok(conn) => conn,
|
||||
@@ -53,16 +69,24 @@ async fn dispatch_loop(
|
||||
};
|
||||
|
||||
let Some(payload) = conn.payload else {
|
||||
let _ = bi.send(Err(Status::invalid_argument("Missing client request payload"))).await;
|
||||
let _ = bi
|
||||
.send(Err(Status::invalid_argument(
|
||||
"Missing client request payload",
|
||||
)))
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
match dispatch_inner(&actor, payload).await {
|
||||
Ok(response) => {
|
||||
if bi.send(Ok(ClientResponse {
|
||||
request_id: Some(request_id),
|
||||
payload: Some(response),
|
||||
})).await.is_err() {
|
||||
if bi
|
||||
.send(Ok(ClientResponse {
|
||||
request_id: Some(request_id),
|
||||
payload: Some(response),
|
||||
}))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -92,6 +116,47 @@ async fn dispatch_inner(
|
||||
};
|
||||
Ok(ClientResponsePayload::VaultState(state.into()))
|
||||
}
|
||||
ClientRequestPayload::EvmSignTransaction(request) => {
|
||||
let address: Address = RawEvmAddress(request.wallet_address).try_convert()?;
|
||||
let transaction = RawEvmTransaction(request.rlp_transaction).try_convert()?;
|
||||
|
||||
let response = match actor
|
||||
.ask(HandleSignTransaction {
|
||||
wallet_address: address,
|
||||
transaction,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(signature) => EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Signature(
|
||||
signature.as_bytes().to_vec(),
|
||||
)),
|
||||
},
|
||||
Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Vet(
|
||||
vet_error,
|
||||
))) => EvmSignTransactionResponse {
|
||||
result: Some(vet_error.convert()),
|
||||
},
|
||||
|
||||
Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Internal)) => {
|
||||
EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Error(
|
||||
ProtoEvmError::Internal.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(error = ?err, "Failed to sign EVM transaction");
|
||||
EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Error(
|
||||
ProtoEvmError::Internal.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ClientResponsePayload::EvmSignTransaction(response))
|
||||
}
|
||||
payload => {
|
||||
warn!(?payload, "Unsupported post-auth client request");
|
||||
Err(Status::invalid_argument("Unsupported client request"))
|
||||
@@ -102,14 +167,21 @@ async fn dispatch_inner(
|
||||
pub async fn start(mut conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
||||
let mut request_tracker = RequestTracker::default();
|
||||
|
||||
if let Err(e) = auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||
let mut transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
|
||||
let _ = transport.send(Err(e.clone())).await;
|
||||
warn!(error = ?e, "Client authentication failed");
|
||||
return;
|
||||
let client_id = match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
let _ = bi
|
||||
.send(Err(Status::unauthenticated(format!(
|
||||
"Authentication failed: {}",
|
||||
err
|
||||
))))
|
||||
.await;
|
||||
warn!(error = ?err, "Client authentication failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let actor = client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
|
||||
let actor = ClientSession::spawn(ClientSession::new(conn, client_id));
|
||||
let actor_for_cleanup = actor.clone();
|
||||
|
||||
info!("Client authenticated successfully");
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use arbiter_proto::{
|
||||
ClientMetadata, proto::client::{
|
||||
ClientMetadata,
|
||||
proto::client::{
|
||||
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||
ClientInfo as ProtoClientInfo, ClientRequest, ClientResponse,
|
||||
client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
}, transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}
|
||||
},
|
||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use tonic::Status;
|
||||
@@ -181,8 +183,7 @@ pub async fn start(
|
||||
conn: &mut ClientConnection,
|
||||
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
||||
request_tracker: &mut RequestTracker,
|
||||
) -> Result<(), auth::Error> {
|
||||
) -> Result<i32, auth::Error> {
|
||||
let mut transport = AuthTransportAdapter::new(bi, request_tracker);
|
||||
client::auth::authenticate(conn, &mut transport).await?;
|
||||
Ok(())
|
||||
client::auth::authenticate(conn, &mut transport).await
|
||||
}
|
||||
|
||||
2
server/crates/arbiter-server/src/grpc/common.rs
Normal file
2
server/crates/arbiter-server/src/grpc/common.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod inbound;
|
||||
pub mod outbound;
|
||||
36
server/crates/arbiter-server/src/grpc/common/inbound.rs
Normal file
36
server/crates/arbiter-server/src/grpc/common/inbound.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use alloy::{consensus::TxEip1559, primitives::Address, rlp::Decodable as _};
|
||||
|
||||
use crate::grpc::TryConvert;
|
||||
|
||||
pub struct RawEvmAddress(pub Vec<u8>);
|
||||
impl TryConvert for RawEvmAddress {
|
||||
type Output = Address;
|
||||
|
||||
type Error = tonic::Status;
|
||||
|
||||
fn try_convert(self) -> Result<Self::Output, Self::Error> {
|
||||
let wallet_address = match <[u8; 20]>::try_from(self.0.as_slice()) {
|
||||
Ok(address) => Address::from(address),
|
||||
Err(_) => {
|
||||
return Err(tonic::Status::invalid_argument(
|
||||
"Invalid EVM wallet address",
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(wallet_address)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawEvmTransaction(pub Vec<u8>);
|
||||
impl TryConvert for RawEvmTransaction {
|
||||
type Output = TxEip1559;
|
||||
|
||||
type Error = tonic::Status;
|
||||
|
||||
fn try_convert(mut self) -> Result<Self::Output, Self::Error> {
|
||||
let tx = TxEip1559::decode(&mut self.0.as_slice()).map_err(|_| {
|
||||
tonic::Status::invalid_argument("Invalid EVM transaction format")
|
||||
})?;
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
114
server/crates/arbiter-server/src/grpc/common/outbound.rs
Normal file
114
server/crates/arbiter-server/src/grpc/common/outbound.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use alloy::primitives::U256;
|
||||
use arbiter_proto::proto::evm::{
|
||||
EvalViolation as ProtoEvalViolation, EvmError as ProtoEvmError, GasLimitExceededViolation,
|
||||
NoMatchingGrantError, PolicyViolationsError, SpecificMeaning as ProtoSpecificMeaning,
|
||||
TokenInfo as ProtoTokenInfo, TransactionEvalError as ProtoTransactionEvalError,
|
||||
eval_violation::Kind as ProtoEvalViolationKind,
|
||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
||||
specific_meaning::Meaning as ProtoSpecificMeaningKind,
|
||||
transaction_eval_error::Kind as ProtoTransactionEvalErrorKind,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
evm::{
|
||||
PolicyError, VetError,
|
||||
policies::{EvalViolation, SpecificMeaning},
|
||||
},
|
||||
grpc::Convert,
|
||||
};
|
||||
|
||||
fn u256_to_proto_bytes(value: U256) -> Vec<u8> {
|
||||
value.to_be_bytes::<32>().to_vec()
|
||||
}
|
||||
|
||||
impl Convert for SpecificMeaning {
|
||||
type Output = ProtoSpecificMeaning;
|
||||
|
||||
fn convert(self) -> Self::Output {
|
||||
let kind = match self {
|
||||
SpecificMeaning::EtherTransfer(meaning) => ProtoSpecificMeaningKind::EtherTransfer(
|
||||
arbiter_proto::proto::evm::EtherTransferMeaning {
|
||||
to: meaning.to.to_vec(),
|
||||
value: u256_to_proto_bytes(meaning.value),
|
||||
},
|
||||
),
|
||||
SpecificMeaning::TokenTransfer(meaning) => ProtoSpecificMeaningKind::TokenTransfer(
|
||||
arbiter_proto::proto::evm::TokenTransferMeaning {
|
||||
token: Some(ProtoTokenInfo {
|
||||
symbol: meaning.token.symbol.to_string(),
|
||||
address: meaning.token.contract.to_vec(),
|
||||
chain_id: meaning.token.chain,
|
||||
}),
|
||||
to: meaning.to.to_vec(),
|
||||
value: u256_to_proto_bytes(meaning.value),
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
ProtoSpecificMeaning {
|
||||
meaning: Some(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Convert for EvalViolation {
|
||||
type Output = ProtoEvalViolation;
|
||||
|
||||
fn convert(self) -> Self::Output {
|
||||
let kind = match self {
|
||||
EvalViolation::InvalidTarget { target } => {
|
||||
ProtoEvalViolationKind::InvalidTarget(target.to_vec())
|
||||
}
|
||||
EvalViolation::GasLimitExceeded {
|
||||
max_gas_fee_per_gas,
|
||||
max_priority_fee_per_gas,
|
||||
} => ProtoEvalViolationKind::GasLimitExceeded(GasLimitExceededViolation {
|
||||
max_gas_fee_per_gas: max_gas_fee_per_gas.map(u256_to_proto_bytes),
|
||||
max_priority_fee_per_gas: max_priority_fee_per_gas.map(u256_to_proto_bytes),
|
||||
}),
|
||||
EvalViolation::RateLimitExceeded => ProtoEvalViolationKind::RateLimitExceeded(()),
|
||||
EvalViolation::VolumetricLimitExceeded => {
|
||||
ProtoEvalViolationKind::VolumetricLimitExceeded(())
|
||||
}
|
||||
EvalViolation::InvalidTime => ProtoEvalViolationKind::InvalidTime(()),
|
||||
EvalViolation::InvalidTransactionType => {
|
||||
ProtoEvalViolationKind::InvalidTransactionType(())
|
||||
}
|
||||
};
|
||||
|
||||
ProtoEvalViolation { kind: Some(kind) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Convert for VetError {
|
||||
type Output = EvmSignTransactionResult;
|
||||
|
||||
fn convert(self) -> Self::Output {
|
||||
let kind = match self {
|
||||
VetError::ContractCreationNotSupported => {
|
||||
ProtoTransactionEvalErrorKind::ContractCreationNotSupported(())
|
||||
}
|
||||
VetError::UnsupportedTransactionType => {
|
||||
ProtoTransactionEvalErrorKind::UnsupportedTransactionType(())
|
||||
}
|
||||
VetError::Evaluated(meaning, policy_error) => match policy_error {
|
||||
PolicyError::NoMatchingGrant => {
|
||||
ProtoTransactionEvalErrorKind::NoMatchingGrant(NoMatchingGrantError {
|
||||
meaning: Some(meaning.convert()),
|
||||
})
|
||||
}
|
||||
PolicyError::Violations(violations) => {
|
||||
ProtoTransactionEvalErrorKind::PolicyViolations(PolicyViolationsError {
|
||||
meaning: Some(meaning.convert()),
|
||||
violations: violations.into_iter().map(Convert::convert).collect(),
|
||||
})
|
||||
}
|
||||
PolicyError::Database(_) => {
|
||||
return EvmSignTransactionResult::Error(ProtoEvmError::Internal.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
EvmSignTransactionResult::EvalError(ProtoTransactionEvalError { kind: Some(kind) }.into())
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,13 @@ use crate::{
|
||||
grpc::user_agent::start,
|
||||
};
|
||||
|
||||
pub mod client;
|
||||
mod request_tracker;
|
||||
|
||||
pub mod client;
|
||||
pub mod user_agent;
|
||||
|
||||
mod common;
|
||||
|
||||
pub trait Convert {
|
||||
type Output;
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ use arbiter_proto::{
|
||||
evm::{
|
||||
EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
|
||||
EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
|
||||
GrantEntry, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse,
|
||||
evm_grant_create_response::Result as EvmGrantCreateResult,
|
||||
EvmSignTransactionResponse, GrantEntry, WalletCreateResponse, WalletEntry, WalletList,
|
||||
WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult,
|
||||
evm_grant_delete_response::Result as EvmGrantDeleteResult,
|
||||
evm_grant_list_response::Result as EvmGrantListResult,
|
||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
||||
wallet_create_response::Result as WalletCreateResult,
|
||||
wallet_list_response::Result as WalletListResult,
|
||||
},
|
||||
@@ -22,8 +23,8 @@ use arbiter_proto::{
|
||||
SdkClientGrantWalletAccess, SdkClientList as ProtoSdkClientList,
|
||||
SdkClientListResponse as ProtoSdkClientListResponse, SdkClientRevokeWalletAccess,
|
||||
SdkClientWalletAccess, UnsealEncryptedKey as ProtoUnsealEncryptedKey,
|
||||
UnsealResult as ProtoUnsealResult, UnsealStart, UserAgentRequest, UserAgentResponse,
|
||||
VaultState as ProtoVaultState,
|
||||
UnsealResult as ProtoUnsealResult, UnsealStart, UserAgentEvmSignTransactionRequest,
|
||||
UserAgentRequest, UserAgentResponse, VaultState as ProtoVaultState,
|
||||
sdk_client_list_response::Result as ProtoSdkClientListResult,
|
||||
user_agent_request::Payload as UserAgentRequestPayload,
|
||||
user_agent_response::Payload as UserAgentResponsePayload,
|
||||
@@ -49,12 +50,24 @@ use crate::{
|
||||
HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete,
|
||||
HandleGrantEvmWalletAccess, HandleGrantList, HandleListWalletAccess,
|
||||
HandleNewClientApprove, HandleQueryVaultState, HandleRevokeEvmWalletAccess,
|
||||
HandleSdkClientList, HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
||||
HandleSdkClientList, HandleSignTransaction, HandleUnsealEncryptedKey,
|
||||
HandleUnsealRequest, SignTransactionError as SessionSignTransactionError,
|
||||
UnsealError,
|
||||
},
|
||||
},
|
||||
},
|
||||
db::models::{CoreEvmWalletAccess, NewEvmWalletAccess},
|
||||
grpc::{Convert, TryConvert, request_tracker::RequestTracker},
|
||||
evm::{PolicyError, VetError, policies::EvalViolation},
|
||||
grpc::{
|
||||
Convert, TryConvert,
|
||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||
request_tracker::RequestTracker,
|
||||
},
|
||||
};
|
||||
use alloy::{
|
||||
consensus::TxEip1559,
|
||||
primitives::{Address, U256},
|
||||
rlp::Decodable,
|
||||
};
|
||||
mod auth;
|
||||
mod inbound;
|
||||
@@ -178,7 +191,6 @@ async fn dispatch_inner(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::UnsealEncryptedKey(ProtoUnsealEncryptedKey {
|
||||
nonce,
|
||||
ciphertext,
|
||||
@@ -203,7 +215,6 @@ async fn dispatch_inner(
|
||||
};
|
||||
UserAgentResponsePayload::UnsealResult(result.into())
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::BootstrapEncryptedKey(ProtoBootstrapEncryptedKey {
|
||||
nonce,
|
||||
ciphertext,
|
||||
@@ -231,7 +242,6 @@ async fn dispatch_inner(
|
||||
};
|
||||
UserAgentResponsePayload::BootstrapResult(result.into())
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::QueryVaultState(_) => {
|
||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||
@@ -244,7 +254,6 @@ async fn dispatch_inner(
|
||||
};
|
||||
UserAgentResponsePayload::VaultState(state.into())
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::EvmWalletCreate(_) => {
|
||||
let result = match actor.ask(HandleEvmWalletCreate {}).await {
|
||||
Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry {
|
||||
@@ -260,7 +269,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::EvmWalletList(_) => {
|
||||
let result = match actor.ask(HandleEvmWalletList {}).await {
|
||||
Ok(wallets) => WalletListResult::Wallets(WalletList {
|
||||
@@ -281,7 +289,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::EvmGrantList(_) => {
|
||||
let result = match actor.ask(HandleGrantList {}).await {
|
||||
Ok(grants) => EvmGrantListResult::Grants(EvmGrantList {
|
||||
@@ -304,7 +311,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::EvmGrantCreate(EvmGrantCreateRequest { shared, specific }) => {
|
||||
let basic = shared
|
||||
.ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))?
|
||||
@@ -324,7 +330,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::EvmGrantDelete(EvmGrantDeleteRequest { grant_id }) => {
|
||||
let result = match actor.ask(HandleGrantDelete { grant_id }).await {
|
||||
Ok(()) => EvmGrantDeleteResult::Ok(()),
|
||||
@@ -337,7 +342,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::SdkClientConnectionResponse(resp) => {
|
||||
let pubkey_bytes = <[u8; 32]>::try_from(resp.pubkey)
|
||||
.map_err(|_| Status::invalid_argument("Invalid Ed25519 public key length"))?;
|
||||
@@ -357,9 +361,7 @@ async fn dispatch_inner(
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::SdkClientRevoke(_) => todo!(),
|
||||
|
||||
UserAgentRequestPayload::SdkClientList(_) => {
|
||||
let result = match actor.ask(HandleSdkClientList {}).await {
|
||||
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
|
||||
@@ -386,7 +388,6 @@ async fn dispatch_inner(
|
||||
result: Some(result),
|
||||
})
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::GrantWalletAccess(SdkClientGrantWalletAccess { accesses }) => {
|
||||
let entries: Vec<NewEvmWalletAccess> =
|
||||
accesses.into_iter().map(|a| a.convert()).collect();
|
||||
@@ -402,9 +403,11 @@ async fn dispatch_inner(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::RevokeWalletAccess(SdkClientRevokeWalletAccess { accesses }) => {
|
||||
match actor.ask(HandleRevokeEvmWalletAccess { entries: accesses }).await {
|
||||
match actor
|
||||
.ask(HandleRevokeEvmWalletAccess { entries: accesses })
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
info!("Successfully revoked wallet access");
|
||||
return Ok(None);
|
||||
@@ -415,7 +418,6 @@ async fn dispatch_inner(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::ListWalletAccess(_) => {
|
||||
let result = match actor.ask(HandleListWalletAccess {}).await {
|
||||
Ok(accesses) => ListWalletAccessResponse {
|
||||
@@ -428,12 +430,59 @@ async fn dispatch_inner(
|
||||
};
|
||||
UserAgentResponsePayload::ListWalletAccessResponse(result)
|
||||
}
|
||||
|
||||
UserAgentRequestPayload::AuthChallengeRequest(..)
|
||||
| UserAgentRequestPayload::AuthChallengeSolution(..) => {
|
||||
warn!(?payload, "Unsupported post-auth user agent request");
|
||||
return Err(Status::invalid_argument("Unsupported user-agent request"));
|
||||
}
|
||||
UserAgentRequestPayload::EvmSignTransaction(UserAgentEvmSignTransactionRequest {
|
||||
client_id,
|
||||
request,
|
||||
}) => {
|
||||
let Some(request) = request else {
|
||||
warn!("Missing transaction signing request");
|
||||
return Err(Status::invalid_argument(
|
||||
"Missing transaction signing request",
|
||||
));
|
||||
};
|
||||
|
||||
let address: Address = RawEvmAddress(request.wallet_address).try_convert()?;
|
||||
let transaction = RawEvmTransaction(request.rlp_transaction).try_convert()?;
|
||||
|
||||
let response = match actor
|
||||
.ask(HandleSignTransaction {
|
||||
client_id,
|
||||
wallet_address: address,
|
||||
transaction,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(signature) => EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Signature(
|
||||
signature.as_bytes().to_vec(),
|
||||
)),
|
||||
},
|
||||
Err(SendError::HandlerError(SessionSignTransactionError::Vet(vet_error))) => {
|
||||
EvmSignTransactionResponse { result: Some(vet_error.convert()) }
|
||||
}
|
||||
Err(SendError::HandlerError(SessionSignTransactionError::Internal)) => {
|
||||
EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Error(
|
||||
ProtoEvmError::Internal.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(error = ?err, "Failed to sign EVM transaction via user-agent");
|
||||
EvmSignTransactionResponse {
|
||||
result: Some(EvmSignTransactionResult::Error(
|
||||
ProtoEvmError::Internal.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
UserAgentResponsePayload::EvmSignTransaction(response)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(response))
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
||||
use miette::miette;
|
||||
use tonic::transport::{Identity, ServerTlsConfig};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{actors::bootstrap::GetToken, context::ServerContext};
|
||||
use crate::context::ServerContext;
|
||||
|
||||
pub mod actors;
|
||||
pub mod context;
|
||||
@@ -26,64 +18,3 @@ impl Server {
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RunConfig {
|
||||
pub addr: SocketAddr,
|
||||
pub data_dir: Option<PathBuf>,
|
||||
pub log_arbiter_url: bool,
|
||||
}
|
||||
|
||||
impl RunConfig {
|
||||
pub fn new(addr: SocketAddr, data_dir: Option<PathBuf>) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
data_dir,
|
||||
log_arbiter_url: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_server_until_shutdown<F>(config: RunConfig, shutdown: F) -> miette::Result<()>
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
arbiter_proto::set_home_path_override(config.data_dir.clone())
|
||||
.map_err(|err| miette!("failed to set home path override: {err}"))?;
|
||||
|
||||
let db = db::create_pool(None).await?;
|
||||
info!(addr = %config.addr, "Database ready");
|
||||
|
||||
let context = ServerContext::new(db).await?;
|
||||
info!(addr = %config.addr, "Server context ready");
|
||||
|
||||
if config.log_arbiter_url {
|
||||
let url = ArbiterUrl {
|
||||
host: config.addr.ip().to_string(),
|
||||
port: config.addr.port(),
|
||||
ca_cert: context.tls.ca_cert().clone().into_owned(),
|
||||
bootstrap_token: context
|
||||
.actors
|
||||
.bootstrapper
|
||||
.ask(GetToken)
|
||||
.await
|
||||
.map_err(|err| miette!("failed to get bootstrap token from actor: {err}"))?,
|
||||
};
|
||||
info!(%url, "Server URL");
|
||||
}
|
||||
|
||||
let tls = ServerTlsConfig::new().identity(Identity::from_pem(
|
||||
context.tls.cert_pem(),
|
||||
context.tls.key_pem(),
|
||||
));
|
||||
|
||||
tonic::transport::Server::builder()
|
||||
.tls_config(tls)
|
||||
.map_err(|err| miette!("Failed to setup TLS: {err}"))?
|
||||
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
||||
.serve_with_shutdown(config.addr, shutdown)
|
||||
.await
|
||||
.map_err(|e| miette!("gRPC server error: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,42 +1,56 @@
|
||||
mod cli;
|
||||
mod service;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Command, RunArgs, ServiceCommand};
|
||||
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
||||
use arbiter_server::{Server, actors::bootstrap::GetToken, context::ServerContext, db};
|
||||
use miette::miette;
|
||||
use rustls::crypto::aws_lc_rs;
|
||||
use tonic::transport::{Identity, ServerTlsConfig};
|
||||
use tracing::info;
|
||||
|
||||
const PORT: u16 = 50051;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> miette::Result<()> {
|
||||
aws_lc_rs::default_provider().install_default().unwrap();
|
||||
init_logging();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
None => run_foreground(RunArgs::default()).await,
|
||||
Some(Command::Run(args)) => run_foreground(args).await,
|
||||
Some(Command::Service { command }) => match command {
|
||||
ServiceCommand::Install(args) => service::install_service(args),
|
||||
ServiceCommand::Run(args) => service::run_service_dispatcher(args),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_foreground(args: RunArgs) -> miette::Result<()> {
|
||||
info!(addr = %args.listen_addr, "Starting arbiter server");
|
||||
arbiter_server::run_server_until_shutdown(
|
||||
arbiter_server::RunConfig::new(args.listen_addr, args.data_dir),
|
||||
std::future::pending::<()>(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn init_logging() {
|
||||
let _ = tracing_subscriber::fmt()
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.try_init();
|
||||
.init();
|
||||
|
||||
info!("Starting arbiter server");
|
||||
|
||||
let db = db::create_pool(None).await?;
|
||||
info!("Database ready");
|
||||
|
||||
let context = ServerContext::new(db).await?;
|
||||
|
||||
let addr: SocketAddr = format!("127.0.0.1:{PORT}").parse().expect("valid address");
|
||||
info!(%addr, "Starting gRPC server");
|
||||
|
||||
let url = ArbiterUrl {
|
||||
host: addr.ip().to_string(),
|
||||
port: addr.port(),
|
||||
ca_cert: context.tls.ca_cert().clone().into_owned(),
|
||||
bootstrap_token: context.actors.bootstrapper.ask(GetToken).await.unwrap(),
|
||||
};
|
||||
|
||||
info!(%url, "Server URL");
|
||||
|
||||
let tls = ServerTlsConfig::new().identity(Identity::from_pem(
|
||||
context.tls.cert_pem(),
|
||||
context.tls.key_pem(),
|
||||
));
|
||||
|
||||
tonic::transport::Server::builder()
|
||||
.tls_config(tls)
|
||||
.map_err(|err| miette!("Faild to setup TLS: {err}"))?
|
||||
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
||||
.serve(addr)
|
||||
.await
|
||||
.map_err(|e| miette::miette!("gRPC server error: {e}"))?;
|
||||
|
||||
unreachable!("gRPC server should run indefinitely");
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::{install_service, run_service_dispatcher};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn install_service(_: crate::cli::ServiceInstallArgs) -> miette::Result<()> {
|
||||
Err(miette::miette!(
|
||||
"service install is currently supported only on Windows"
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn run_service_dispatcher(_: crate::cli::ServiceRunArgs) -> miette::Result<()> {
|
||||
Err(miette::miette!(
|
||||
"service run entrypoint is currently supported only on Windows"
|
||||
))
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use miette::{Context as _, IntoDiagnostic as _, miette};
|
||||
use windows_service::{
|
||||
define_windows_service,
|
||||
service::{
|
||||
ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
|
||||
ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
|
||||
},
|
||||
service_control_handler::{self, ServiceControlHandlerResult},
|
||||
service_dispatcher,
|
||||
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
};
|
||||
|
||||
use crate::cli::{ServiceInstallArgs, ServiceRunArgs};
|
||||
use arbiter_server::{RunConfig, run_server_until_shutdown};
|
||||
|
||||
const SERVICE_NAME: &str = "ArbiterServer";
|
||||
const SERVICE_DISPLAY_NAME: &str = "Arbiter Server";
|
||||
|
||||
pub fn default_service_data_dir() -> PathBuf {
|
||||
let base = std::env::var_os("PROGRAMDATA")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"));
|
||||
base.join("Arbiter")
|
||||
}
|
||||
|
||||
pub fn install_service(args: ServiceInstallArgs) -> miette::Result<()> {
|
||||
ensure_admin_rights()?;
|
||||
|
||||
let executable = std::env::current_exe().into_diagnostic()?;
|
||||
let data_dir = args.data_dir.unwrap_or_else(default_service_data_dir);
|
||||
|
||||
std::fs::create_dir_all(&data_dir)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("failed to create service data dir: {}", data_dir.display()))?;
|
||||
ensure_token_acl_contract(&data_dir)?;
|
||||
|
||||
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
|
||||
let manager = ServiceManager::local_computer(None::<&str>, manager_access)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to open Service Control Manager")?;
|
||||
|
||||
let launch_arguments = vec![
|
||||
OsString::from("service"),
|
||||
OsString::from("run"),
|
||||
OsString::from("--data-dir"),
|
||||
data_dir.as_os_str().to_os_string(),
|
||||
];
|
||||
|
||||
let service_info = ServiceInfo {
|
||||
name: OsString::from(SERVICE_NAME),
|
||||
display_name: OsString::from(SERVICE_DISPLAY_NAME),
|
||||
service_type: ServiceType::OWN_PROCESS,
|
||||
start_type: ServiceStartType::AutoStart,
|
||||
error_control: ServiceErrorControl::Normal,
|
||||
executable_path: executable,
|
||||
launch_arguments,
|
||||
dependencies: vec![],
|
||||
account_name: Some(OsString::from(r"NT AUTHORITY\LocalService")),
|
||||
account_password: None,
|
||||
};
|
||||
|
||||
let service = manager
|
||||
.create_service(
|
||||
&service_info,
|
||||
ServiceAccess::QUERY_STATUS | ServiceAccess::START,
|
||||
)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to create Windows service in SCM")?;
|
||||
|
||||
if args.start {
|
||||
service
|
||||
.start::<&str>(&[])
|
||||
.into_diagnostic()
|
||||
.wrap_err("service created but failed to start")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_service_dispatcher(args: ServiceRunArgs) -> miette::Result<()> {
|
||||
SERVICE_RUN_ARGS
|
||||
.set(args)
|
||||
.map_err(|_| miette!("service runtime args are already initialized"))?;
|
||||
|
||||
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to start service dispatcher")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
define_windows_service!(ffi_service_main, service_main);
|
||||
|
||||
static SERVICE_RUN_ARGS: std::sync::OnceLock<ServiceRunArgs> = std::sync::OnceLock::new();
|
||||
|
||||
fn service_main(_arguments: Vec<OsString>) {
|
||||
if let Err(error) = run_service_main() {
|
||||
tracing::error!(error = ?error, "Windows service main failed");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_service_main() -> miette::Result<()> {
|
||||
let args = SERVICE_RUN_ARGS
|
||||
.get()
|
||||
.cloned()
|
||||
.ok_or_else(|| miette!("service run args are missing"))?;
|
||||
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>();
|
||||
|
||||
let status_handle =
|
||||
service_control_handler::register(SERVICE_NAME, move |control| match control {
|
||||
ServiceControl::Stop => {
|
||||
let _ = shutdown_tx.send(());
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
_ => ServiceControlHandlerResult::NotImplemented,
|
||||
})
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to register service control handler")?;
|
||||
|
||||
set_status(
|
||||
&status_handle,
|
||||
ServiceState::StartPending,
|
||||
ServiceControlAccept::empty(),
|
||||
)?;
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to build tokio runtime for service")?;
|
||||
|
||||
set_status(
|
||||
&status_handle,
|
||||
ServiceState::Running,
|
||||
ServiceControlAccept::STOP,
|
||||
)?;
|
||||
|
||||
let data_dir = args.data_dir.unwrap_or_else(default_service_data_dir);
|
||||
let config = RunConfig {
|
||||
addr: args.listen_addr,
|
||||
data_dir: Some(data_dir),
|
||||
log_arbiter_url: true,
|
||||
};
|
||||
|
||||
let result = runtime.block_on(run_server_until_shutdown(config, async move {
|
||||
let _ = tokio::task::spawn_blocking(move || shutdown_rx.recv()).await;
|
||||
}));
|
||||
|
||||
set_status(
|
||||
&status_handle,
|
||||
ServiceState::Stopped,
|
||||
ServiceControlAccept::empty(),
|
||||
)?;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn set_status(
|
||||
status_handle: &service_control_handler::ServiceStatusHandle,
|
||||
current_state: ServiceState,
|
||||
controls_accepted: ServiceControlAccept,
|
||||
) -> miette::Result<()> {
|
||||
status_handle
|
||||
.set_service_status(ServiceStatus {
|
||||
service_type: ServiceType::OWN_PROCESS,
|
||||
current_state,
|
||||
controls_accepted,
|
||||
exit_code: ServiceExitCode::Win32(0),
|
||||
checkpoint: 0,
|
||||
wait_hint: Duration::from_secs(10),
|
||||
process_id: None,
|
||||
})
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to update service state")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_admin_rights() -> miette::Result<()> {
|
||||
let status = Command::new("net")
|
||||
.arg("session")
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to check administrator rights")?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(miette!(
|
||||
"administrator privileges are required to install Windows service"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_token_acl_contract(data_dir: &Path) -> miette::Result<()> {
|
||||
// IMPORTANT: Keep this ACL setup explicit.
|
||||
// The service account needs write access, while the interactive user only needs read access
|
||||
// to the bootstrap token and service data directory.
|
||||
let target = data_dir.as_os_str();
|
||||
|
||||
let status = Command::new("icacls")
|
||||
.arg(target)
|
||||
.arg("/grant")
|
||||
.arg("*S-1-5-19:(OI)(CI)M")
|
||||
.arg("/grant")
|
||||
.arg("*S-1-5-32-545:(OI)(CI)RX")
|
||||
.arg("/T")
|
||||
.arg("/C")
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to apply ACLs for service data directory")?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(miette!(
|
||||
"failed to ensure ACL contract for service data directory"
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user