Compare commits

...

10 Commits

Author SHA1 Message Date
871e26edc2
fix app price calculation
fix overflow when calculating account escrow
calculates app price per minute based on resource usage
2025-07-01 19:21:26 +05:30
2fe8d38156
Merge branch 'app_payment' into credits_app 2025-07-01 12:23:05 +05:30
30eef43aa5
Merge branch 'credits-v2' into credits_app
fixed all mb gb issues on migration and test
2025-06-30 23:32:01 +05:30
ddba7a4f95
switch from LP to credits
As part of open sourcing the software product, we should consider that
loyalty points are not the best language. Switching to "credits" makes
sense from a lot of points of view.

At the same time, this change allows an achitectural change towards
slots. Slots allow daemon resources to get booked based on the HW ratio
configured in the daemon config.
2025-06-27 15:47:42 +03:00
2b6151d12f
Adds vcpus, memory_mib, and disk_mib fields to the AppNodeListResp 2025-06-26 18:28:40 +05:30
547246629b
cargo fmt 2025-06-25 18:57:01 +05:30
46ba0961bf
Refactor app engine resource units
updated app proto file to mib
fixed all tests and migrations
removed unused price calculation functions
modified mock data
2025-06-25 18:51:22 +05:30
1ca66f3bc3
switch from LP to credits
As part of open sourcing the software product, we should consider that
loyalty points are not the best language. Switching to "credits" makes
sense from a lot of points of view.

At the same time, this change allows an achitectural change towards
slots. Slots allow daemon resources to get booked based on the HW ratio
configured in the daemon config.
2025-06-25 03:51:02 +03:00
41b6b9b9d0
Minor fix on app node fields
Remove offline_minutes from AppNodeWithReports and update query condition for active app node filter
2025-06-23 15:46:49 +05:30
dcacd7b6b0
App contract payment
implement online/offline status updates
add timer to charge app contract
modified app_node in schema
2025-06-20 16:08:06 +05:30
25 changed files with 594 additions and 624 deletions

2
Cargo.lock generated

@ -1011,7 +1011,7 @@ dependencies = [
[[package]]
name = "detee-shared"
version = "0.1.0"
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#0b195b4589e4ec689af7ddca27dc051716ecee78"
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=credits_app#01e93d3a2e4502c0e8e72026e8a1c55810961815"
dependencies = [
"bincode 2.0.1",
"prost",

@ -15,7 +15,7 @@ serde_yaml = "0.9.34"
surrealdb = "2.2.2"
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
tonic = { version = "0.12", features = ["tls"] }
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain_app" }
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "credits_app" }
ed25519-dalek = "2.1.1"
bs58 = "0.5.1"
tokio-stream = "0.1.17"

@ -1,80 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
accounts:
DXXkYSnhP3ijsHYxkedcuMomEyc122WaAbkDX7SaGuUS:
balance: 20293420000
fY3NNjvFTeR1FBh5nXV3ujX7zZqrm3eBUWGEiG75TK1:
balance: 1000000000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL:
balance: 25949200000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
Cnkvn3WuHYfTzh1YK1TAv2VD25sNvstJNnQtxjcdQSL7:
balance: 4794480000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS:
balance: 4672207240000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
E3bgXsWvgichXeC6AqULJCZDp7FbEdTxBD67UaYVWf9y:
balance: 21121600000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
HQyGWpiteHbxjszngZvmiX7ZFZAmF6nFjEraBa1M6bbM:
balance: 979410300000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc:
balance: 976000000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
45Pyv9hRfub43NyRrYv95MhZs1Wrm8sj3RhBvA3F1Bvr:
balance: 1670441080000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
49JBVzmgsQbUURHzAWax2gxo6jmukqbEQzP97YeeNQyu:
balance: 1076960680000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB:
balance: 3271040000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK:
balance: 554454460000
tmp_locked: 547200000
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
3BNggj8ZTsoSjfAGdPfmcU2Gobm2qcTEBg9iHXEUPe1t:
balance: 9978460000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
B981xPHmHthfKr15J9uJ64qd9zt2KsdiEuDRR7UUCGWi:
balance: 99980200000
FBMWVqME3t1i4R6zWyDQGUuiTeruZ1TxLhTmhaEcFypZ:
balance: 181560160000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
@ -91,352 +23,316 @@ accounts:
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
fY3NNjvFTeR1FBh5nXV3ujX7zZqrm3eBUWGEiG75TK1:
balance: 1000000000
49JBVzmgsQbUURHzAWax2gxo6jmukqbEQzP97YeeNQyu:
balance: 1076960680000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
FBMWVqME3t1i4R6zWyDQGUuiTeruZ1TxLhTmhaEcFypZ:
balance: 181560160000
HQyGWpiteHbxjszngZvmiX7ZFZAmF6nFjEraBa1M6bbM:
balance: 979410300000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX:
E3bgXsWvgichXeC6AqULJCZDp7FbEdTxBD67UaYVWf9y:
balance: 21121600000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL:
balance: 1156240000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
45Pyv9hRfub43NyRrYv95MhZs1Wrm8sj3RhBvA3F1Bvr:
balance: 933585660000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc:
balance: 109066280000
tmp_locked: 453600000
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK:
balance: 2565079420000
tmp_locked: 547200000
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB:
balance: 7063640000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS:
balance: 13535509680000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
E27C967A84DEAA3339B4D57C1A7321E4906772244BBECCE25356D0EA6F851086:
balance: 100000000000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
DwfL5iFu32xh2YMCUxg63OeaThLRqehDAumiP9q6zuuX:
balance: 74660380000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
DXXkYSnhP3ijsHYxkedcuMomEyc122WaAbkDX7SaGuUS:
balance: 20293420000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
Bb5Xfkk4fc5i4GiTEgChwMb1ToWDQ5uzGtgD6yKTQYAy:
balance: 99979600000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
5hx2f3odEx6sXqCY6FEAv6bBm3BXdhJ97G6X7uScsLAj:
balance: 94473640000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
3BNggj8ZTsoSjfAGdPfmcU2Gobm2qcTEBg9iHXEUPe1t:
balance: 9978460000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
B981xPHmHthfKr15J9uJ64qd9zt2KsdiEuDRR7UUCGWi:
balance: 99980200000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
Cnkvn3WuHYfTzh1YK1TAv2VD25sNvstJNnQtxjcdQSL7:
balance: 11021340000
tmp_locked: 0
kicked_for: []
last_kick: 1970-01-01T00:00:00Z
banned_by: []
operators:
BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS:
escrow: 5096692000000
email: first_on_detee@proton.me
banned_users: []
vm_nodes:
- HiyMp21zaBVbRCjDsD5hEjQnHeHv4e1gpUR6pVfHTKqv
- 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
- Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
- 4QbUXDM915RUFnHm3NiysLXFLk1WRGZvABwLNzx4tTEW
- DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
app_nodes: []
x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK:
escrow: 5499700480000
escrow: 5489633280000
email: gheo@detee.ltd
banned_users: []
vm_nodes:
- 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f
- 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9
- 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f
- 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9
app_nodes: []
BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS:
escrow: 5091906400000
email: first_on_detee@proton.me
banned_users: []
vm_nodes:
- DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
- 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
- Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
app_nodes: []
7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB:
escrow: 888888888899999
email: ""
escrow: 5500000000000
email: nmohammed@detee.ltd
banned_users: []
vm_nodes: []
app_nodes:
- BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
- BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
vm_nodes:
- public_key: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9
operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK
country: GB
region: England
city: London
ip: 173.234.17.2
avail_mem_mb: 26000
avail_vcpus: 28
avail_storage_gbs: 680
avail_ipv4: 2
avail_ipv6: 65516
avail_ports: 19999
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: FR
region: Île-de-France
city: Paris
ip: 156.146.63.215
avail_mem_mb: 123000
avail_vcpus: 46
avail_storage_gbs: 440
avail_ipv4: 2
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f
operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK
country: CA
region: Quebec
city: Montréal
ip: 184.107.169.199
avail_mem_mb: 30000
avail_vcpus: 31
avail_storage_gbs: 700
avail_ipv4: 0
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 18000
reports: {}
offline_minutes: 0
- public_key: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: CA
region: British Columbia
city: Vancouver
ip: 149.22.95.1
avail_mem_mb: 109000
avail_vcpus: 45
avail_storage_gbs: 400
avail_ipv4: 25
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: US
region: California
city: San Jose
ip: 149.36.48.99
avail_mem_mb: 120000
avail_vcpus: 41
avail_storage_gbs: 390
avail_ipv4: 23
avail_ipv6: 0
avail_ports: 19999
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: HiyMp21zaBVbRCjDsD5hEjQnHeHv4e1gpUR6pVfHTKqv
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: CA
region: British Columbia
city: Vancouver
ip: 149.22.95.28
avail_mem_mb: 125000
avail_vcpus: 46
avail_storage_gbs: 400
avail_ipv4: 26
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: FR
region: Île-de-France
city: Paris
ip: 156.146.63.215
avail_mem_mb: 117000
avail_vcpus: 40
avail_storage_gbs: 410
avail_ipv4: 2
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: US
region: California
city: San Jose
ip: 149.36.48.99
avail_mem_mb: 121000
avail_vcpus: 42
avail_storage_gbs: 400
avail_ipv4: 23
avail_ipv6: 0
avail_ports: 20000
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS
country: CA
region: British Columbia
city: Vancouver
ip: 149.22.95.1
avail_mem_mb: 106400
avail_vcpus: 42
avail_storage_gbs: 400
avail_ipv4: 25
avail_ipv6: 0
avail_ports: 19999
max_ports_per_vm: 5
price: 20000
reports: {}
offline_minutes: 0
- public_key: 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f
operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK
country: CA
region: Quebec
city: Montréal
ip: 184.107.169.199
avail_mem_mb: 29000
avail_vcpus: 30
avail_storage_gbs: 700
avail_ipv4: 0
avail_ipv6: 0
avail_ports: 19999
max_ports_per_vm: 5
price: 18000
reports: {}
offline_minutes: 0
vm_contracts:
- uuid: 958165e3-dea8-407d-8c42-dd17002ef79c
hostname: detee-landing-fr
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
exposed_ports: []
public_ipv4: 156.146.63.216
public_ipv6: ""
disk_size_gb: 10
vcpus: 2
memory_mb: 3000
kernel_sha: 3ec4fc5aa5729f515967ec71be4a851622785c0080f7191b1b07717149840151
dtrfs_sha: 3f6b3e5740f249eedfb2f7248c521a551be8b2676f7fcb040f3f3bc840a5004b
created_at: 2025-02-28T23:19:41.769423466Z
updated_at: 2025-04-12T12:11:58.516768949Z
price_per_unit: 20000
locked_nano: 14875500000
collected_at: 2025-04-20T00:34:15.461165181Z
- uuid: e807a2fd-cf90-4a14-bc3a-89ce6dc59033
hostname: detee-landing-gb
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9
exposed_ports: []
public_ipv4: 173.234.136.154
public_ipv6: ""
disk_size_gb: 10
vcpus: 2
memory_mb: 3000
kernel_sha: 3ec4fc5aa5729f515967ec71be4a851622785c0080f7191b1b07717149840151
dtrfs_sha: 3f6b3e5740f249eedfb2f7248c521a551be8b2676f7fcb040f3f3bc840a5004b
created_at: 2025-03-06T19:51:39.595163157Z
updated_at: 2025-03-06T19:51:39.595163842Z
price_per_unit: 20000
locked_nano: 14875500000
collected_at: 2025-04-20T00:34:15.461181545Z
- uuid: 23094406-2307-4332-a642-acee718d0186
hostname: heroic-door
admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX
node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9
exposed_ports:
- 38288
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
vcpus: 1
memory_mb: 1000
kernel_sha: 14e225e4aaf84cc2e0b5f64206121186ddebc4b378b886da3b2f7515dfd41692
dtrfs_sha: 03ce24dbbe917fdd4f6347e61036805ddbdded5044c272bab188ef9333093bee
created_at: 2025-03-12T16:28:24.749161605Z
updated_at: 2025-03-12T16:28:24.749162477Z
price_per_unit: 20000
locked_nano: 14134140000
collected_at: 2025-04-20T00:34:15.461191231Z
- uuid: 1f49a71c-f68c-4c64-a82e-f50e0ba0b574
hostname: astromech-wrench
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
exposed_ports: []
public_ipv4: 149.22.95.2
public_ipv6: ""
disk_size_gb: 10
vcpus: 2
memory_mb: 3000
kernel_sha: 3a68709138bed09c16671949cf1f03acee95a08381ba84fc70fb586001fa6767
dtrfs_sha: 0bb93443f65c9f4379ed469f94794f5c1bf14d8905b0b2c56a125df4a9ebe83e
created_at: 2025-03-20T14:40:25.557753393Z
updated_at: 2025-03-20T14:40:25.557754242Z
price_per_unit: 20000
locked_nano: 11865620000
collected_at: 2025-04-20T00:34:15.461201690Z
- uuid: 16577f1c-9867-4a17-80a8-6cf0490f1270
hostname: sofenty
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
exposed_ports: []
public_ipv4: 156.146.63.217
public_ipv6: ""
disk_size_gb: 10
vcpus: 2
memory_mb: 3000
kernel_sha: e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542
dtrfs_sha: b5f408d00e2b93dc594fed3a7f2466a9878802ff1c7ae502247471cd06728a45
created_at: 2025-04-07T22:57:57.646151746Z
updated_at: 2025-04-07T22:57:57.646152630Z
price_per_unit: 20000
locked_nano: 11867500000
collected_at: 2025-04-20T00:34:15.461211040Z
- uuid: 4b6e25ca-87ac-478b-8f16-aa8f5c44c704
hostname: cloaked-mailbox
admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX
node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
exposed_ports: []
public_ipv4: 149.22.95.2
public_ipv6: ""
disk_size_gb: 30
vcpus: 1
memory_mb: 1000
kernel_sha: e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542
dtrfs_sha: b5f408d00e2b93dc594fed3a7f2466a9878802ff1c7ae502247471cd06728a45
created_at: 2025-04-12T13:44:56.957037550Z
updated_at: 2025-04-12T13:44:56.957038546Z
price_per_unit: 20000
locked_nano: 11177760000
collected_at: 2025-04-20T00:34:15.461219779Z
- uuid: eb1a13ed-d782-4b71-8860-73540129cb7d
hostname: twenty
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
exposed_ports: []
public_ipv4: 149.36.48.100
public_ipv6: ""
disk_size_gb: 10
vcpus: 4
memory_mb: 4000
kernel_sha: e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542
dtrfs_sha: b5f408d00e2b93dc594fed3a7f2466a9878802ff1c7ae502247471cd06728a45
created_at: 2025-04-15T00:46:35.622165457Z
updated_at: 2025-04-15T00:46:35.622166372Z
price_per_unit: 20000
locked_nano: 15570720000
collected_at: 2025-04-20T00:34:15.461230948Z
- uuid: 1bf36309-3774-4825-b023-b2a0ef0405ed
hostname: shadowy-hobo
admin_pubkey: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK
node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
exposed_ports:
- 46393
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
vcpus: 1
memory_mb: 1000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-04-16T20:37:57.176592933Z
updated_at: 2025-04-16T20:37:57.176594069Z
price_per_unit: 20000
locked_nano: 12730960000
collected_at: 2025-04-20T00:34:15.461240342Z
- uuid: dbe09a11-0bcf-472e-9f27-9a4939ea2226
hostname: detee-fr
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
exposed_ports: []
public_ipv4: 156.146.63.217
public_ipv6: ''
disk_size_gb: 10
vcpus: 4
memory_mb: 4000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-05-16T11:07:53.903282009Z
updated_at: 2025-05-16T11:07:53.903282959Z
price_per_unit: 20000
locked_nano: 24513120000
collected_at: 2025-06-26T11:32:59.521517733Z
- uuid: 338312387c6e4e5ebec015277d27c21d
hostname: sofenty-staging
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
exposed_ports: []
public_ipv4: 149.22.95.3
public_ipv6: ''
disk_size_gb: 10
vcpus: 2
memory_mb: 4000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-06-12T23:20:23.797184848Z
updated_at: 2025-06-12T23:20:23.797185855Z
price_per_unit: 20000
locked_nano: 17703920000
collected_at: 2025-06-26T11:32:59.521538185Z
- uuid: 46656273dc964fdeaec2fd1efd49fc12
hostname: sofenty-scraper-bot
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb
exposed_ports:
- 36057
public_ipv4: ''
public_ipv6: ''
disk_size_gb: 10
vcpus: 2
memory_mb: 4000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-06-17T11:12:18.659422501Z
updated_at: 2025-06-17T11:12:18.659423285Z
price_per_unit: 20000
locked_nano: 14299320000
collected_at: 2025-06-26T11:32:59.521547200Z
- uuid: 1b3365a15fe64b8aa283bb7883c62e09
hostname: detee-us
admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL
node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4
exposed_ports: []
public_ipv4: 149.36.48.100
public_ipv6: ''
disk_size_gb: 10
vcpus: 4
memory_mb: 4000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-06-18T10:51:17.699206021Z
updated_at: 2025-06-18T10:51:17.699206835Z
price_per_unit: 20000
locked_nano: 26552160000
collected_at: 2025-06-26T11:32:59.521554160Z
- uuid: b11ad0fcfc194f5490d64f5a72574dc8
hostname: brain-backups
admin_pubkey: 45Pyv9hRfub43NyRrYv95MhZs1Wrm8sj3RhBvA3F1Bvr
node_pubkey: 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f
exposed_ports:
- 38175
public_ipv4: ''
public_ipv6: ''
disk_size_gb: 30
vcpus: 1
memory_mb: 1000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-06-18T13:59:30.713579315Z
updated_at: 2025-06-18T13:59:30.713580515Z
price_per_unit: 20000
locked_nano: 11638260000
collected_at: 2025-06-26T11:32:59.521562057Z
- uuid: 89237736b97047beac3611e25e26408e
hostname: brain-staging
admin_pubkey: 45Pyv9hRfub43NyRrYv95MhZs1Wrm8sj3RhBvA3F1Bvr
node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu
exposed_ports: []
public_ipv4: 156.146.63.216
public_ipv6: ''
disk_size_gb: 20
vcpus: 2
memory_mb: 4000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
dtrfs_sha: d207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990
created_at: 2025-06-18T14:00:48.016735075Z
updated_at: 2025-06-18T14:00:48.016736647Z
price_per_unit: 20000
locked_nano: 32466740000
collected_at: 2025-06-26T11:32:59.521568755Z
app_nodes:
- node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
operator_wallet: 7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB
country: DE
region: Hesse
city: Frankfurt am Main
ip: 212.95.45.139
avail_mem_mb: 16000
avail_vcpus: 16
avail_storage_mb: 200000
avail_no_of_port: 20000
max_ports_per_app: 9
price: 20000
offline_minutes: 0
app_contracts:
- uuid: e3d01f25-2b2a-410b-80e3-12f44e474334
package_url: https://registry.detee.ltd/sgx/packages/base_package_2025-04-17_11-01-08.tar.gz
admin_pubkey: H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc
node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
mapped_ports:
- - 27158
- 34500
- - 28667
- 8080
host_ipv4: 212.95.45.139
disk_size_mb: 1000
vcpus: 1
memory_mb: 1000
created_at: 2025-04-21T11:27:28.833236909Z
updated_at: 2025-04-21T11:27:28.833237729Z
price_per_unit: 200000
locked_nano: 121200000
collected_at: 2025-04-21T11:28:24.905665571Z
hratls_pubkey: 7E0F887AA6BB9104EEC1066F454D4C2D9063D676715F55F919D3FBCEDC63240B
public_package_mr_enclave:
- 52
- 183
- 102
- 210
- 251
- 219
- 218
- 140
- 168
- 118
- 10
- 193
- 98
- 240
- 147
- 124
- 240
- 189
- 46
- 95
- 138
- 172
- 15
- 246
- 227
- 114
- 70
- 159
- 232
- 212
- 9
- 234
app_name: diligent-seahorse
- node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
operator_wallet: 7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB
country: DE
region: Hesse
city: Frankfurt am Main
ip: 212.95.45.139
avail_mem_mb: 16000
avail_vcpus: 16
avail_storage_mb: 200000
avail_no_of_port: 20000
max_ports_per_app: 9
price: 20000
reports: {}
offline_minutes: 0
app_contracts: []

@ -12,7 +12,7 @@ server="$1"
exit 1
}
[[ "$server" == "testnet" ]] && server="root@prod-brain-1"
[[ "$server" == "testnet" ]] && server="brain-testnet"
[[ "$server" == "staging" ]] && server="brain-staging"
cargo build --release --bin brain

@ -50,7 +50,6 @@ pub const ID_ALPHABET: [char; 62] = [
];
pub const TOKEN_DECIMAL: u64 = 1_000_000_000;
pub const MIN_ESCROW: u64 = 5000 * TOKEN_DECIMAL;
pub const APP_DAEMON_TIMEOUT: u64 = 20;
pub const VM_DAEMON_TIMEOUT: u64 = 10;

@ -6,9 +6,8 @@ use super::Error;
use crate::constants::{
ACCOUNT, ACTIVE_APP, APP_DAEMON_TIMEOUT, APP_NODE, DEFAULT_ENDPOINT, DELETED_APP, NEW_APP_REQ,
};
use crate::db;
use crate::db::general::Report;
use crate::old_brain;
use crate::{db, old_brain};
use detee_shared::app_proto::{self, NewAppRes};
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Client;
@ -25,13 +24,14 @@ pub struct AppNode {
pub region: String,
pub city: String,
pub ip: String,
pub avail_mem_mb: u32,
pub avail_mem_mib: u32,
pub avail_vcpus: u32,
pub avail_storage_gbs: u32,
pub avail_storage_mib: u32,
pub avail_ports: u32,
pub max_ports_per_app: u32,
pub price: u64,
pub offline_minutes: u64,
pub connected_at: Datetime,
pub disconnected_at: Datetime,
}
impl AppNode {
@ -41,6 +41,18 @@ impl AppNode {
let app_node: Option<AppNode> = db.upsert(app_node_id.clone()).content(self).await?;
app_node.ok_or(Error::FailedToCreateDBEntry(format!("{APP_NODE}:{app_node_id}")))
}
pub async fn set_online(db: &Surreal<Client>, app_node_id: &str) -> Result<(), Error> {
db.query(format!("UPDATE {APP_NODE}:{app_node_id} SET connected_at = time::now();"))
.await?;
Ok(())
}
pub async fn set_offline(db: &Surreal<Client>, app_node_id: &str) -> Result<(), Error> {
db.query(format!("UPDATE {APP_NODE}:{app_node_id} SET disconnected_at = time::now();"))
.await?;
Ok(())
}
}
pub enum AppDaemonMsg {
@ -72,9 +84,9 @@ pub struct NewAppReq {
pub mr_enclave: String,
pub hratls_pubkey: String,
pub ports: Vec<u32>,
pub memory_mb: u32,
pub memory_mib: u32,
pub vcpus: u32,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub locked_nano: u64,
pub price_per_unit: u64,
pub error: String,
@ -165,12 +177,12 @@ impl NewAppReq {
->$app_node
CONTENT {{
created_at: time::now(), app_name: $app_name, package_url: $package_url,
mr_enclave: $mr_enclave, hratls_pubkey: $hratls_pubkey, ports: {:?}, memory_mb: {},
vcpus: {}, disk_size_gb: {}, locked_nano: {locked_nano}, price_per_unit: {}, error: '',
mr_enclave: $mr_enclave, hratls_pubkey: $hratls_pubkey, ports: {:?}, memory_mib: {},
vcpus: {}, disk_size_mib: {}, locked_nano: {locked_nano}, price_per_unit: {}, error: '',
}};
COMMIT TRANSACTION;",
self.ports, self.memory_mb, self.vcpus, self.disk_size_gb, self.price_per_unit);
self.ports, self.memory_mib, self.vcpus, self.disk_size_mib, self.price_per_unit);
log::trace!("submit_new_app_req query: {tx_query}");
@ -212,13 +224,12 @@ pub struct AppNodeWithReports {
pub region: String,
pub city: String,
pub ip: String,
pub avail_mem_mb: u32,
pub avail_mem_mib: u32,
pub avail_vcpus: u32,
pub avail_storage_gbs: u32,
pub avail_storage_mib: u32,
pub avail_ports: u32,
pub max_ports_per_app: u32,
pub price: u64,
pub offline_minutes: u64,
pub reports: Vec<Report>,
}
@ -233,13 +244,13 @@ impl AppNodeWithReports {
avail_ports >= {} &&
max_ports_per_app >= {} &&
avail_vcpus >= {} &&
avail_mem_mb >= {} &&
avail_storage_gbs >= {} ",
avail_mem_mib >= {} &&
avail_storage_mib >= {} ",
filters.free_ports,
filters.free_ports,
filters.vcpus,
filters.memory_mb,
filters.storage_gb
filters.memory_mib,
filters.storage_mib
);
// TODO: bind all strings
@ -256,6 +267,8 @@ impl AppNodeWithReports {
filter_query += &format!("&& ip = '{}' ", filters.ip);
}
filter_query += " && connected_at > disconnected_at ";
if limit_one {
filter_query += "limit 1";
}
@ -278,8 +291,8 @@ pub struct ActiveApp {
pub mapped_ports: Vec<(u32, u32)>,
pub host_ipv4: String,
pub vcpus: u32,
pub memory_mb: u32,
pub disk_size_gb: u32,
pub memory_mib: u32,
pub disk_size_mib: u32,
pub created_at: Datetime,
pub price_per_unit: u64,
pub locked_nano: u64,
@ -299,8 +312,8 @@ impl From<ActiveApp> for DeletedApp {
mapped_ports: value.mapped_ports,
host_ipv4: value.host_ipv4,
vcpus: value.vcpus,
memory_mb: value.memory_mb,
disk_size_gb: value.disk_size_gb,
memory_mib: value.memory_mib,
disk_size_mib: value.disk_size_mib,
created_at: value.created_at,
price_per_unit: value.price_per_unit,
mr_enclave: value.mr_enclave,
@ -311,15 +324,6 @@ impl From<ActiveApp> for DeletedApp {
}
impl ActiveApp {
pub fn price_per_minute(&self) -> u64 {
(self.total_units() * self.price_per_unit as f64) as u64
}
fn total_units(&self) -> f64 {
// TODO: Optimize this based on price of hardware.
(self.vcpus as f64 * 5f64) + (self.memory_mb as f64 / 200f64) + (self.disk_size_gb as f64)
}
pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> {
let contract: Option<Self> = db
.query("select * from $active_app_id;".to_string())
@ -352,8 +356,8 @@ impl ActiveApp {
mapped_ports,
host_ipv4: new_app_res.ip_address,
vcpus: new_app_req.vcpus,
memory_mb: new_app_req.memory_mb,
disk_size_gb: new_app_req.disk_size_gb,
memory_mib: new_app_req.memory_mib,
disk_size_mib: new_app_req.disk_size_mib,
created_at: new_app_req.created_at.clone(),
price_per_unit: new_app_req.price_per_unit,
locked_nano: new_app_req.locked_nano,
@ -367,7 +371,7 @@ impl ActiveApp {
let locked_nano = active_app.locked_nano;
let _: Vec<ActiveApp> = db.insert(()).relation(active_app).await?;
NewAppReq::delete(&db, &new_app_res.uuid).await?;
NewAppReq::delete(db, &new_app_res.uuid).await?;
db.query(format!("UPDATE {ACCOUNT}:{admin_account} SET tmp_locked -= {locked_nano};"))
.await?;
@ -498,8 +502,8 @@ pub struct ActiveAppWithNode {
pub mapped_ports: Vec<(u32, u32)>,
pub host_ipv4: String,
pub vcpus: u32,
pub memory_mb: u32,
pub disk_size_gb: u32,
pub memory_mib: u32,
pub disk_size_mib: u32,
pub created_at: Datetime,
pub price_per_unit: u64,
pub locked_nano: u64,
@ -519,8 +523,8 @@ impl From<ActiveAppWithNode> for ActiveApp {
mapped_ports: val.mapped_ports,
host_ipv4: val.host_ipv4,
vcpus: val.vcpus,
memory_mb: val.memory_mb,
disk_size_gb: val.disk_size_gb,
memory_mib: val.memory_mib,
disk_size_mib: val.disk_size_mib,
created_at: val.created_at,
price_per_unit: val.price_per_unit,
locked_nano: val.locked_nano,
@ -588,6 +592,17 @@ impl ActiveAppWithNode {
}
}
fn total_units(&self) -> f64 {
// TODO: Optimize this based on price of hardware.
(self.vcpus as f64 * 5f64)
+ (self.memory_mib as f64 / 200f64)
+ (self.disk_size_mib as f64 / 1024f64 / 10f64)
}
pub fn price_per_minute(&self) -> u64 {
(self.total_units() * self.price_per_unit as f64) as u64
}
pub async fn list_all(db: &Surreal<Client>) -> Result<Vec<Self>, Error> {
let mut query_response = db.query(format!("SELECT * FROM {ACTIVE_APP} FETCH out;")).await?;
let active_apps: Vec<Self> = query_response.take(0)?;
@ -599,8 +614,8 @@ impl ActiveAppWithNode {
pub struct AppNodeResources {
pub avail_ports: u32,
pub avail_vcpus: u32,
pub avail_mem_mb: u32,
pub avail_storage_gbs: u32,
pub avail_mem_mib: u32,
pub avail_storage_mib: u32,
pub max_ports_per_app: u32,
}
@ -628,13 +643,14 @@ impl From<&old_brain::BrainData> for Vec<AppNode> {
region: old_node.region.clone(),
city: old_node.city.clone(),
ip: old_node.ip.clone(),
avail_mem_mb: old_node.avail_mem_mb,
avail_mem_mib: old_node.avail_mem_mb,
avail_vcpus: old_node.avail_vcpus,
avail_storage_gbs: old_node.avail_storage_mb,
avail_storage_mib: old_node.avail_storage_mb,
avail_ports: old_node.avail_no_of_port,
max_ports_per_app: old_node.max_ports_per_app,
price: old_node.price,
offline_minutes: old_node.offline_minutes,
disconnected_at: Datetime::default(),
connected_at: Datetime::default(),
});
}
nodes
@ -664,9 +680,9 @@ impl From<&old_brain::BrainData> for Vec<ActiveApp> {
app_node: RecordId::from((APP_NODE, old_c.node_pubkey.clone())),
mapped_ports,
host_ipv4: old_c.host_ipv4.clone(),
disk_size_gb: old_c.disk_size_mb * 1024,
disk_size_mib: old_c.disk_size_mb,
vcpus: old_c.vcpus,
memory_mb: old_c.memory_mb,
memory_mib: old_c.memory_mb,
price_per_unit: old_c.price_per_unit,
locked_nano: old_c.locked_nano,
created_at: old_c.created_at.into(),
@ -692,8 +708,8 @@ pub struct DeletedApp {
pub mapped_ports: Vec<(u32, u32)>,
pub host_ipv4: String,
pub vcpus: u32,
pub memory_mb: u32,
pub disk_size_gb: u32,
pub memory_mib: u32,
pub disk_size_mib: u32,
pub created_at: Datetime,
pub price_per_unit: u64,
pub mr_enclave: String,

@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use super::Error;
use crate::constants::{ACCOUNT, BAN, KICK, MIN_ESCROW, VM_NODE};
use crate::constants::{ACCOUNT, BAN, KICK, TOKEN_DECIMAL, VM_NODE};
use crate::db::prelude::*;
use crate::old_brain;
use serde::{Deserialize, Serialize};
@ -61,9 +61,7 @@ impl Account {
email: &str,
escrow: u64,
) -> Result<(), Error> {
if escrow < MIN_ESCROW {
return Err(Error::MinimalEscrow);
}
let escrow = escrow.saturating_mul(TOKEN_DECIMAL);
let mut op_account = Self::get(db, wallet).await?;
let op_total_balance = op_account.balance.saturating_add(op_account.escrow);
if op_total_balance < escrow {

@ -5,8 +5,8 @@ pub mod general;
pub mod vm;
use crate::constants::{
APP_NODE, DB_SCHEMA_FILES, DEFAULT_ENDPOINT, DELETED_APP, DELETED_VM, MIN_ESCROW, NEW_APP_REQ,
NEW_VM_REQ, UPDATE_VM_REQ,
APP_NODE, DB_SCHEMA_FILES, DEFAULT_ENDPOINT, DELETED_APP, DELETED_VM, NEW_APP_REQ, NEW_VM_REQ,
UPDATE_VM_REQ,
};
use crate::old_brain;
use prelude::*;
@ -33,8 +33,6 @@ pub enum Error {
UnknownTable(String),
#[error("Daemon channel got closed: {0}")]
AppDaemonConnection(#[from] tokio::sync::mpsc::error::SendError<AppDaemonMsg>),
#[error("Minimum escrow amount is {MIN_ESCROW}")]
MinimalEscrow,
#[error("Insufficient funds, deposit more tokens")]
InsufficientFunds,
#[error("Contract not found")]
@ -101,7 +99,7 @@ pub async fn migration0(
println!("Inserting vm nodes...");
let _: Vec<VmNode> = db.insert(()).content(vm_nodes).await?;
println!("Inserting app nodes...");
let _: Vec<AppNode> = db.insert(()).content(app_nodes).await?;
let _: Vec<AppNode> = db.insert(()).content(app_nodes).await.unwrap();
println!("Inserting active vm contracts...");
let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?;
println!("Inserting app contracts...");

@ -26,9 +26,9 @@ pub struct VmNode {
pub region: String,
pub city: String,
pub ip: String,
pub avail_mem_mb: u32,
pub avail_mem_mib: u32,
pub avail_vcpus: u32,
pub avail_storage_gbs: u32,
pub avail_storage_mib: u32,
pub avail_ipv4: u32,
pub avail_ipv6: u32,
pub avail_ports: u32,
@ -59,9 +59,9 @@ impl VmNode {
#[derive(Serialize)]
pub struct VmNodeResources {
pub avail_mem_mb: u32,
pub avail_mem_mib: u32,
pub avail_vcpus: u32,
pub avail_storage_gbs: u32,
pub avail_storage_mib: u32,
pub avail_ipv4: u32,
pub avail_ipv6: u32,
pub avail_ports: u32,
@ -83,9 +83,9 @@ pub struct VmNodeWithReports {
pub region: String,
pub city: String,
pub ip: String,
pub avail_mem_mb: u32,
pub avail_vcpus: u32,
pub avail_storage_gbs: u32,
pub avail_mem_mib: u64,
pub avail_vcpus: u64,
pub avail_storage_mib: u64,
pub avail_ipv4: u32,
pub avail_ipv6: u32,
pub avail_ports: u32,
@ -106,15 +106,15 @@ impl VmNodeWithReports {
avail_ipv4 >= {} &&
avail_ipv6 >= {} &&
avail_vcpus >= {} &&
avail_mem_mb >= {} &&
avail_storage_gbs >= {}\n",
avail_mem_mib >= {} &&
avail_storage_mib >= {}\n",
filters.free_ports,
filters.free_ports,
filters.offers_ipv4 as u32,
filters.offers_ipv6 as u32,
filters.vcpus,
filters.memory_mb,
filters.storage_gb
filters.memory_mib,
filters.storage_mib
);
if !filters.city.is_empty() {
query += &format!("&& city = '{}' ", filters.city);
@ -170,9 +170,9 @@ pub struct NewVmReq {
pub extra_ports: Vec<u32>,
pub public_ipv4: bool,
pub public_ipv6: bool,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_url: String,
pub dtrfs_sha: String,
pub kernel_sha: String,
@ -266,7 +266,7 @@ impl NewVmReq {
->$new_vm_req
->$vm_node
CONTENT {{
created_at: time::now(), hostname: $hostname, vcpus: {}, memory_mb: {}, disk_size_gb: {},
created_at: time::now(), hostname: $hostname, vcpus: {}, memory_mib: {}, disk_size_mib: {},
extra_ports: {:?}, public_ipv4: {}, public_ipv6: {},
dtrfs_url: $dtrfs_url, dtrfs_sha: $dtrfs_sha, kernel_url: $kernel_url, kernel_sha: $kernel_sha,
price_per_unit: {}, locked_nano: {locked_nano}, error: ''
@ -274,8 +274,8 @@ impl NewVmReq {
COMMIT TRANSACTION;",
self.vcpus,
self.memory_mb,
self.disk_size_gb,
self.memory_mib,
self.disk_size_mib,
self.extra_ports,
self.public_ipv4,
self.public_ipv6,
@ -334,7 +334,7 @@ impl WrappedMeasurement {
_ => NEW_VM_REQ,
};
let mut resp = db
.query(format!("live select error from {table} where id = {NEW_VM_REQ}:{vm_id};"))
.query(format!("live select error from {table} where id = {table}:{vm_id};"))
.query(format!(
"live select * from measurement_args where id = measurement_args:{vm_id};"
))
@ -404,9 +404,9 @@ pub struct ActiveVm {
pub mapped_ports: Vec<(u32, u32)>,
pub public_ipv4: String,
pub public_ipv6: String,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_sha: String,
pub kernel_sha: String,
pub created_at: Datetime,
@ -422,8 +422,8 @@ impl ActiveVm {
// I tried, but this can be done better.
// Storage cost should also be based on tier
(self.vcpus as u64 * 10)
+ ((self.memory_mb + 256) as u64 / 200)
+ (self.disk_size_gb as u64 / 10)
+ ((self.memory_mib + 256) as u64 / 200)
+ (self.disk_size_mib as u64 / 10)
+ (!self.public_ipv4.is_empty() as u64 * 10)
}
@ -483,9 +483,9 @@ impl ActiveVm {
mapped_ports,
public_ipv4,
public_ipv6,
disk_size_gb: new_vm_req.disk_size_gb,
disk_size_mib: new_vm_req.disk_size_mib,
vcpus: new_vm_req.vcpus,
memory_mb: new_vm_req.memory_mb,
memory_mib: new_vm_req.memory_mib,
dtrfs_sha: new_vm_req.dtrfs_sha,
kernel_sha: new_vm_req.kernel_sha,
created_at: new_vm_req.created_at.clone(),
@ -517,11 +517,11 @@ impl ActiveVm {
if update_vm_req.vcpus > 0 {
active_vm.vcpus = update_vm_req.vcpus;
}
if update_vm_req.memory_mb > 0 {
active_vm.memory_mb = update_vm_req.memory_mb;
if update_vm_req.memory_mib > 0 {
active_vm.memory_mib = update_vm_req.memory_mib;
}
if update_vm_req.disk_size_gb > 0 {
active_vm.disk_size_gb = update_vm_req.disk_size_gb;
if update_vm_req.disk_size_mib > 0 {
active_vm.disk_size_mib = update_vm_req.disk_size_mib;
}
if !update_vm_req.dtrfs_sha.is_empty() && !update_vm_req.kernel_sha.is_empty() {
active_vm.dtrfs_sha = update_vm_req.dtrfs_sha;
@ -670,9 +670,9 @@ pub struct UpdateVmReq {
pub admin: RecordId,
#[serde(rename = "out")]
pub vm_node: RecordId,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_url: String,
pub dtrfs_sha: String,
pub kernel_sha: String,
@ -688,9 +688,9 @@ pub struct UpdateVmEvent {
pub admin: RecordId,
#[serde(rename = "out")]
pub vm_node: RecordId,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_url: String,
pub dtrfs_sha: String,
pub kernel_sha: String,
@ -704,9 +704,9 @@ impl From<UpdateVmReq> for UpdateVmEvent {
vm_id: RecordId::from((VM_UPDATE_EVENT, update_vm_req.id.key().to_string())),
admin: update_vm_req.admin,
vm_node: update_vm_req.vm_node,
disk_size_gb: update_vm_req.disk_size_gb,
disk_size_mib: update_vm_req.disk_size_mib,
vcpus: update_vm_req.vcpus,
memory_mb: update_vm_req.memory_mb,
memory_mib: update_vm_req.memory_mib,
dtrfs_url: update_vm_req.dtrfs_url,
dtrfs_sha: update_vm_req.dtrfs_sha,
kernel_sha: update_vm_req.kernel_sha,
@ -743,17 +743,32 @@ impl UpdateVmReq {
return Ok(None);
}
let contract = contract.unwrap();
// this is needed cause TryFrom does not support await
let mem_per_cpu = contract.memory_mib / contract.vcpus;
let disk_per_cpu = contract.disk_size_mib / contract.vcpus;
self.vm_node = contract.vm_node;
if !((self.vcpus != 0 && contract.vcpus != self.vcpus)
|| (self.memory_mb != 0 && contract.memory_mb != self.memory_mb)
|| (self.memory_mib != 0 && contract.memory_mib != self.memory_mib)
|| (!self.dtrfs_sha.is_empty() && contract.dtrfs_sha != self.dtrfs_sha)
|| (self.disk_size_gb != 0 && contract.disk_size_gb != self.disk_size_gb))
|| (self.disk_size_mib != 0 && contract.disk_size_mib != self.disk_size_mib))
{
return Ok(Some(false));
}
// Do not allow user to unbalance node resources
if self.vcpus == 0 {
self.vcpus = self.memory_mib / mem_per_cpu;
}
if self.memory_mib == 0 {
self.memory_mib = self.vcpus * mem_per_cpu;
}
if self.vcpus == 0 {
self.vcpus = self.disk_size_mib / disk_per_cpu;
}
if self.disk_size_mib == 0 {
self.disk_size_mib = self.vcpus * disk_per_cpu;
}
let _: Vec<Self> = db.insert(UPDATE_VM_REQ).relation(self).await?;
Ok(Some(true))
}
@ -764,6 +779,7 @@ impl UpdateVmReq {
error: String,
}
let _: Option<Self> = db.update((UPDATE_VM_REQ, id)).merge(UpdateVmError { error }).await?;
let _: Option<Self> = db.delete((UPDATE_VM_REQ, id)).await?;
Ok(())
}
}
@ -779,9 +795,9 @@ pub struct DeletedVm {
pub mapped_ports: Vec<(u32, u32)>,
pub public_ipv4: String,
pub public_ipv6: String,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_sha: String,
pub kernel_sha: String,
pub created_at: Datetime,
@ -799,9 +815,9 @@ impl From<ActiveVm> for DeletedVm {
mapped_ports: active_vm.mapped_ports,
public_ipv4: active_vm.public_ipv4,
public_ipv6: active_vm.public_ipv6,
disk_size_gb: active_vm.disk_size_gb,
disk_size_mib: active_vm.disk_size_mib,
vcpus: active_vm.vcpus,
memory_mb: active_vm.memory_mb,
memory_mib: active_vm.memory_mib,
dtrfs_sha: active_vm.dtrfs_sha,
kernel_sha: active_vm.kernel_sha,
created_at: active_vm.created_at,
@ -862,8 +878,8 @@ impl DeletedVm {
// I tried, but this can be done better.
// Storage cost should also be based on tier
(self.vcpus as u64 * 10)
+ ((self.memory_mb + 256) as u64 / 200)
+ (self.disk_size_gb as u64 / 10)
+ ((self.memory_mib + 256) as u64 / 200)
+ (self.disk_size_mib as u64 / 10)
+ (!self.public_ipv4.is_empty() as u64 * 10)
}
@ -884,9 +900,9 @@ pub struct ActiveVmWithNode {
pub mapped_ports: Vec<(u32, u32)>,
pub public_ipv4: String,
pub public_ipv6: String,
pub disk_size_gb: u32,
pub disk_size_mib: u32,
pub vcpus: u32,
pub memory_mb: u32,
pub memory_mib: u32,
pub dtrfs_sha: String,
pub kernel_sha: String,
pub created_at: Datetime,
@ -905,9 +921,9 @@ impl From<ActiveVmWithNode> for ActiveVm {
mapped_ports: val.mapped_ports,
public_ipv4: val.public_ipv4,
public_ipv6: val.public_ipv6,
disk_size_gb: val.disk_size_gb,
disk_size_mib: val.disk_size_mib,
vcpus: val.vcpus,
memory_mb: val.memory_mb,
memory_mib: val.memory_mib,
dtrfs_sha: val.dtrfs_sha,
kernel_sha: val.kernel_sha,
created_at: val.created_at,
@ -974,8 +990,8 @@ impl ActiveVmWithNode {
// I tried, but this can be done better.
// Storage cost should also be based on tier
(self.vcpus as u64 * 10)
+ ((self.memory_mb + 256) as u64 / 200)
+ (self.disk_size_gb as u64 / 10)
+ ((self.memory_mib + 256) as u64 / 200)
+ (self.disk_size_mib as u64 / 1024 / 10)
+ (!self.public_ipv4.is_empty() as u64 * 10)
}
@ -1005,9 +1021,9 @@ impl From<&old_brain::BrainData> for Vec<VmNode> {
region: old_node.region.clone(),
city: old_node.city.clone(),
ip: old_node.ip.clone(),
avail_mem_mb: old_node.avail_mem_mb,
avail_mem_mib: old_node.avail_mem_mb,
avail_vcpus: old_node.avail_vcpus,
avail_storage_gbs: old_node.avail_storage_gbs,
avail_storage_mib: old_node.avail_storage_gbs * 1024,
avail_ipv4: old_node.avail_ipv4,
avail_ipv6: old_node.avail_ipv6,
avail_ports: old_node.avail_ports,
@ -1037,9 +1053,9 @@ impl From<&old_brain::BrainData> for Vec<ActiveVm> {
mapped_ports,
public_ipv4: old_c.public_ipv4.clone(),
public_ipv6: old_c.public_ipv6.clone(),
disk_size_gb: old_c.disk_size_gb,
disk_size_mib: old_c.disk_size_gb * 1024,
vcpus: old_c.vcpus,
memory_mb: old_c.memory_mb,
memory_mib: old_c.memory_mb,
dtrfs_sha: old_c.dtrfs_sha.clone(),
kernel_sha: old_c.kernel_sha.clone(),
price_per_unit: old_c.price_per_unit,

@ -12,8 +12,7 @@ use log::info;
use std::pin::Pin;
use std::sync::Arc;
use surrealdb::engine::remote::ws::Client;
use surrealdb::RecordId;
use surrealdb::Surreal;
use surrealdb::{RecordId, Surreal};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::{Stream, StreamExt};
@ -31,8 +30,8 @@ impl AppDaemonServer {
#[tonic::async_trait]
impl BrainAppDaemon for AppDaemonServer {
type RegisterAppNodeStream = Pin<Box<dyn Stream<Item = Result<DelAppReq, Status>> + Send>>;
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainMessageApp, Status>> + Send>>;
type RegisterAppNodeStream = Pin<Box<dyn Stream<Item = Result<DelAppReq, Status>> + Send>>;
async fn register_app_node(
&self,
@ -52,12 +51,13 @@ impl BrainAppDaemon for AppDaemonServer {
ip: req.main_ip,
price: req.price,
avail_mem_mb: 0,
avail_mem_mib: 0,
avail_vcpus: 0,
avail_storage_gbs: 0,
avail_storage_mib: 0,
avail_ports: 0,
max_ports_per_app: 0,
offline_minutes: 0,
disconnected_at: surrealdb::sql::Datetime::default(),
connected_at: surrealdb::sql::Datetime::default(),
};
app_node.register(&self.db).await?;
@ -89,6 +89,7 @@ impl BrainAppDaemon for AppDaemonServer {
)?;
info!("App Daemon {} connected to receive brain messages", pubkey);
let _ = db::AppNode::set_online(&self.db, &pubkey).await;
let (tx, rx) = mpsc::channel(6);
{
@ -162,7 +163,8 @@ impl BrainAppDaemon for AppDaemonServer {
},
Err(e) => {
log::warn!("App Daemon Disconnected: {e:?}")
log::warn!("App Daemon Disconnected: {e:?}");
let _ = db::AppNode::set_offline(&self.db, &pubkey).await;
}
}
}

@ -115,7 +115,7 @@ impl BrainGeneralCli for GeneralCliServer {
log::info!("Regitering new operator: {req:?}");
match db::Account::operator_reg(&self.db, &req.pubkey, &req.email, req.escrow).await {
Ok(()) => Ok(Response::new(Empty {})),
Err(e) if matches!(e, db::Error::InsufficientFunds | db::Error::MinimalEscrow) => {
Err(e) if matches!(e, db::Error::InsufficientFunds) => {
Err(Status::failed_precondition(e.to_string()))
}
Err(e) => {
@ -133,7 +133,7 @@ impl BrainGeneralCli for GeneralCliServer {
match db::kick_contract(&self.db, &req.operator_wallet, &req.contract_uuid, &req.reason)
.await
{
Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })),
Ok(nano_credits) => Ok(Response::new(KickResp { nano_credits })),
Err(e)
if matches!(
e,

@ -2,10 +2,10 @@
use crate::constants::{ACCOUNT, APP_NODE, ID_ALPHABET, NEW_APP_REQ, NEW_VM_REQ, VM_NODE};
use crate::db::prelude as db;
use detee_shared::app_proto::AppNodeListResp;
use detee_shared::app_proto::*;
use detee_shared::common_proto::MappedPort;
use detee_shared::general_proto::{Account, AccountBalance, ListOperatorsResp};
use detee_shared::{app_proto::*, vm_proto::*};
use detee_shared::vm_proto::*;
use nanoid::nanoid;
use surrealdb::RecordId;
@ -36,9 +36,9 @@ impl From<NewVmReq> for db::NewVmReq {
extra_ports: new_vm_req.extra_ports,
public_ipv4: new_vm_req.public_ipv4,
public_ipv6: new_vm_req.public_ipv6,
disk_size_gb: new_vm_req.disk_size_gb,
disk_size_mib: new_vm_req.disk_size_mib,
vcpus: new_vm_req.vcpus,
memory_mb: new_vm_req.memory_mb,
memory_mib: new_vm_req.memory_mib,
kernel_url: new_vm_req.kernel_url,
kernel_sha: new_vm_req.kernel_sha,
dtrfs_url: new_vm_req.dtrfs_url,
@ -61,9 +61,9 @@ impl From<db::NewVmReq> for NewVmReq {
extra_ports: new_vm_req.extra_ports,
public_ipv4: new_vm_req.public_ipv4,
public_ipv6: new_vm_req.public_ipv6,
disk_size_gb: new_vm_req.disk_size_gb,
disk_size_mib: new_vm_req.disk_size_mib,
vcpus: new_vm_req.vcpus,
memory_mb: new_vm_req.memory_mb,
memory_mib: new_vm_req.memory_mib,
kernel_url: new_vm_req.kernel_url,
kernel_sha: new_vm_req.kernel_sha,
dtrfs_url: new_vm_req.dtrfs_url,
@ -104,9 +104,9 @@ impl From<UpdateVmReq> for db::UpdateVmReq {
admin: RecordId::from((ACCOUNT, new_vm_req.admin_pubkey)),
// vm_node gets modified later, and only if the db::UpdateVmReq is required
vm_node: RecordId::from((VM_NODE, String::new())),
disk_size_gb: new_vm_req.disk_size_gb,
disk_size_mib: new_vm_req.disk_size_mib,
vcpus: new_vm_req.vcpus,
memory_mb: new_vm_req.memory_mb,
memory_mib: new_vm_req.memory_mib,
kernel_url: new_vm_req.kernel_url,
kernel_sha: new_vm_req.kernel_sha,
dtrfs_url: new_vm_req.dtrfs_url,
@ -124,9 +124,9 @@ impl From<db::UpdateVmReq> for UpdateVmReq {
// daemon does not care about VM hostname
hostname: String::new(),
admin_pubkey: update_vm_req.admin.key().to_string(),
disk_size_gb: update_vm_req.disk_size_gb,
disk_size_mib: update_vm_req.disk_size_mib,
vcpus: update_vm_req.vcpus,
memory_mb: update_vm_req.memory_mb,
memory_mib: update_vm_req.memory_mib,
kernel_url: update_vm_req.kernel_url,
kernel_sha: update_vm_req.kernel_sha,
dtrfs_url: update_vm_req.dtrfs_url,
@ -176,9 +176,9 @@ impl From<db::ActiveVmWithNode> for VmContract {
"{}, {}, {}",
db_c.vm_node.city, db_c.vm_node.region, db_c.vm_node.country
),
memory_mb: db_c.memory_mb,
memory_mb: db_c.memory_mib,
vcpus: db_c.vcpus,
disk_size_gb: db_c.disk_size_gb,
disk_size_gb: db_c.disk_size_mib,
mapped_ports: db_c
.mapped_ports
.iter()
@ -230,6 +230,11 @@ impl From<db::VmNodeWithReports> for VmNodeListResp {
ip: vm_node.ip,
reports: vm_node.reports.iter().map(|n| n.reason.clone()).collect(),
price: vm_node.price,
vcpus: vm_node.avail_vcpus,
memory_mib: vm_node.avail_mem_mib,
disk_mib: vm_node.avail_storage_mib,
public_ipv4: vm_node.avail_ipv4 > 0,
public_ipv6: vm_node.avail_ipv6 > 0,
}
}
}
@ -245,6 +250,10 @@ impl From<db::AppNodeWithReports> for AppNodeListResp {
ip: app_node.ip,
reports: app_node.reports.iter().map(|n| n.reason.clone()).collect(),
price: app_node.price,
vcpus: app_node.avail_vcpus as u64,
memory_mib: app_node.avail_mem_mib as u64,
disk_mib: app_node.avail_storage_mib as u64,
}
}
}
@ -252,9 +261,9 @@ impl From<db::AppNodeWithReports> for AppNodeListResp {
impl From<VmNodeResources> for db::VmNodeResources {
fn from(res: VmNodeResources) -> Self {
Self {
avail_mem_mb: res.avail_memory_mb,
avail_mem_mib: res.avail_memory_mib,
avail_vcpus: res.avail_vcpus,
avail_storage_gbs: res.avail_storage_gb,
avail_storage_mib: res.avail_storage_mib,
avail_ipv4: res.avail_ipv4,
avail_ipv6: res.avail_ipv6,
avail_ports: res.avail_ports,
@ -265,6 +274,7 @@ impl From<VmNodeResources> for db::VmNodeResources {
impl From<db::ActiveAppWithNode> for AppContract {
fn from(value: db::ActiveAppWithNode) -> Self {
let nano_per_minute = value.price_per_minute();
let public_package_mr_enclave =
Some(hex::decode(value.mr_enclave.clone()).unwrap_or_default());
@ -275,8 +285,8 @@ impl From<db::ActiveAppWithNode> for AppContract {
node_pubkey: value.app_node.id.key().to_string(),
public_ipv4: value.host_ipv4,
resource: Some(AppResource {
memory_mb: value.memory_mb,
disk_size_gb: value.disk_size_gb,
memory_mib: value.memory_mib,
disk_size_mib: value.disk_size_mib,
vcpus: value.vcpus,
ports: value.mapped_ports.iter().map(|(_, g)| *g).collect(),
}),
@ -288,7 +298,7 @@ impl From<db::ActiveAppWithNode> for AppContract {
created_at: value.created_at.to_rfc3339(),
updated_at: value.created_at.to_rfc3339(),
nano_per_minute: value.price_per_unit,
nano_per_minute,
locked_nano: value.locked_nano,
collected_at: value.collected_at.to_rfc3339(),
hratls_pubkey: value.hratls_pubkey,
@ -317,9 +327,9 @@ impl From<NewAppReq> for db::NewAppReq {
mr_enclave,
hratls_pubkey: val.hratls_pubkey,
ports: resource.ports,
memory_mb: resource.memory_mb,
memory_mib: resource.memory_mib,
vcpus: resource.vcpus,
disk_size_gb: resource.disk_size_gb,
disk_size_mib: resource.disk_size_mib,
locked_nano: val.locked_nano,
price_per_unit: val.price_per_unit,
error: String::new(),
@ -332,8 +342,8 @@ impl From<db::NewAppReq> for NewAppReq {
fn from(value: db::NewAppReq) -> Self {
let resource = AppResource {
vcpus: value.vcpus,
memory_mb: value.memory_mb,
disk_size_gb: value.disk_size_gb,
memory_mib: value.memory_mib,
disk_size_mib: value.disk_size_mib,
ports: value.ports,
};
let mr_enclave = Some(hex::decode(value.mr_enclave).unwrap_or_default());
@ -377,8 +387,8 @@ impl From<AppNodeResources> for db::AppNodeResources {
Self {
avail_ports: value.avail_no_of_port,
avail_vcpus: value.avail_vcpus,
avail_mem_mb: value.avail_memory_mb,
avail_storage_gbs: value.avail_storage_gb,
avail_mem_mib: value.avail_memory_mib,
avail_storage_mib: value.avail_storage_mib,
max_ports_per_app: value.max_ports_per_app,
}
}

@ -51,9 +51,9 @@ impl BrainVmDaemon for VmDaemonServer {
city: req.city,
ip: req.main_ip,
price: req.price,
avail_mem_mb: 0,
avail_mem_mib: 0,
avail_vcpus: 0,
avail_storage_gbs: 0,
avail_storage_mib: 0,
avail_ipv4: 0,
avail_ipv6: 0,
avail_ports: 0,
@ -273,10 +273,17 @@ impl BrainVmCli for VmCliServer {
let db_req: db::UpdateVmReq = req.clone().into();
if let Some(redirect) = db::check_pubsub_node(&self.db, db_req.vm_node.clone()).await? {
log::info!("redirect: {redirect}");
return Err(redirect);
}
println!("The node is {}", db_req.vm_node);
// TODO: vm_node is not known at this point. It is populated by `request_hw_update`.
// As such, the pubsub node cannot be checked at this stage. This code should be moved,
// however we are not working on the redirect mechanic at the moment
//
// if let Some(redirect) = db::check_pubsub_node(&self.db, db_req.vm_node.clone()).await? {
// log::info!("redirect: {redirect}");
// return Err(redirect);
// }
let id = db_req.id.key().to_string();
let mut hostname_changed = false;

@ -6,7 +6,7 @@ DEFINE FUNCTION OVERWRITE fn::vm_price_per_minute(
LET $vm = (select * from $vm_id)[0];
LET $ip_price = IF $vm.public_ipv4.len() > 0 { 10 } ELSE { 0 };
RETURN (
($vm.vcpus * 10) + (($vm.memory_mb + 256) / 200) + ($vm.disk_size_gb / 10) + $ip_price)
($vm.vcpus * 10) + (($vm.memory_mib + 256) / 200) + ($vm.disk_size_mib / 1024 / 10) + $ip_price)
* $vm.price_per_unit;
};
@ -33,8 +33,8 @@ DEFINE FUNCTION OVERWRITE fn::app_price_per_minute(
LET $app = (select * from $app_id)[0];
RETURN
(($app.vcpus * 5) +
($app.memory_mb / 200) +
($app.disk_size_gb / 10))
($app.memory_mib / 200) +
($app.disk_size_mib / 10))
* $app.price_per_unit;
};
@ -53,4 +53,4 @@ DEFINE FUNCTION OVERWRITE fn::delete_app(
};
INSERT RELATION INTO deleted_app ( $deleted_app );
RETURN DELETE $app.id RETURN BEFORE;
};
};

@ -13,9 +13,9 @@ DEFINE FIELD country ON TABLE vm_node TYPE string;
DEFINE FIELD region ON TABLE vm_node TYPE string;
DEFINE FIELD city ON TABLE vm_node TYPE string;
DEFINE FIELD ip ON TABLE vm_node TYPE string;
DEFINE FIELD avail_mem_mb ON TABLE vm_node TYPE int;
DEFINE FIELD avail_mem_mib ON TABLE vm_node TYPE int;
DEFINE FIELD avail_vcpus ON TABLE vm_node TYPE int;
DEFINE FIELD avail_storage_gbs ON TABLE vm_node TYPE int;
DEFINE FIELD avail_storage_mib ON TABLE vm_node TYPE int;
DEFINE FIELD avail_ipv4 ON TABLE vm_node TYPE int;
DEFINE FIELD avail_ipv6 ON TABLE vm_node TYPE int;
DEFINE FIELD avail_ports ON TABLE vm_node TYPE int;
@ -29,9 +29,9 @@ DEFINE FIELD hostname ON TABLE new_vm_req TYPE string;
DEFINE FIELD extra_ports ON TABLE new_vm_req TYPE array<int>;
DEFINE FIELD public_ipv4 ON TABLE new_vm_req TYPE bool;
DEFINE FIELD public_ipv6 ON TABLE new_vm_req TYPE bool;
DEFINE FIELD disk_size_gb ON TABLE new_vm_req TYPE int;
DEFINE FIELD disk_size_mib ON TABLE new_vm_req TYPE int;
DEFINE FIELD vcpus ON TABLE new_vm_req TYPE int;
DEFINE FIELD memory_mb ON TABLE new_vm_req TYPE int;
DEFINE FIELD memory_mib ON TABLE new_vm_req TYPE int;
DEFINE FIELD dtrfs_sha ON TABLE new_vm_req TYPE string;
DEFINE FIELD dtrfs_url ON TABLE new_vm_req TYPE string;
DEFINE FIELD kernel_sha ON TABLE new_vm_req TYPE string;
@ -46,9 +46,9 @@ DEFINE FIELD hostname ON TABLE active_vm TYPE string;
DEFINE FIELD mapped_ports ON TABLE active_vm TYPE array<[int, int]>;
DEFINE FIELD public_ipv4 ON TABLE active_vm TYPE string;
DEFINE FIELD public_ipv6 ON TABLE active_vm TYPE string;
DEFINE FIELD disk_size_gb ON TABLE active_vm TYPE int;
DEFINE FIELD disk_size_mib ON TABLE active_vm TYPE int;
DEFINE FIELD vcpus ON TABLE active_vm TYPE int;
DEFINE FIELD memory_mb ON TABLE active_vm TYPE int;
DEFINE FIELD memory_mib ON TABLE active_vm TYPE int;
DEFINE FIELD dtrfs_sha ON TABLE active_vm TYPE string;
DEFINE FIELD kernel_sha ON TABLE active_vm TYPE string;
DEFINE FIELD created_at ON TABLE active_vm TYPE datetime;
@ -58,8 +58,8 @@ DEFINE FIELD collected_at ON TABLE active_vm TYPE datetime;
DEFINE TABLE update_vm_req TYPE RELATION FROM account TO vm_node SCHEMAFULL;
DEFINE FIELD vcpus ON TABLE update_vm_req TYPE int;
DEFINE FIELD memory_mb ON TABLE update_vm_req TYPE int;
DEFINE FIELD disk_size_gb ON TABLE update_vm_req TYPE int;
DEFINE FIELD memory_mib ON TABLE update_vm_req TYPE int;
DEFINE FIELD disk_size_mib ON TABLE update_vm_req TYPE int;
DEFINE FIELD dtrfs_sha ON TABLE update_vm_req TYPE string;
DEFINE FIELD dtrfs_url ON TABLE update_vm_req TYPE string;
DEFINE FIELD kernel_sha ON TABLE update_vm_req TYPE string;
@ -72,9 +72,9 @@ DEFINE FIELD hostname ON TABLE deleted_vm TYPE string;
DEFINE FIELD mapped_ports ON TABLE deleted_vm TYPE array<[int, int]>;
DEFINE FIELD public_ipv4 ON TABLE deleted_vm TYPE string;
DEFINE FIELD public_ipv6 ON TABLE deleted_vm TYPE string;
DEFINE FIELD disk_size_gb ON TABLE deleted_vm TYPE int;
DEFINE FIELD disk_size_mib ON TABLE deleted_vm TYPE int;
DEFINE FIELD vcpus ON TABLE deleted_vm TYPE int;
DEFINE FIELD memory_mb ON TABLE deleted_vm TYPE int;
DEFINE FIELD memory_mib ON TABLE deleted_vm TYPE int;
DEFINE FIELD dtrfs_sha ON TABLE deleted_vm TYPE string;
DEFINE FIELD kernel_sha ON TABLE deleted_vm TYPE string;
DEFINE FIELD created_at ON TABLE deleted_vm TYPE datetime;
@ -88,13 +88,14 @@ DEFINE FIELD country ON TABLE app_node TYPE string;
DEFINE FIELD region ON TABLE app_node TYPE string;
DEFINE FIELD city ON TABLE app_node TYPE string;
DEFINE FIELD ip ON TABLE app_node TYPE string;
DEFINE FIELD avail_mem_mb ON TABLE app_node TYPE int;
DEFINE FIELD avail_mem_mib ON TABLE app_node TYPE int;
DEFINE FIELD avail_vcpus ON TABLE app_node TYPE int;
DEFINE FIELD avail_storage_gbs ON TABLE app_node TYPE int;
DEFINE FIELD avail_storage_mib ON TABLE app_node TYPE int;
DEFINE FIELD avail_ports ON TABLE app_node TYPE int;
DEFINE FIELD max_ports_per_app ON TABLE app_node TYPE int;
DEFINE FIELD price ON TABLE app_node TYPE int;
DEFINE FIELD offline_minutes ON TABLE app_node TYPE int;
DEFINE FIELD connected_at ON TABLE app_node TYPE datetime;
DEFINE FIELD disconnected_at ON TABLE app_node TYPE datetime;
DEFINE TABLE new_app_req Type RELATION FROM account to app_node SCHEMAFULL;
DEFINE FIELD app_name ON TABLE new_app_req TYPE string;
@ -102,9 +103,9 @@ DEFINE FIELD package_url ON TABLE new_app_req TYPE string;
DEFINE FIELD mr_enclave ON TABLE new_app_req TYPE string;
DEFINE FIELD hratls_pubkey ON TABLE new_app_req TYPE string;
DEFINE FIELD ports ON TABLE new_app_req TYPE array<int>;
DEFINE FIELD memory_mb ON TABLE new_app_req TYPE int;
DEFINE FIELD memory_mib ON TABLE new_app_req TYPE int;
DEFINE FIELD vcpus ON TABLE new_app_req TYPE int;
DEFINE FIELD disk_size_gb ON TABLE new_app_req TYPE int;
DEFINE FIELD disk_size_mib ON TABLE new_app_req TYPE int;
DEFINE FIELD locked_nano ON TABLE new_app_req TYPE int;
DEFINE FIELD price_per_unit ON TABLE new_app_req TYPE int;
DEFINE FIELD error ON TABLE new_app_req TYPE string;
@ -115,8 +116,8 @@ DEFINE FIELD app_name ON TABLE active_app TYPE string;
DEFINE FIELD mapped_ports ON TABLE active_app TYPE array<[int, int]>;
DEFINE FIELD host_ipv4 ON TABLE active_app TYPE string;
DEFINE FIELD vcpus ON TABLE active_app TYPE int;
DEFINE FIELD memory_mb ON TABLE active_app TYPE int;
DEFINE FIELD disk_size_gb ON TABLE active_app TYPE int;
DEFINE FIELD memory_mib ON TABLE active_app TYPE int;
DEFINE FIELD disk_size_mib ON TABLE active_app TYPE int;
DEFINE FIELD created_at ON TABLE active_app TYPE datetime;
DEFINE FIELD price_per_unit ON TABLE active_app TYPE int;
DEFINE FIELD locked_nano ON TABLE active_app TYPE int;
@ -130,8 +131,8 @@ DEFINE FIELD app_name ON TABLE deleted_app TYPE string;
DEFINE FIELD mapped_ports ON TABLE deleted_app TYPE array<[int, int]>;
DEFINE FIELD host_ipv4 ON TABLE deleted_app TYPE string;
DEFINE FIELD vcpus ON TABLE deleted_app TYPE int;
DEFINE FIELD memory_mb ON TABLE deleted_app TYPE int;
DEFINE FIELD disk_size_gb ON TABLE deleted_app TYPE int;
DEFINE FIELD memory_mib ON TABLE deleted_app TYPE int;
DEFINE FIELD disk_size_mib ON TABLE deleted_app TYPE int;
DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime;
DEFINE FIELD deleted_at ON TABLE deleted_app TYPE datetime DEFAULT time::now();
DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int;

@ -10,9 +10,8 @@ FOR $contract IN (select * from active_vm fetch out) {
} ELSE {
$amount_due
};
LET $escrow_multiplier = IF $operator.escrow < 5_000_000_000_000 { 1 } ELSE { 5 };
IF $node_is_online {
UPDATE $operator.id SET balance += $amount_paid * $escrow_multiplier;
UPDATE $operator.id SET balance += $amount_paid;
UPDATE $contract.id SET
locked_nano -= $amount_paid,
collected_at = time::now();
@ -23,11 +22,41 @@ FOR $contract IN (select * from active_vm fetch out) {
$amount_due
};
UPDATE $operator.id SET escrow -= $compensation;
UPDATE $contract.in SET balance += $compensation;
UPDATE $contract.id SET
locked_nano += $compensation,
collected_at = time::now();
};
IF $amount_paid >= $contract.locked_nano {
fn::delete_vm($contract.id);
};
};
-- TODO: implement for active_app
FOR $app_contract IN (select * from active_app fetch out) {
LET $operator = (select * from $app_contract.out.operator)[0];
LET $node_is_online = $app_contract.out.connected_at > $app_contract.out.disconnected_at;
LET $price_per_minute = fn::app_price_per_minute($app_contract.id);
LET $amount_due = (time::now() - $app_contract.collected_at).mins() * $price_per_minute;
LET $amount_paid = IF $amount_due > $app_contract.locked_nano {
$app_contract.locked_nano
} ELSE {
$amount_due
};
LET $escrow_multiplier = IF $operator.escrow < 5_000_000_000_000 { 1 } ELSE { 5 };
IF $node_is_online {
UPDATE $operator.id SET balance += $amount_paid * $escrow_multiplier;
UPDATE $app_contract.id SET
locked_nano -= $amount_paid,
collected_at = time::now();
} ELSE {
LET $compensation = IF $amount_due > $operator.escrow {
$operator.escrow
} ELSE {
$amount_due
};
UPDATE $operator.id SET escrow -= $compensation;
UPDATE $app_contract.in SET balance += $compensation;
};
IF $amount_paid >= $app_contract.locked_nano {
fn::delete_app($app_contract.id);
}
}

@ -1,9 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use detee_shared::app_proto::{
brain_app_cli_client::BrainAppCliClient, AppResource, NewAppReq, NewAppRes,
};
use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient;
use detee_shared::app_proto::{AppResource, NewAppReq, NewAppRes};
use tonic::transport::Channel;
use crate::common::test_utils::Key;

@ -1,10 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use detee_shared::app_proto as sgx_proto;
use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient;
use detee_shared::general_proto::AirdropReq;
use detee_shared::vm_proto as snp_proto;
use detee_shared::{app_proto, vm_proto};
use ed25519_dalek::{Signer, SigningKey};
use itertools::Itertools;
use rand::Rng;
@ -75,20 +74,20 @@ impl Key {
pub fn sign_stream_auth_vm(
&self,
contracts: Vec<String>,
) -> Result<snp_proto::DaemonStreamAuth> {
) -> Result<vm_proto::DaemonStreamAuth> {
let pubkey = self.pubkey.clone();
let timestamp = chrono::Utc::now().to_rfc3339();
let signature =
self.try_sign_message(&(timestamp.to_string() + &format!("{contracts:?}")))?;
Ok(snp_proto::DaemonStreamAuth { timestamp, pubkey, contracts, signature })
Ok(vm_proto::DaemonStreamAuth { timestamp, pubkey, contracts, signature })
}
pub fn sign_stream_auth_app(&self, contracts: Vec<String>) -> Result<sgx_proto::DaemonAuth> {
pub fn sign_stream_auth_app(&self, contracts: Vec<String>) -> Result<app_proto::DaemonAuth> {
let pubkey = self.pubkey.clone();
let timestamp = chrono::Utc::now().to_rfc3339();
let signature =
self.try_sign_message(&(timestamp.to_string() + &format!("{contracts:?}")))?;
Ok(sgx_proto::DaemonAuth { timestamp, pubkey, contracts, signature })
Ok(app_proto::DaemonAuth { timestamp, pubkey, contracts, signature })
}
}

@ -2,12 +2,11 @@
use super::test_utils::Key;
use anyhow::{anyhow, Result};
use detee_shared::app_proto;
use detee_shared::common_proto::Empty;
use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient;
use detee_shared::general_proto::{Account, RegOperatorReq, ReportNodeReq};
use detee_shared::vm_proto;
use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient;
use detee_shared::{app_proto, vm_proto};
use futures::StreamExt;
use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ};
use surreal_brain::db::prelude as db;

@ -9,25 +9,25 @@ async fn test_new_app_db_tx() {
let db = prepare_test_db().await.unwrap();
let req = NewAppReq {
package_url: "https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-04-16_21-59-38.tar.gz".to_string(),
node_pubkey: "AH3SpV6ZjXMGSSe6xGH2ekUZxyUhnesAFz4LjX7PnvVn".to_string(),
resource: Some(
AppResource {
memory_mb: 1500,
disk_size_gb: 2,
vcpus: 1,
ports: vec![ 8080 ],
},
),
uuid: "".to_string(),
admin_pubkey: "H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc".to_string(),
price_per_unit: 200000,
locked_nano: 152400000,
hratls_pubkey: "7E0F887AA6BB9104EEC1066F454D4C2D9063D676715F55F919D3FBCEDC63240B".to_string(),
public_package_mr_enclave: Some(
vec![ 128, 0, 97, 103, 165, 103, 68, 203, 240, 145, 153, 254, 34, 129, 75, 140, 8, 186, 63, 226, 144, 129, 201, 187, 175, 66, 80, 1, 151, 114, 183, 159, ],
),
app_name: "lively-ferret".to_string(),
package_url: "https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-04-16_21-59-38.tar.gz".to_string(),
node_pubkey: "AH3SpV6ZjXMGSSe6xGH2ekUZxyUhnesAFz4LjX7PnvVn".to_string(),
resource: Some(
AppResource {
memory_mib: 1500,
disk_size_mib: 2000,
vcpus: 1,
ports: vec![ 8080 ],
},
),
uuid: "".to_string(),
admin_pubkey: "H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc".to_string(),
price_per_unit: 200000,
locked_nano: 152400000,
hratls_pubkey: "7E0F887AA6BB9104EEC1066F454D4C2D9063D676715F55F919D3FBCEDC63240B".to_string(),
public_package_mr_enclave: Some(
vec![ 128, 0, 97, 103, 165, 103, 68, 203, 240, 145, 153, 254, 34, 129, 75, 140, 8, 186, 63, 226, 144, 129, 201, 187, 175, 66, 80, 1, 151, 114, 183, 159, ],
),
app_name: "lively-ferret".to_string(),
};
let db_req: db::NewAppReq = req.into();

@ -93,6 +93,7 @@ async fn test_app_creation() {
db.select::<Option<db::ActiveApp>>((ACTIVE_APP, new_app_resp.uuid)).await.unwrap();
assert!(active_app.is_some());
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
let acc_db: db::Account = db.select((ACCOUNT, key.pubkey.clone())).await.unwrap().unwrap();
assert_eq!(acc_db.balance, airdrop_amount * TOKEN_DECIMAL - (locking_nano + 100));
assert_eq!(acc_db.tmp_locked, 0);

@ -102,8 +102,8 @@ async fn test_app_daemon_resource_msg() {
node_pubkey: daemon_pubkey,
avail_no_of_port: 5,
avail_vcpus: 4,
avail_memory_mb: 8192,
avail_storage_gb: 100,
avail_memory_mib: 8192,
avail_storage_mib: 10_0000,
max_ports_per_app: 5,
};
@ -123,17 +123,17 @@ async fn test_app_daemon_resource_msg() {
let app_node_opt: Option<AppNode> = db.select((APP_NODE, daemon_key.pubkey)).await.unwrap();
assert!(app_node_opt.is_some());
let db::AppNode {
avail_mem_mb,
avail_mem_mib,
avail_vcpus,
avail_storage_gbs,
avail_storage_mib,
avail_ports,
max_ports_per_app,
..
} = app_node_opt.unwrap();
assert_eq!(avail_mem_mb, req_data.avail_memory_mb);
assert_eq!(avail_mem_mib, req_data.avail_memory_mib);
assert_eq!(avail_vcpus, req_data.avail_vcpus);
assert_eq!(avail_storage_gbs, req_data.avail_storage_gb);
assert_eq!(avail_storage_mib, req_data.avail_storage_mib);
assert_eq!(avail_ports, req_data.avail_no_of_port);
assert_eq!(max_ports_per_app, req_data.max_ports_per_app);
}

@ -3,12 +3,11 @@
use common::prepare_test_env::{prepare_test_db, run_service_for_stream};
use common::test_utils::{airdrop, Key};
use common::vm_daemon_utils::register_vm_node;
use detee_shared::app_proto;
use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient;
use detee_shared::app_proto::brain_app_daemon_client::BrainAppDaemonClient;
use detee_shared::vm_proto;
use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient;
use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient;
use detee_shared::{app_proto, vm_proto};
use crate::common::app_daemon_utils::register_app_node;

@ -91,8 +91,8 @@ async fn test_vm_daemon_resource_msg() {
avail_ipv4: 2,
avail_ipv6: 88,
avail_vcpus: 4,
avail_memory_mb: 8192,
avail_storage_gb: 100,
avail_memory_mib: 8192,
avail_storage_mib: 102400,
max_ports_per_vm: 5,
};
@ -114,9 +114,9 @@ async fn test_vm_daemon_resource_msg() {
assert!(vm_node_opt.is_some());
let db::VmNode {
avail_mem_mb,
avail_mem_mib,
avail_vcpus,
avail_storage_gbs,
avail_storage_mib,
avail_ports,
avail_ipv4,
avail_ipv6,
@ -124,9 +124,9 @@ async fn test_vm_daemon_resource_msg() {
..
} = vm_node_opt.unwrap();
assert_eq!(avail_mem_mb, req_data.avail_memory_mb);
assert_eq!(avail_mem_mib, req_data.avail_memory_mib);
assert_eq!(avail_vcpus, req_data.avail_vcpus);
assert_eq!(avail_storage_gbs, req_data.avail_storage_gb);
assert_eq!(avail_storage_mib, req_data.avail_storage_mib);
assert_eq!(avail_ports, req_data.avail_ports);
assert_eq!(avail_ipv4, req_data.avail_ipv4);
assert_eq!(avail_ipv6, req_data.avail_ipv6);

@ -312,7 +312,7 @@ vm_contracts:
exposed_ports: []
public_ipv4: 156.146.63.216
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 2
memory_mb: 3000
kernel_sha: 3ec4fc5aa5729f515967ec71be4a851622785c0080f7191b1b07717149840151
@ -329,7 +329,7 @@ vm_contracts:
exposed_ports: []
public_ipv4: 173.234.136.154
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 2
memory_mb: 3000
kernel_sha: 3ec4fc5aa5729f515967ec71be4a851622785c0080f7191b1b07717149840151
@ -347,7 +347,7 @@ vm_contracts:
- 38288
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 1
memory_mb: 1000
kernel_sha: 14e225e4aaf84cc2e0b5f64206121186ddebc4b378b886da3b2f7515dfd41692
@ -364,7 +364,7 @@ vm_contracts:
exposed_ports: []
public_ipv4: 149.22.95.2
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 2
memory_mb: 3000
kernel_sha: 3a68709138bed09c16671949cf1f03acee95a08381ba84fc70fb586001fa6767
@ -381,7 +381,7 @@ vm_contracts:
exposed_ports: []
public_ipv4: 156.146.63.217
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 2
memory_mb: 3000
kernel_sha: e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542
@ -415,7 +415,7 @@ vm_contracts:
exposed_ports: []
public_ipv4: 149.36.48.100
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 4
memory_mb: 4000
kernel_sha: e49c8587287b21df7600c04326fd7393524453918c14d67f73757dc769a13542
@ -433,7 +433,7 @@ vm_contracts:
- 46393
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 1
memory_mb: 1000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
@ -452,7 +452,7 @@ vm_contracts:
- 46393
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 1
memory_mb: 1000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
@ -470,7 +470,7 @@ vm_contracts:
- 46393
public_ipv4: ""
public_ipv6: ""
disk_size_gb: 10
disk_size_gb: 10240
vcpus: 1
memory_mb: 1000
kernel_sha: e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919
@ -480,6 +480,7 @@ vm_contracts:
price_per_unit: 20000
locked_nano: 12730960000
collected_at: 2025-04-20T00:34:15.461240342Z
app_nodes:
- node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg
operator_wallet: 7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB