Compare commits
	
		
			2 Commits
		
	
	
		
			198f43f472
			...
			0ec1b61d8b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0ec1b61d8b | |||
| d1e85ec03e | 
							
								
								
									
										109
									
								
								saved_data.yaml
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										109
									
								
								saved_data.yaml
									
									
									
									
									
								
							| @ -128,14 +128,14 @@ operators: | |||||||
|       - 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 |       - 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 | ||||||
|     app_nodes: [] |     app_nodes: [] | ||||||
|   7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB: |   7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB: | ||||||
|     escrow: 0 |     escrow: 888888888899999 | ||||||
|     email: '' |     email: "" | ||||||
|     banned_users: [] |     banned_users: [] | ||||||
|     vm_nodes: [] |     vm_nodes: [] | ||||||
|     app_nodes: |     app_nodes: | ||||||
|       - BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg |       - BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg | ||||||
| vm_nodes: | vm_nodes: | ||||||
| - public_key: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 |   - public_key: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 | ||||||
|     operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK |     operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK | ||||||
|     country: GB |     country: GB | ||||||
|     region: England |     region: England | ||||||
| @ -151,7 +151,7 @@ vm_nodes: | |||||||
|     price: 20000 |     price: 20000 | ||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| - public_key: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu |   - public_key: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu | ||||||
|     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS |     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS | ||||||
|     country: FR |     country: FR | ||||||
|     region: Île-de-France |     region: Île-de-France | ||||||
| @ -167,7 +167,7 @@ vm_nodes: | |||||||
|     price: 20000 |     price: 20000 | ||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| - public_key: 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f |   - public_key: 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f | ||||||
|     operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK |     operator_wallet: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK | ||||||
|     country: CA |     country: CA | ||||||
|     region: Quebec |     region: Quebec | ||||||
| @ -183,7 +183,7 @@ vm_nodes: | |||||||
|     price: 18000 |     price: 18000 | ||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| - public_key: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb |   - public_key: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb | ||||||
|     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS |     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS | ||||||
|     country: CA |     country: CA | ||||||
|     region: British Columbia |     region: British Columbia | ||||||
| @ -199,7 +199,7 @@ vm_nodes: | |||||||
|     price: 20000 |     price: 20000 | ||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| - public_key: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 |   - public_key: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 | ||||||
|     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS |     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS | ||||||
|     country: US |     country: US | ||||||
|     region: California |     region: California | ||||||
| @ -215,7 +215,7 @@ vm_nodes: | |||||||
|     price: 20000 |     price: 20000 | ||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| - public_key: HiyMp21zaBVbRCjDsD5hEjQnHeHv4e1gpUR6pVfHTKqv |   - public_key: HiyMp21zaBVbRCjDsD5hEjQnHeHv4e1gpUR6pVfHTKqv | ||||||
|     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS |     operator_wallet: BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS | ||||||
|     country: CA |     country: CA | ||||||
|     region: British Columbia |     region: British Columbia | ||||||
| @ -232,13 +232,13 @@ vm_nodes: | |||||||
|     reports: {} |     reports: {} | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| vm_contracts: | vm_contracts: | ||||||
| - uuid: 958165e3-dea8-407d-8c42-dd17002ef79c |   - uuid: 958165e3-dea8-407d-8c42-dd17002ef79c | ||||||
|     hostname: detee-landing-fr |     hostname: detee-landing-fr | ||||||
|     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL |     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL | ||||||
|     node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu |     node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 156.146.63.216 |     public_ipv4: 156.146.63.216 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 2 |     vcpus: 2 | ||||||
|     memory_mb: 3000 |     memory_mb: 3000 | ||||||
| @ -249,13 +249,13 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 14875500000 |     locked_nano: 14875500000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461165181Z |     collected_at: 2025-04-20T00:34:15.461165181Z | ||||||
| - uuid: e807a2fd-cf90-4a14-bc3a-89ce6dc59033 |   - uuid: e807a2fd-cf90-4a14-bc3a-89ce6dc59033 | ||||||
|     hostname: detee-landing-gb |     hostname: detee-landing-gb | ||||||
|     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL |     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL | ||||||
|     node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 |     node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 173.234.136.154 |     public_ipv4: 173.234.136.154 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 2 |     vcpus: 2 | ||||||
|     memory_mb: 3000 |     memory_mb: 3000 | ||||||
| @ -266,14 +266,14 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 14875500000 |     locked_nano: 14875500000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461181545Z |     collected_at: 2025-04-20T00:34:15.461181545Z | ||||||
| - uuid: 23094406-2307-4332-a642-acee718d0186 |   - uuid: 23094406-2307-4332-a642-acee718d0186 | ||||||
|     hostname: heroic-door |     hostname: heroic-door | ||||||
|     admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX |     admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX | ||||||
|     node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 |     node_pubkey: 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 | ||||||
|     exposed_ports: |     exposed_ports: | ||||||
|       - 38288 |       - 38288 | ||||||
|   public_ipv4: '' |     public_ipv4: "" | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 1 |     vcpus: 1 | ||||||
|     memory_mb: 1000 |     memory_mb: 1000 | ||||||
| @ -284,13 +284,13 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 14134140000 |     locked_nano: 14134140000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461191231Z |     collected_at: 2025-04-20T00:34:15.461191231Z | ||||||
| - uuid: 1f49a71c-f68c-4c64-a82e-f50e0ba0b574 |   - uuid: 1f49a71c-f68c-4c64-a82e-f50e0ba0b574 | ||||||
|     hostname: astromech-wrench |     hostname: astromech-wrench | ||||||
|     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL |     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL | ||||||
|     node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb |     node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 149.22.95.2 |     public_ipv4: 149.22.95.2 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 2 |     vcpus: 2 | ||||||
|     memory_mb: 3000 |     memory_mb: 3000 | ||||||
| @ -301,13 +301,13 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 11865620000 |     locked_nano: 11865620000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461201690Z |     collected_at: 2025-04-20T00:34:15.461201690Z | ||||||
| - uuid: 16577f1c-9867-4a17-80a8-6cf0490f1270 |   - uuid: 16577f1c-9867-4a17-80a8-6cf0490f1270 | ||||||
|     hostname: sofenty |     hostname: sofenty | ||||||
|     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL |     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL | ||||||
|     node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu |     node_pubkey: Du3UfPSUUZmA5thQmc9Vrxdy7UimpygcpDsQNnwRQPtu | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 156.146.63.217 |     public_ipv4: 156.146.63.217 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 2 |     vcpus: 2 | ||||||
|     memory_mb: 3000 |     memory_mb: 3000 | ||||||
| @ -318,13 +318,13 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 11867500000 |     locked_nano: 11867500000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461211040Z |     collected_at: 2025-04-20T00:34:15.461211040Z | ||||||
| - uuid: 4b6e25ca-87ac-478b-8f16-aa8f5c44c704 |   - uuid: 4b6e25ca-87ac-478b-8f16-aa8f5c44c704 | ||||||
|     hostname: cloaked-mailbox |     hostname: cloaked-mailbox | ||||||
|     admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX |     admin_pubkey: DwfL5iFu32xh2YMCUxg63oEAThLRqehDAumiP9q6zuuX | ||||||
|     node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb |     node_pubkey: DgkbsrwttkZXvzxY5kDwQQoDd79GLmZ5tc7fYJUFkQQb | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 149.22.95.2 |     public_ipv4: 149.22.95.2 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 30 |     disk_size_gb: 30 | ||||||
|     vcpus: 1 |     vcpus: 1 | ||||||
|     memory_mb: 1000 |     memory_mb: 1000 | ||||||
| @ -335,13 +335,13 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 11177760000 |     locked_nano: 11177760000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461219779Z |     collected_at: 2025-04-20T00:34:15.461219779Z | ||||||
| - uuid: eb1a13ed-d782-4b71-8860-73540129cb7d |   - uuid: eb1a13ed-d782-4b71-8860-73540129cb7d | ||||||
|     hostname: twenty |     hostname: twenty | ||||||
|     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL |     admin_pubkey: FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL | ||||||
|     node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 |     node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 | ||||||
|     exposed_ports: [] |     exposed_ports: [] | ||||||
|     public_ipv4: 149.36.48.100 |     public_ipv4: 149.36.48.100 | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 4 |     vcpus: 4 | ||||||
|     memory_mb: 4000 |     memory_mb: 4000 | ||||||
| @ -352,14 +352,14 @@ vm_contracts: | |||||||
|     price_per_unit: 20000 |     price_per_unit: 20000 | ||||||
|     locked_nano: 15570720000 |     locked_nano: 15570720000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461230948Z |     collected_at: 2025-04-20T00:34:15.461230948Z | ||||||
| - uuid: 1bf36309-3774-4825-b023-b2a0ef0405ed |   - uuid: 1bf36309-3774-4825-b023-b2a0ef0405ed | ||||||
|     hostname: shadowy-hobo |     hostname: shadowy-hobo | ||||||
|     admin_pubkey: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK |     admin_pubkey: x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK | ||||||
|     node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 |     node_pubkey: 3zRxiGRnf46vd3zAEmpaYBJocTV9oJB6yXf5GZFR1Sq4 | ||||||
|     exposed_ports: |     exposed_ports: | ||||||
|       - 46393 |       - 46393 | ||||||
|   public_ipv4: '' |     public_ipv4: "" | ||||||
|   public_ipv6: '' |     public_ipv6: "" | ||||||
|     disk_size_gb: 10 |     disk_size_gb: 10 | ||||||
|     vcpus: 1 |     vcpus: 1 | ||||||
|     memory_mb: 1000 |     memory_mb: 1000 | ||||||
| @ -371,7 +371,7 @@ vm_contracts: | |||||||
|     locked_nano: 12730960000 |     locked_nano: 12730960000 | ||||||
|     collected_at: 2025-04-20T00:34:15.461240342Z |     collected_at: 2025-04-20T00:34:15.461240342Z | ||||||
| app_nodes: | app_nodes: | ||||||
| - node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg |   - node_pubkey: BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg | ||||||
|     operator_wallet: 7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB |     operator_wallet: 7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB | ||||||
|     country: DE |     country: DE | ||||||
|     region: Hesse |     region: Hesse | ||||||
| @ -384,4 +384,57 @@ app_nodes: | |||||||
|     max_ports_per_app: 9 |     max_ports_per_app: 9 | ||||||
|     price: 20000 |     price: 20000 | ||||||
|     offline_minutes: 0 |     offline_minutes: 0 | ||||||
| app_contracts: [] | 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 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ pub const CERT_PATH: &str = "/etc/detee/brain/brain-crt.pem"; | |||||||
| pub const CERT_KEY_PATH: &str = "/etc/detee/brain/brain-key.pem"; | pub const CERT_KEY_PATH: &str = "/etc/detee/brain/brain-key.pem"; | ||||||
| pub const CONFIG_PATH: &str = "/etc/detee/brain/config.ini"; | pub const CONFIG_PATH: &str = "/etc/detee/brain/config.ini"; | ||||||
| 
 | 
 | ||||||
| pub const DB_SCHEMA_FILE: &str = "interim_tables.surql"; | pub const DB_SCHEMA_FILES: [&str; 2] = ["surql/tables.sql", "surql/functions.sql"]; | ||||||
| 
 | 
 | ||||||
| pub static ADMIN_ACCOUNTS: LazyLock<Vec<String>> = LazyLock::new(|| { | pub static ADMIN_ACCOUNTS: LazyLock<Vec<String>> = LazyLock::new(|| { | ||||||
|     let default_admin_keys = vec![ |     let default_admin_keys = vec![ | ||||||
| @ -23,6 +23,9 @@ pub static ADMIN_ACCOUNTS: LazyLock<Vec<String>> = LazyLock::new(|| { | |||||||
| pub const OLD_BRAIN_DATA_PATH: &str = "./saved_data.yaml"; | pub const OLD_BRAIN_DATA_PATH: &str = "./saved_data.yaml"; | ||||||
| 
 | 
 | ||||||
| pub const ACCOUNT: &str = "account"; | pub const ACCOUNT: &str = "account"; | ||||||
|  | pub const KICK: &str = "kick"; | ||||||
|  | pub const BAN: &str = "ban"; | ||||||
|  | 
 | ||||||
| pub const VM_NODE: &str = "vm_node"; | pub const VM_NODE: &str = "vm_node"; | ||||||
| pub const ACTIVE_VM: &str = "active_vm"; | pub const ACTIVE_VM: &str = "active_vm"; | ||||||
| pub const VM_UPDATE_EVENT: &str = "vm_update_event"; | pub const VM_UPDATE_EVENT: &str = "vm_update_event"; | ||||||
| @ -42,3 +45,6 @@ pub const ID_ALPHABET: [char; 62] = [ | |||||||
|     'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', |     'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', | ||||||
|     'V', 'W', 'X', 'Y', 'Z', |     'V', 'W', 'X', 'Y', 'Z', | ||||||
| ]; | ]; | ||||||
|  | 
 | ||||||
|  | pub const MIN_ESCROW: u64 = 5000; | ||||||
|  | pub const TOKEN_DECIMAL: u64 = 1_000_000_000; | ||||||
|  | |||||||
							
								
								
									
										132
									
								
								src/db/app.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										132
									
								
								src/db/app.rs
									
									
									
									
									
								
							| @ -12,7 +12,7 @@ use surrealdb::sql::Datetime; | |||||||
| use surrealdb::{Notification, RecordId, Surreal}; | use surrealdb::{Notification, RecordId, Surreal}; | ||||||
| use tokio_stream::StreamExt; | use tokio_stream::StreamExt; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct AppNode { | pub struct AppNode { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
|     pub operator: RecordId, |     pub operator: RecordId, | ||||||
| @ -32,8 +32,9 @@ pub struct AppNode { | |||||||
| impl AppNode { | impl AppNode { | ||||||
|     pub async fn register(self, db: &Surreal<Client>) -> Result<AppNode, Error> { |     pub async fn register(self, db: &Surreal<Client>) -> Result<AppNode, Error> { | ||||||
|         db::Account::get_or_create(db, &self.operator.key().to_string()).await?; |         db::Account::get_or_create(db, &self.operator.key().to_string()).await?; | ||||||
|         let app_node: Option<AppNode> = db.upsert(self.id.clone()).content(self).await?; |         let app_node_id = self.id.clone(); | ||||||
|         app_node.ok_or(Error::FailedToCreateDBEntry) |         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}"))) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -54,7 +55,7 @@ impl From<DeletedApp> for AppDaemonMsg { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct NewAppReq { | pub struct NewAppReq { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
|     #[serde(rename = "in")] |     #[serde(rename = "in")] | ||||||
| @ -97,6 +98,7 @@ impl NewAppReq { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> { |     pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> { | ||||||
|  |         // TODO: handle financial transaction
 | ||||||
|         let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?; |         let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?; | ||||||
|         Ok(new_app_req) |         Ok(new_app_req) | ||||||
|     } |     } | ||||||
| @ -164,7 +166,7 @@ impl AppNodeWithReports { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct ActiveApp { | pub struct ActiveApp { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
|     #[serde(rename = "in")] |     #[serde(rename = "in")] | ||||||
| @ -172,11 +174,11 @@ pub struct ActiveApp { | |||||||
|     #[serde(rename = "out")] |     #[serde(rename = "out")] | ||||||
|     pub app_node: RecordId, |     pub app_node: RecordId, | ||||||
|     pub app_name: String, |     pub app_name: String, | ||||||
|     pub mapped_ports: Vec<(u64, u64)>, |     pub mapped_ports: Vec<(u32, u32)>, | ||||||
|     pub host_ipv4: String, |     pub host_ipv4: String, | ||||||
|     pub vcpus: u64, |     pub vcpus: u32, | ||||||
|     pub memory_mb: u64, |     pub memory_mb: u32, | ||||||
|     pub disk_size_gb: u64, |     pub disk_size_gb: u32, | ||||||
|     pub created_at: Datetime, |     pub created_at: Datetime, | ||||||
|     pub price_per_unit: u64, |     pub price_per_unit: u64, | ||||||
|     pub locked_nano: u64, |     pub locked_nano: u64, | ||||||
| @ -210,6 +212,15 @@ impl From<ActiveApp> for DeletedApp { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl ActiveApp { | 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 activate(db: &Surreal<Client>, id: &str) -> Result<(), Error> { |     pub async fn activate(db: &Surreal<Client>, id: &str) -> Result<(), Error> { | ||||||
|         let new_app_req = match NewAppReq::get(db, id).await? { |         let new_app_req = match NewAppReq::get(db, id).await? { | ||||||
|             Some(r) => r, |             Some(r) => r, | ||||||
| @ -223,9 +234,9 @@ impl ActiveApp { | |||||||
|             app_name: new_app_req.app_name, |             app_name: new_app_req.app_name, | ||||||
|             mapped_ports: vec![], |             mapped_ports: vec![], | ||||||
|             host_ipv4: String::new(), |             host_ipv4: String::new(), | ||||||
|             vcpus: new_app_req.vcpu as u64, |             vcpus: new_app_req.vcpu, | ||||||
|             memory_mb: new_app_req.memory_mb as u64, |             memory_mb: new_app_req.memory_mb, | ||||||
|             disk_size_gb: new_app_req.disk_mb as u64, |             disk_size_gb: new_app_req.disk_mb, | ||||||
|             created_at: new_app_req.created_at.clone(), |             created_at: new_app_req.created_at.clone(), | ||||||
|             price_per_unit: new_app_req.price_per_unit, |             price_per_unit: new_app_req.price_per_unit, | ||||||
|             locked_nano: new_app_req.locked_nano, |             locked_nano: new_app_req.locked_nano, | ||||||
| @ -293,7 +304,7 @@ impl ActiveApp { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct ActiveAppWithNode { | pub struct ActiveAppWithNode { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
|     #[serde(rename = "in")] |     #[serde(rename = "in")] | ||||||
| @ -301,11 +312,11 @@ pub struct ActiveAppWithNode { | |||||||
|     #[serde(rename = "out")] |     #[serde(rename = "out")] | ||||||
|     pub app_node: AppNode, |     pub app_node: AppNode, | ||||||
|     pub app_name: String, |     pub app_name: String, | ||||||
|     pub mapped_ports: Vec<(u64, u64)>, |     pub mapped_ports: Vec<(u32, u32)>, | ||||||
|     pub host_ipv4: String, |     pub host_ipv4: String, | ||||||
|     pub vcpus: u64, |     pub vcpus: u32, | ||||||
|     pub memory_mb: u64, |     pub memory_mb: u32, | ||||||
|     pub disk_size_gb: u64, |     pub disk_size_gb: u32, | ||||||
|     pub created_at: Datetime, |     pub created_at: Datetime, | ||||||
|     pub price_per_unit: u64, |     pub price_per_unit: u64, | ||||||
|     pub locked_nano: u64, |     pub locked_nano: u64, | ||||||
| @ -315,10 +326,37 @@ pub struct ActiveAppWithNode { | |||||||
|     pub hratls_pubkey: String, |     pub hratls_pubkey: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<ActiveAppWithNode> for ActiveApp { | ||||||
|  |     fn from(val: ActiveAppWithNode) -> Self { | ||||||
|  |         Self { | ||||||
|  |             id: val.id, | ||||||
|  |             admin: val.admin, | ||||||
|  |             app_node: val.app_node.id, | ||||||
|  |             app_name: val.app_name, | ||||||
|  |             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, | ||||||
|  |             created_at: val.created_at, | ||||||
|  |             price_per_unit: val.price_per_unit, | ||||||
|  |             locked_nano: val.locked_nano, | ||||||
|  |             collected_at: val.collected_at, | ||||||
|  |             mr_enclave: val.mr_enclave, | ||||||
|  |             package_url: val.package_url, | ||||||
|  |             hratls_pubkey: val.hratls_pubkey, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ActiveAppWithNode { | impl ActiveAppWithNode { | ||||||
|     pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> { |     pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> { | ||||||
|         let contract: Option<Self> = |         let contract: Option<Self> = db | ||||||
|             db.query(format!("select * from {ACTIVE_APP}:{uuid} fetch out;")).await?.take(0)?; |             .query(format!("select * from {ACTIVE_APP} where id = $uuid_input fetch out;")) | ||||||
|  |             .bind(("uuid_input", RecordId::from((ACTIVE_APP, uuid)))) | ||||||
|  |             .await? | ||||||
|  |             .take(0)?; | ||||||
|  | 
 | ||||||
|         Ok(contract) |         Ok(contract) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -366,6 +404,12 @@ impl ActiveAppWithNode { | |||||||
|             None => Ok(vec![]), |             None => Ok(vec![]), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     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)?; | ||||||
|  |         Ok(active_apps) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize)] | #[derive(Debug, Serialize)] | ||||||
| @ -393,7 +437,7 @@ impl From<&old_brain::BrainData> for Vec<AppNode> { | |||||||
|         let mut nodes = Vec::new(); |         let mut nodes = Vec::new(); | ||||||
|         for old_node in old_data.app_nodes.iter() { |         for old_node in old_data.app_nodes.iter() { | ||||||
|             nodes.push(AppNode { |             nodes.push(AppNode { | ||||||
|                 id: RecordId::from(("app_node", old_node.node_pubkey.clone())), |                 id: RecordId::from((APP_NODE, old_node.node_pubkey.clone())), | ||||||
|                 operator: RecordId::from((ACCOUNT, old_node.operator_wallet.clone())), |                 operator: RecordId::from((ACCOUNT, old_node.operator_wallet.clone())), | ||||||
|                 country: old_node.country.clone(), |                 country: old_node.country.clone(), | ||||||
|                 region: old_node.region.clone(), |                 region: old_node.region.clone(), | ||||||
| @ -412,6 +456,46 @@ impl From<&old_brain::BrainData> for Vec<AppNode> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<&old_brain::BrainData> for Vec<ActiveApp> { | ||||||
|  |     fn from(old_data: &old_brain::BrainData) -> Self { | ||||||
|  |         let mut contracts = Vec::new(); | ||||||
|  |         for old_c in old_data.app_contracts.iter() { | ||||||
|  |             let mut mapped_ports = Vec::new(); | ||||||
|  |             for port in old_c.mapped_ports.clone().into_iter().map(|(b, c)| (b as u32, c as u32)) { | ||||||
|  |                 mapped_ports.push(port); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let mr_enclave_hex = old_c | ||||||
|  |                 .public_package_mr_enclave | ||||||
|  |                 .clone() | ||||||
|  |                 .unwrap_or_default() | ||||||
|  |                 .iter() | ||||||
|  |                 .map(|byte| format!("{byte:02X}")) | ||||||
|  |                 .collect(); | ||||||
|  | 
 | ||||||
|  |             contracts.push(ActiveApp { | ||||||
|  |                 id: RecordId::from((ACTIVE_APP, old_c.uuid.replace("-", ""))), | ||||||
|  |                 admin: RecordId::from((ACCOUNT, old_c.admin_pubkey.clone())), | ||||||
|  |                 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, | ||||||
|  |                 vcpus: old_c.vcpus, | ||||||
|  |                 memory_mb: 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(), | ||||||
|  |                 collected_at: old_c.collected_at.into(), | ||||||
|  |                 app_name: old_c.app_name.clone(), | ||||||
|  |                 mr_enclave: mr_enclave_hex, | ||||||
|  |                 package_url: old_c.package_url.clone(), | ||||||
|  |                 hratls_pubkey: old_c.hratls_pubkey.clone(), | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         contracts | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize)] | ||||||
| pub struct DeletedApp { | pub struct DeletedApp { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
| @ -420,11 +504,11 @@ pub struct DeletedApp { | |||||||
|     #[serde(rename = "out")] |     #[serde(rename = "out")] | ||||||
|     pub app_node: RecordId, |     pub app_node: RecordId, | ||||||
|     pub app_name: String, |     pub app_name: String, | ||||||
|     pub mapped_ports: Vec<(u64, u64)>, |     pub mapped_ports: Vec<(u32, u32)>, | ||||||
|     pub host_ipv4: String, |     pub host_ipv4: String, | ||||||
|     pub vcpus: u64, |     pub vcpus: u32, | ||||||
|     pub memory_mb: u64, |     pub memory_mb: u32, | ||||||
|     pub disk_size_gb: u64, |     pub disk_size_gb: u32, | ||||||
|     pub created_at: Datetime, |     pub created_at: Datetime, | ||||||
|     pub price_per_unit: u64, |     pub price_per_unit: u64, | ||||||
|     pub locked_nano: u64, |     pub locked_nano: u64, | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| use crate::constants::ACCOUNT; |  | ||||||
| use crate::db::prelude::*; |  | ||||||
| 
 |  | ||||||
| use super::Error; | use super::Error; | ||||||
|  | use crate::constants::{ACCOUNT, BAN, KICK, MIN_ESCROW, TOKEN_DECIMAL, VM_NODE}; | ||||||
|  | use crate::db::prelude::*; | ||||||
| use crate::old_brain; | use crate::old_brain; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use surrealdb::engine::remote::ws::Client; | use surrealdb::engine::remote::ws::Client; | ||||||
| @ -37,7 +36,7 @@ impl Account { | |||||||
|             Some(account) => Ok(account), |             Some(account) => Ok(account), | ||||||
|             None => { |             None => { | ||||||
|                 let account: Option<Self> = db.create(id).await?; |                 let account: Option<Self> = db.create(id).await?; | ||||||
|                 account.ok_or(Error::FailedToCreateDBEntry) |                 account.ok_or(Error::FailedToCreateDBEntry(ACCOUNT.to_string())) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -49,6 +48,70 @@ impl Account { | |||||||
|             .await?; |             .await?; | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn save(self, db: &Surreal<Client>) -> Result<Option<Self>, Error> { | ||||||
|  |         let account: Option<Self> = db.upsert(self.id.clone()).content(self).await?; | ||||||
|  |         Ok(account) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn operator_reg( | ||||||
|  |         db: &Surreal<Client>, | ||||||
|  |         wallet: &str, | ||||||
|  |         email: &str, | ||||||
|  |         escrow: u64, | ||||||
|  |     ) -> Result<(), Error> { | ||||||
|  |         if escrow < MIN_ESCROW { | ||||||
|  |             return Err(Error::MinimalEscrow); | ||||||
|  |         } | ||||||
|  |         let mut op_account = Self::get(db, wallet).await?; | ||||||
|  |         let escrow = escrow.saturating_mul(TOKEN_DECIMAL); | ||||||
|  |         let op_total_balance = op_account.balance.saturating_add(op_account.escrow); | ||||||
|  |         if op_total_balance < escrow { | ||||||
|  |             return Err(Error::InsufficientFunds); | ||||||
|  |         } | ||||||
|  |         op_account.email = email.to_string(); | ||||||
|  |         op_account.balance = op_total_balance.saturating_sub(escrow); | ||||||
|  |         op_account.escrow = escrow; | ||||||
|  |         op_account.save(db).await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn slash_account( | ||||||
|  |         db: &Surreal<Client>, | ||||||
|  |         account: &str, | ||||||
|  |         slash_amount: u64, | ||||||
|  |     ) -> Result<(), Error> { | ||||||
|  |         let tx_query = " | ||||||
|  |         BEGIN TRANSACTION; | ||||||
|  |             LET $account = $account_input; | ||||||
|  |         
 | ||||||
|  |             UPDATE $account SET escrow -= $slash_amount; | ||||||
|  |             IF $account.escrow < 0 {{ | ||||||
|  |                 THROW 'Insufficient escrow.' | ||||||
|  |             }}; | ||||||
|  | 
 | ||||||
|  |         COMMIT TRANSACTION;";
 | ||||||
|  | 
 | ||||||
|  |         let mut query_resp = db | ||||||
|  |             .query(tx_query) | ||||||
|  |             .bind(("account_input", RecordId::from((ACCOUNT, account)))) | ||||||
|  |             .bind(("slash_amount", slash_amount.saturating_mul(TOKEN_DECIMAL))) | ||||||
|  |             .await?; | ||||||
|  | 
 | ||||||
|  |         log::trace!("query_resp: {query_resp:?}"); | ||||||
|  | 
 | ||||||
|  |         let query_error = query_resp.take_errors(); | ||||||
|  |         if !query_error.is_empty() { | ||||||
|  |             log::error!("slash_account query error: {query_error:?}"); | ||||||
|  |             return Err(Error::FailedToSlashOperator(account.to_string())); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn list_accounts(db: &Surreal<Client>) -> Result<Vec<Self>, Error> { | ||||||
|  |         let accounts: Vec<Account> = db.select(ACCOUNT).await?; | ||||||
|  |         Ok(accounts) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Account { | impl Account { | ||||||
| @ -60,20 +123,27 @@ impl Account { | |||||||
|         let mut query_response = db |         let mut query_response = db | ||||||
|             .query(format!( |             .query(format!( | ||||||
|                 "(select operator->ban[0] as ban
 |                 "(select operator->ban[0] as ban
 | ||||||
|                     from vm_node:{node} |                     from $vm_node_input | ||||||
|                     where operator->ban->account contains account:{user} |                     where operator->ban->account contains $user_account_input | ||||||
|                 ).ban;" |                 ).ban;" | ||||||
|             )) |             )) | ||||||
|  |             .bind(("vm_node_input", RecordId::from((VM_NODE, node)))) | ||||||
|  |             .bind(("user_account_input", RecordId::from((ACCOUNT, user)))) | ||||||
|             .query(format!( |             .query(format!( | ||||||
|                 "(select operator->ban[0] as ban
 |                 "(select operator->ban[0] as ban
 | ||||||
|                     from app_node:{node} |                     from $app_node_input | ||||||
|                     where operator->ban->account contains account:{user} |                     where operator->ban->account contains $user_account_input | ||||||
|                 ).ban;" |                 ).ban;" | ||||||
|             )) |             )) | ||||||
|  |             .bind(("app_node_input", RecordId::from((APP_NODE, node)))) | ||||||
|  |             .bind(("user_account_input", RecordId::from((ACCOUNT, user)))) | ||||||
|             .await?; |             .await?; | ||||||
| 
 | 
 | ||||||
|         let vm_node_ban: Option<Self> = query_response.take(0)?; |         let vm_node_ban: Option<Ban> = query_response.take(0).unwrap(); | ||||||
|         let app_node_ban: Option<Self> = query_response.take(1)?; |         let app_node_ban: Option<Ban> = query_response.take(1).unwrap(); | ||||||
|  | 
 | ||||||
|  |         log::trace!("vm_node_ban: {vm_node_ban:?}"); | ||||||
|  |         log::trace!("app_node_ban: {app_node_ban:?}"); | ||||||
| 
 | 
 | ||||||
|         Ok(vm_node_ban.is_some() || app_node_ban.is_some()) |         Ok(vm_node_ban.is_some() || app_node_ban.is_some()) | ||||||
|     } |     } | ||||||
| @ -84,7 +154,7 @@ impl From<&old_brain::BrainData> for Vec<Account> { | |||||||
|         let mut accounts = Vec::new(); |         let mut accounts = Vec::new(); | ||||||
|         for old_account in old_data.accounts.iter() { |         for old_account in old_data.accounts.iter() { | ||||||
|             let mut a = Account { |             let mut a = Account { | ||||||
|                 id: RecordId::from(("account", old_account.key())), |                 id: RecordId::from((ACCOUNT, old_account.key())), | ||||||
|                 balance: old_account.value().balance, |                 balance: old_account.value().balance, | ||||||
|                 tmp_locked: old_account.value().tmp_locked, |                 tmp_locked: old_account.value().tmp_locked, | ||||||
|                 escrow: 0, |                 escrow: 0, | ||||||
| @ -102,12 +172,49 @@ impl From<&old_brain::BrainData> for Vec<Account> { | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize)] | ||||||
| pub struct Ban { | pub struct Ban { | ||||||
|     id: RecordId, |  | ||||||
|     #[serde(rename = "in")] |     #[serde(rename = "in")] | ||||||
|     from_account: RecordId, |     pub from_account: RecordId, | ||||||
|     #[serde(rename = "out")] |     #[serde(rename = "out")] | ||||||
|     to_account: RecordId, |     pub to_account: RecordId, | ||||||
|     created_at: Datetime, |     pub created_at: Datetime, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Ban { | ||||||
|  |     pub async fn get_record( | ||||||
|  |         db: &Surreal<Client>, | ||||||
|  |         op_wallet: &str, | ||||||
|  |         user_wallet: &str, | ||||||
|  |     ) -> Result<Option<Self>, Error> { | ||||||
|  |         let query = | ||||||
|  |             format!("SELECT * FROM {BAN} WHERE in = $operator_input AND out = $user_input;"); | ||||||
|  |         let mut response = db | ||||||
|  |             .query(query) | ||||||
|  |             .bind(("operator_input", RecordId::from((ACCOUNT, op_wallet)))) | ||||||
|  |             .bind(("user_input", RecordId::from((ACCOUNT, user_wallet)))) | ||||||
|  |             .await?; | ||||||
|  |         let ban_record: Option<Self> = response.take(0)?; | ||||||
|  |         Ok(ban_record) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn create( | ||||||
|  |         db: &Surreal<Client>, | ||||||
|  |         op_wallet: &str, | ||||||
|  |         user_wallet: &str, | ||||||
|  |     ) -> Result<(), Error> { | ||||||
|  |         if Self::get_record(db, op_wallet, user_wallet).await?.is_some() { | ||||||
|  |             log::error!("User {user_wallet} is already banned by {op_wallet}"); | ||||||
|  |             return Err(Error::AlreadyBanned(op_wallet.to_string())); | ||||||
|  |         } | ||||||
|  |         let _: Vec<Self> = db | ||||||
|  |             .insert(BAN) | ||||||
|  |             .relation(Self { | ||||||
|  |                 from_account: RecordId::from((ACCOUNT, op_wallet)), | ||||||
|  |                 to_account: RecordId::from((ACCOUNT, user_wallet)), | ||||||
|  |                 created_at: Default::default(), | ||||||
|  |             }) | ||||||
|  |             .await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize)] | ||||||
| @ -120,6 +227,24 @@ pub struct Kick { | |||||||
|     created_at: Datetime, |     created_at: Datetime, | ||||||
|     reason: String, |     reason: String, | ||||||
|     contract: RecordId, |     contract: RecordId, | ||||||
|  |     node: RecordId, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Kick { | ||||||
|  |     pub async fn kicked_in_a_day(db: &Surreal<Client>, account: &str) -> Result<Vec<Self>, Error> { | ||||||
|  |         let mut result = db | ||||||
|  |             .query(format!( | ||||||
|  |                 "select * from {KICK} where out = {ACCOUNT}:{account} and created_at > time::now() - 24h;" | ||||||
|  |             )) | ||||||
|  |             .await?; | ||||||
|  |         let kicks: Vec<Self> = result.take(0)?; | ||||||
|  |         Ok(kicks) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> { | ||||||
|  |         let _: Vec<Self> = db.insert(KICK).relation(self).await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize, Clone)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| @ -158,7 +283,7 @@ impl Report { | |||||||
| 
 | 
 | ||||||
| /// This is the operator obtained from the DB,
 | /// This is the operator obtained from the DB,
 | ||||||
| /// however the relation is defined using OperatorRelation
 | /// however the relation is defined using OperatorRelation
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct Operator { | pub struct Operator { | ||||||
|     pub account: RecordId, |     pub account: RecordId, | ||||||
|     pub app_nodes: u64, |     pub app_nodes: u64, | ||||||
| @ -231,3 +356,107 @@ impl Operator { | |||||||
|         Ok((operator, vm_nodes, app_nodes)) |         Ok((operator, vm_nodes, app_nodes)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub async fn kick_contract( | ||||||
|  |     db: &Surreal<Client>, | ||||||
|  |     operator_wallet: &str, | ||||||
|  |     contract_uuid: &str, | ||||||
|  |     reason: &str, | ||||||
|  | ) -> Result<u64, Error> { | ||||||
|  |     let (contract_id, operator_id, admin_id, app_or_vm) = | ||||||
|  |         if let Some(active_vm) = ActiveVmWithNode::get_by_uuid(db, contract_uuid).await? { | ||||||
|  |             (active_vm.id, active_vm.vm_node.operator, active_vm.admin, "vm") | ||||||
|  |         } else if let Some(active_app) = ActiveAppWithNode::get_by_uuid(db, contract_uuid).await? { | ||||||
|  |             (active_app.id, active_app.app_node.operator, active_app.admin, "app") | ||||||
|  |         } else { | ||||||
|  |             return Err(Error::ContractNotFound); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     if operator_id.key().to_string() != operator_wallet { | ||||||
|  |         return Err(Error::AccessDenied); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     log::debug!("Kicking contract {contract_id} by operator {operator_id} for reason: '{reason}'",); | ||||||
|  | 
 | ||||||
|  |     let transaction_query = format!( | ||||||
|  |         " | ||||||
|  |         BEGIN TRANSACTION; | ||||||
|  |             LET $contract = {contract_id}; | ||||||
|  |             LET $operator_account = {operator_id}; | ||||||
|  |             LET $reason = $reason_str_input; | ||||||
|  |             LET $contract_id = record::id($contract.id); | ||||||
|  |             LET $admin = $contract.in; | ||||||
|  |             LET $node = $contract.out; | ||||||
|  | 
 | ||||||
|  |             LET $active_contract = (select * from $contract)[0]; | ||||||
|  |             LET $deleted_contract = $active_contract.patch([{{ | ||||||
|  |                 'op': 'replace', | ||||||
|  |                 'path': 'id', | ||||||
|  |                 'value': type::record('deleted_{app_or_vm}:' + $contract_id) | ||||||
|  |             }}]); | ||||||
|  |             LET $deleted_contract = (INSERT RELATION INTO deleted_{app_or_vm} ( $deleted_contract ) RETURN AFTER)[0]; | ||||||
|  | 
 | ||||||
|  |             -- calculating refund minutes | ||||||
|  |             LET $one_week_minutes = duration::mins(1w); | ||||||
|  |             LET $uncollected_minutes =  (time::now() - $active_contract.collected_at).mins(); | ||||||
|  |             
 | ||||||
|  |             LET $minutes_to_refund = if $uncollected_minutes > $one_week_minutes {{ | ||||||
|  |                 $one_week_minutes; | ||||||
|  |             }} ELSE {{ | ||||||
|  |                 $uncollected_minutes; | ||||||
|  |             }}; | ||||||
|  | 
 | ||||||
|  |             -- calculating refund amount | ||||||
|  |             LET $prince_per_minute = fn::{app_or_vm}_price_per_minute($active_contract.id); | ||||||
|  |             
 | ||||||
|  |             LET $refund_amount = $prince_per_minute * $minutes_to_refund; | ||||||
|  | 
 | ||||||
|  |             LET $refund =  IF SELECT * FROM {KICK} WHERE out = $admin.id AND created_at > time::now() - 24h {{ | ||||||
|  |                 0 | ||||||
|  |             }} ELSE IF $operator_account.escrow <= $refund_amount {{ | ||||||
|  |                 $operator_account.escrow | ||||||
|  |             }} ELSE {{ | ||||||
|  |                 $refund_amount; | ||||||
|  |             }}; | ||||||
|  | 
 | ||||||
|  |             RELATE $operator_account->{KICK}->$admin | ||||||
|  |             SET id = $contract_id, | ||||||
|  |                 reason = $reason, | ||||||
|  |                 contract = $deleted_contract.id, | ||||||
|  |                 node = $node.id, | ||||||
|  |                 created_at = time::now() | ||||||
|  |             ; | ||||||
|  |             DELETE $active_contract.id; | ||||||
|  | 
 | ||||||
|  |             -- update balances | ||||||
|  |             UPDATE $operator_account SET escrow -= $refund; | ||||||
|  |             IF $operator_account.escrow < 0 {{ | ||||||
|  |                 THROW 'Insufficient funds.' | ||||||
|  |             }}; | ||||||
|  |             UPDATE $admin SET balance += $refund; | ||||||
|  | 
 | ||||||
|  |             $refund; | ||||||
|  | 
 | ||||||
|  |         COMMIT TRANSACTION; | ||||||
|  |         ",
 | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     log::trace!("kick_contract transaction_query: {}", &transaction_query); | ||||||
|  | 
 | ||||||
|  |     let mut query_res = | ||||||
|  |         db.query(transaction_query).bind(("reason_str_input", reason.to_string())).await?; | ||||||
|  | 
 | ||||||
|  |     log::trace!("transaction_query response: {:?}", &query_res); | ||||||
|  | 
 | ||||||
|  |     let query_error = query_res.take_errors(); | ||||||
|  |     if !query_error.is_empty() { | ||||||
|  |         log::error!("kick_contract query error: {query_error:?}"); | ||||||
|  |         return Err(Error::FailedKickContract(contract_id.to_string())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let refunded: Option<u64> = query_res.take(20)?; | ||||||
|  |     let refunded_amount = refunded.ok_or(Error::FailedToCreateDBEntry("Refund".to_string()))?; | ||||||
|  |     log::info!("Refunded: {refunded_amount} to {admin_id}"); | ||||||
|  | 
 | ||||||
|  |     Ok(refunded_amount) | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,7 +2,10 @@ pub mod app; | |||||||
| pub mod general; | pub mod general; | ||||||
| pub mod vm; | pub mod vm; | ||||||
| 
 | 
 | ||||||
| use crate::constants::{APP_NODE, DELETED_APP, DELETED_VM, NEW_APP_REQ, NEW_VM_REQ, UPDATE_VM_REQ}; | use crate::constants::{ | ||||||
|  |     APP_NODE, DB_SCHEMA_FILES, DELETED_APP, DELETED_VM, MIN_ESCROW, NEW_APP_REQ, NEW_VM_REQ, | ||||||
|  |     UPDATE_VM_REQ, | ||||||
|  | }; | ||||||
| use crate::old_brain; | use crate::old_brain; | ||||||
| use prelude::*; | use prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| @ -22,16 +25,30 @@ pub enum Error { | |||||||
|     StdIo(#[from] std::io::Error), |     StdIo(#[from] std::io::Error), | ||||||
|     #[error(transparent)] |     #[error(transparent)] | ||||||
|     TimeOut(#[from] tokio::time::error::Elapsed), |     TimeOut(#[from] tokio::time::error::Elapsed), | ||||||
|     #[error("Failed to create account")] |     #[error("Failed to create {0}")] | ||||||
|     FailedToCreateDBEntry, |     FailedToCreateDBEntry(String), | ||||||
|     #[error("Unknown Table: {0}")] |     #[error("Unknown Table: {0}")] | ||||||
|     UnknownTable(String), |     UnknownTable(String), | ||||||
|     #[error("Daemon channel got closed: {0}")] |     #[error("Daemon channel got closed: {0}")] | ||||||
|     AppDaemonConnection(#[from] tokio::sync::mpsc::error::SendError<AppDaemonMsg>), |     AppDaemonConnection(#[from] tokio::sync::mpsc::error::SendError<AppDaemonMsg>), | ||||||
|     #[error("AppDaemon Error {0}")] |     #[error("AppDaemon Error {0}")] | ||||||
|     NewAppDaemonResp(String), |     NewAppDaemonResp(String), | ||||||
|  |     #[error("Minimum escrow amount is {MIN_ESCROW}")] | ||||||
|  |     MinimalEscrow, | ||||||
|     #[error("Insufficient funds, deposit more tokens")] |     #[error("Insufficient funds, deposit more tokens")] | ||||||
|     InsufficientFunds, |     InsufficientFunds, | ||||||
|  |     #[error("Contract not found")] | ||||||
|  |     ContractNotFound, | ||||||
|  |     #[error("Access denied")] | ||||||
|  |     AccessDenied, | ||||||
|  |     #[error("Failed to delete contract {0}")] | ||||||
|  |     FailedToDeleteContract(String), | ||||||
|  |     #[error("Failed to kick contract {0}")] | ||||||
|  |     FailedKickContract(String), | ||||||
|  |     #[error("Already banned {0}")] | ||||||
|  |     AlreadyBanned(String), | ||||||
|  |     #[error("Failed to slash operator {0}")] | ||||||
|  |     FailedToSlashOperator(String), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub mod prelude { | pub mod prelude { | ||||||
| @ -63,10 +80,15 @@ pub async fn migration0( | |||||||
|     let accounts: Vec<Account> = old_data.into(); |     let accounts: Vec<Account> = old_data.into(); | ||||||
|     let vm_nodes: Vec<VmNode> = old_data.into(); |     let vm_nodes: Vec<VmNode> = old_data.into(); | ||||||
|     let app_nodes: Vec<AppNode> = old_data.into(); |     let app_nodes: Vec<AppNode> = old_data.into(); | ||||||
|     let vm_contracts: Vec<ActiveVm> = old_data.into(); |     let active_vm: Vec<ActiveVm> = old_data.into(); | ||||||
|  |     let active_app: Vec<ActiveApp> = old_data.into(); | ||||||
| 
 | 
 | ||||||
|     let schema = std::fs::read_to_string(crate::constants::DB_SCHEMA_FILE)?; |     for schema_data in DB_SCHEMA_FILES.map(|path| (std::fs::read_to_string(path), path)) { | ||||||
|  |         let schema_file = schema_data.1; | ||||||
|  |         println!("Loading schema from {schema_file}"); | ||||||
|  |         let schema = schema_data.0?; | ||||||
|         db.query(schema).await?; |         db.query(schema).await?; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     println!("Inserting accounts..."); |     println!("Inserting accounts..."); | ||||||
|     let _: Vec<Account> = db.insert(()).content(accounts).await?; |     let _: Vec<Account> = db.insert(()).content(accounts).await?; | ||||||
| @ -74,8 +96,10 @@ pub async fn migration0( | |||||||
|     let _: Vec<VmNode> = db.insert(()).content(vm_nodes).await?; |     let _: Vec<VmNode> = db.insert(()).content(vm_nodes).await?; | ||||||
|     println!("Inserting app nodes..."); |     println!("Inserting app nodes..."); | ||||||
|     let _: Vec<AppNode> = db.insert(()).content(app_nodes).await?; |     let _: Vec<AppNode> = db.insert(()).content(app_nodes).await?; | ||||||
|     println!("Inserting vm contracts..."); |     println!("Inserting active vm contracts..."); | ||||||
|     let _: Vec<ActiveVm> = db.insert("vm_contract").relation(vm_contracts).await?; |     let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?; | ||||||
|  |     println!("Inserting app contracts..."); | ||||||
|  |     let _: Vec<ActiveApp> = db.insert(()).relation(active_app).await?; | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								src/db/vm.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										48
									
								
								src/db/vm.rs
									
									
									
									
									
								
							| @ -219,7 +219,7 @@ impl NewVmReq { | |||||||
|                 ->vm_node:{vm_node} |                 ->vm_node:{vm_node} | ||||||
|             CONTENT {{ |             CONTENT {{ | ||||||
|                 created_at: time::now(), hostname: '{}', vcpus: {}, memory_mb: {}, disk_size_gb: {}, |                 created_at: time::now(), hostname: '{}', vcpus: {}, memory_mb: {}, disk_size_gb: {}, | ||||||
|                 extra_ports: {}, public_ipv4: {:?}, public_ipv6: {:?}, |                 extra_ports: {:?}, public_ipv4: {:?}, public_ipv6: {:?}, | ||||||
|                 dtrfs_url: '{}', dtrfs_sha: '{}', kernel_url: '{}', kernel_sha: '{}', |                 dtrfs_url: '{}', dtrfs_sha: '{}', kernel_url: '{}', kernel_sha: '{}', | ||||||
|                 price_per_unit: {}, locked_nano: {locked_nano}, error: '' |                 price_per_unit: {}, locked_nano: {locked_nano}, error: '' | ||||||
|             }}; |             }}; | ||||||
| @ -229,7 +229,7 @@ impl NewVmReq { | |||||||
|             self.vcpus, |             self.vcpus, | ||||||
|             self.memory_mb, |             self.memory_mb, | ||||||
|             self.disk_size_gb, |             self.disk_size_gb, | ||||||
|             format!("{:?}", self.extra_ports,), |             self.extra_ports, | ||||||
|             self.public_ipv4, |             self.public_ipv4, | ||||||
|             self.public_ipv6, |             self.public_ipv6, | ||||||
|             self.dtrfs_url, |             self.dtrfs_url, | ||||||
| @ -242,14 +242,12 @@ impl NewVmReq { | |||||||
|         let mut query_resp = db.query(query).await?; |         let mut query_resp = db.query(query).await?; | ||||||
|         let resp_err = query_resp.take_errors(); |         let resp_err = query_resp.take_errors(); | ||||||
| 
 | 
 | ||||||
|         if let Some(insufficient_funds_error) = resp_err.get(&1) { |         if let Some(surrealdb::Error::Api(surrealdb::error::Api::Query(tx_query_error))) = | ||||||
|             if let surrealdb::Error::Api(surrealdb::error::Api::Query(tx_query_error)) = |             resp_err.get(&1) | ||||||
|                 insufficient_funds_error |  | ||||||
|         { |         { | ||||||
|                 log::error!("Transaction error: {}", tx_query_error); |             log::error!("Transaction error: {tx_query_error}"); | ||||||
|             return Err(Error::InsufficientFunds); |             return Err(Error::InsufficientFunds); | ||||||
|         } |         } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| @ -728,10 +726,36 @@ pub struct ActiveVmWithNode { | |||||||
|     pub collected_at: Datetime, |     pub collected_at: Datetime, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<ActiveVmWithNode> for ActiveVm { | ||||||
|  |     fn from(val: ActiveVmWithNode) -> Self { | ||||||
|  |         Self { | ||||||
|  |             id: val.id, | ||||||
|  |             admin: val.admin, | ||||||
|  |             vm_node: val.vm_node.id, | ||||||
|  |             hostname: val.hostname, | ||||||
|  |             mapped_ports: val.mapped_ports, | ||||||
|  |             public_ipv4: val.public_ipv4, | ||||||
|  |             public_ipv6: val.public_ipv6, | ||||||
|  |             disk_size_gb: val.disk_size_gb, | ||||||
|  |             vcpus: val.vcpus, | ||||||
|  |             memory_mb: val.memory_mb, | ||||||
|  |             dtrfs_sha: val.dtrfs_sha, | ||||||
|  |             kernel_sha: val.kernel_sha, | ||||||
|  |             created_at: val.created_at, | ||||||
|  |             price_per_unit: val.price_per_unit, | ||||||
|  |             locked_nano: val.locked_nano, | ||||||
|  |             collected_at: val.collected_at, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ActiveVmWithNode { | impl ActiveVmWithNode { | ||||||
|     pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> { |     pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> { | ||||||
|         let contract: Option<Self> = |         let contract: Option<Self> = db | ||||||
|             db.query(format!("select * from {ACTIVE_VM}:{uuid} fetch out;")).await?.take(0)?; |             .query(format!("select * from {ACTIVE_VM} where id = $uuid_input fetch out;")) | ||||||
|  |             .bind(("uuid_input", RecordId::from((ACTIVE_VM, uuid)))) | ||||||
|  |             .await? | ||||||
|  |             .take(0)?; | ||||||
|         Ok(contract) |         Ok(contract) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -790,6 +814,12 @@ impl ActiveVmWithNode { | |||||||
|     pub fn price_per_minute(&self) -> u64 { |     pub fn price_per_minute(&self) -> u64 { | ||||||
|         self.total_units() * self.price_per_unit |         self.total_units() * self.price_per_unit | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn list_all(db: &Surreal<Client>) -> Result<Vec<Self>, Error> { | ||||||
|  |         let mut query_response = db.query(format!("SELECT * FROM {ACTIVE_VM} FETCH out;")).await?; | ||||||
|  |         let active_vms: Vec<Self> = query_response.take(0)?; | ||||||
|  |         Ok(active_vms) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: delete all of these From implementation after migration 0 gets executed
 | // TODO: delete all of these From implementation after migration 0 gets executed
 | ||||||
|  | |||||||
| @ -115,7 +115,7 @@ impl BrainAppDaemon for AppDaemonServer { | |||||||
|         let mut req_stream = req.into_inner(); |         let mut req_stream = req.into_inner(); | ||||||
|         let pubkey: String; |         let pubkey: String; | ||||||
|         if let Some(Ok(msg)) = req_stream.next().await { |         if let Some(Ok(msg)) = req_stream.next().await { | ||||||
|             log::debug!("App daemon_messages received auth message: {:?}", msg); |             log::debug!("App daemon_messages received auth message: {msg:?}"); | ||||||
|             if let Some(daemon_message_app::Msg::Auth(auth)) = msg.msg { |             if let Some(daemon_message_app::Msg::Auth(auth)) = msg.msg { | ||||||
|                 pubkey = auth.pubkey.clone(); |                 pubkey = auth.pubkey.clone(); | ||||||
|                 check_sig_from_parts( |                 check_sig_from_parts( | ||||||
|  | |||||||
| @ -107,31 +107,56 @@ impl BrainGeneralCli for GeneralCliServer { | |||||||
| 
 | 
 | ||||||
|     async fn register_operator( |     async fn register_operator( | ||||||
|         &self, |         &self, | ||||||
|         _req: Request<RegOperatorReq>, |         req: Request<RegOperatorReq>, | ||||||
|     ) -> Result<Response<Empty>, Status> { |     ) -> Result<Response<Empty>, Status> { | ||||||
|         todo!(); |         let req = check_sig_from_req(req)?; | ||||||
|         // let req = check_sig_from_req(req)?;
 |         log::info!("Regitering new operator: {req:?}"); | ||||||
|         // info!("Regitering new operator: {req:?}");
 |         match db::Account::operator_reg(&self.db, &req.pubkey, &req.email, req.escrow).await { | ||||||
|         // match self.data.register_operator(req) {
 |             Ok(()) => Ok(Response::new(Empty {})), | ||||||
|         //     Ok(()) => Ok(Response::new(Empty {})),
 |             Err(e) if matches!(e, db::Error::InsufficientFunds | db::Error::MinimalEscrow) => { | ||||||
|         //     Err(e) => Err(Status::failed_precondition(e.to_string())),
 |                 Err(Status::failed_precondition(e.to_string())) | ||||||
|         // }
 |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 log::info!("Failed to register operator: {e:?}"); | ||||||
|  |                 Err(Status::unknown( | ||||||
|  |                     "Unknown error. Please try again or contact the DeTEE devs team.", | ||||||
|  |                 )) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn kick_contract(&self, _req: Request<KickReq>) -> Result<Response<KickResp>, Status> { |     async fn kick_contract(&self, req: Request<KickReq>) -> Result<Response<KickResp>, Status> { | ||||||
|         todo!(); |         let req = check_sig_from_req(req)?; | ||||||
|         // let req = check_sig_from_req(req)?;
 |         log::info!("Kicking contract: {}, by: {}", req.contract_uuid, req.operator_wallet); | ||||||
|         // match self.data.kick_contract(&req.operator_wallet, &req.contract_uuid, &req.reason).await {
 |         match db::kick_contract(&self.db, &req.operator_wallet, &req.contract_uuid, &req.reason) | ||||||
|         //     Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })),
 |             .await | ||||||
|         //     Err(e) => Err(Status::permission_denied(e.to_string())),
 |         { | ||||||
|         // }
 |             Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })), | ||||||
|  |             Err(e) | ||||||
|  |                 if matches!( | ||||||
|  |                     e, | ||||||
|  |                     db::Error::ContractNotFound | ||||||
|  |                         | db::Error::AccessDenied | ||||||
|  |                         | db::Error::FailedToDeleteContract(_) | ||||||
|  |                 ) => | ||||||
|  |             { | ||||||
|  |                 log::warn!("Failed to kick contract: {e:?}"); | ||||||
|  |                 Err(Status::failed_precondition(e.to_string())) | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 log::error!("Failed to kick contract: {e:?}"); | ||||||
|  |                 Err(Status::unknown( | ||||||
|  |                     "Unknown error. Please try again or contact the DeTEE devs team.", | ||||||
|  |                 )) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn ban_user(&self, _req: Request<BanUserReq>) -> Result<Response<Empty>, Status> { |     async fn ban_user(&self, req: Request<BanUserReq>) -> Result<Response<Empty>, Status> { | ||||||
|         todo!(); |         let req = check_sig_from_req(req)?; | ||||||
|         // let req = check_sig_from_req(req)?;
 |         log::info!("Banning user: {}, by: {}", req.user_wallet, req.operator_wallet); | ||||||
|         // self.data.ban_user(&req.operator_wallet, &req.user_wallet);
 |         db::Ban::create(&self.db, &req.operator_wallet, &req.user_wallet).await?; | ||||||
|         // Ok(Response::new(Empty {}))
 |         Ok(Response::new(Empty {})) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // admin commands
 |     // admin commands
 | ||||||
| @ -139,69 +164,66 @@ impl BrainGeneralCli for GeneralCliServer { | |||||||
|     async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> { |     async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> { | ||||||
|         check_admin_key(&req)?; |         check_admin_key(&req)?; | ||||||
|         let req = check_sig_from_req(req)?; |         let req = check_sig_from_req(req)?; | ||||||
|  |         log::info!("Airdropping {} tokens to {}", req.tokens, req.pubkey); | ||||||
|         db::Account::airdrop(&self.db, &req.pubkey, req.tokens).await?; |         db::Account::airdrop(&self.db, &req.pubkey, req.tokens).await?; | ||||||
|         Ok(Response::new(Empty {})) |         Ok(Response::new(Empty {})) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn slash(&self, _req: Request<SlashReq>) -> Result<Response<Empty>, Status> { |     async fn slash(&self, req: Request<SlashReq>) -> Result<Response<Empty>, Status> { | ||||||
|         todo!(); |         check_admin_key(&req)?; | ||||||
|         // check_admin_key(&req)?;
 |         let req = check_sig_from_req(req)?; | ||||||
|         // let req = check_sig_from_req(req)?;
 |         db::Account::slash_account(&self.db, &req.pubkey, req.tokens).await?; | ||||||
|         // self.data.slash_account(&req.pubkey, req.tokens);
 |         Ok(Response::new(Empty {})) | ||||||
|         // Ok(Response::new(Empty {}))
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn list_accounts( |     async fn list_accounts( | ||||||
|         &self, |         &self, | ||||||
|         _req: Request<Empty>, |         req: Request<Empty>, | ||||||
|     ) -> Result<Response<Self::ListAccountsStream>, Status> { |     ) -> Result<Response<Self::ListAccountsStream>, Status> { | ||||||
|         todo!(); |         check_admin_key(&req)?; | ||||||
|         // check_admin_key(&req)?;
 |         check_sig_from_req(req)?; | ||||||
|         // let _ = check_sig_from_req(req)?;
 |         let accounts = db::Account::list_accounts(&self.db).await?; | ||||||
|         // let accounts = self.data.list_accounts();
 |         let (tx, rx) = mpsc::channel(6); | ||||||
|         // let (tx, rx) = mpsc::channel(6);
 |         tokio::spawn(async move { | ||||||
|         // tokio::spawn(async move {
 |             for account in accounts { | ||||||
|         //     for account in accounts {
 |                 let _ = tx.send(Ok(account.into())).await; | ||||||
|         //         let _ = tx.send(Ok(account.into())).await;
 |             } | ||||||
|         //     }
 |         }); | ||||||
|         // });
 |         let output_stream = ReceiverStream::new(rx); | ||||||
|         // let output_stream = ReceiverStream::new(rx);
 |         Ok(Response::new(Box::pin(output_stream) as Self::ListAccountsStream)) | ||||||
|         // Ok(Response::new(Box::pin(output_stream) as Self::ListAccountsStream))
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn list_all_vm_contracts( |     async fn list_all_vm_contracts( | ||||||
|         &self, |         &self, | ||||||
|         _req: Request<Empty>, |         req: Request<Empty>, | ||||||
|     ) -> Result<Response<Self::ListAllVmContractsStream>, Status> { |     ) -> Result<Response<Self::ListAllVmContractsStream>, Status> { | ||||||
|         todo!(); |         check_admin_key(&req)?; | ||||||
|         // check_admin_key(&req)?;
 |         check_sig_from_req(req)?; | ||||||
|         // let _ = check_sig_from_req(req)?;
 |         let contracts = db::ActiveVmWithNode::list_all(&self.db).await?; | ||||||
|         // let contracts = self.data.list_all_contracts();
 |         let (tx, rx) = mpsc::channel(6); | ||||||
|         // let (tx, rx) = mpsc::channel(6);
 |         tokio::spawn(async move { | ||||||
|         // tokio::spawn(async move {
 |             for contract in contracts { | ||||||
|         //     for contract in contracts {
 |                 let _ = tx.send(Ok(contract.into())).await; | ||||||
|         //         let _ = tx.send(Ok(contract.into())).await;
 |             } | ||||||
|         //     }
 |         }); | ||||||
|         // });
 |         let output_stream = ReceiverStream::new(rx); | ||||||
|         // let output_stream = ReceiverStream::new(rx);
 |         Ok(Response::new(Box::pin(output_stream) as Self::ListAllVmContractsStream)) | ||||||
|         // Ok(Response::new(Box::pin(output_stream) as Self::ListAllVmContractsStream))
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn list_all_app_contracts( |     async fn list_all_app_contracts( | ||||||
|         &self, |         &self, | ||||||
|         _req: tonic::Request<Empty>, |         req: tonic::Request<Empty>, | ||||||
|     ) -> Result<tonic::Response<Self::ListAllAppContractsStream>, Status> { |     ) -> Result<tonic::Response<Self::ListAllAppContractsStream>, Status> { | ||||||
|         todo!(); |         check_admin_key(&req)?; | ||||||
|         // check_admin_key(&req)?;
 |         check_sig_from_req(req)?; | ||||||
|         // let _ = check_sig_from_req(req)?;
 |         let contracts = db::ActiveAppWithNode::list_all(&self.db).await?; | ||||||
|         // let contracts = self.data.list_all_app_contracts();
 |         let (tx, rx) = mpsc::channel(6); | ||||||
|         // let (tx, rx) = mpsc::channel(6);
 |         tokio::spawn(async move { | ||||||
|         // tokio::spawn(async move {
 |             for contract in contracts { | ||||||
|         //     for contract in contracts {
 |                 let _ = tx.send(Ok(contract.into())).await; | ||||||
|         //         let _ = tx.send(Ok(contract.into())).await;
 |             } | ||||||
|         //     }
 |         }); | ||||||
|         // });
 |         let output_stream = ReceiverStream::new(rx); | ||||||
|         // let output_stream = ReceiverStream::new(rx);
 |         Ok(Response::new(Box::pin(output_stream))) | ||||||
|         // Ok(Response::new(Box::pin(output_stream)))
 |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -58,7 +58,7 @@ impl_pubkey_getter!(RegisterAppNodeReq); | |||||||
| impl_pubkey_getter!(AppNodeFilters); | impl_pubkey_getter!(AppNodeFilters); | ||||||
| 
 | 
 | ||||||
| pub fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> { | pub fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> { | ||||||
|     log::trace!("Checking signature from request: {:?}", req); |     log::trace!("Checking signature from request: {req:?}"); | ||||||
|     let time = match req.metadata().get("timestamp") { |     let time = match req.metadata().get("timestamp") { | ||||||
|         Some(t) => t.clone(), |         Some(t) => t.clone(), | ||||||
|         None => return Err(Status::unauthenticated("Timestamp not found in metadata.")), |         None => return Err(Status::unauthenticated("Timestamp not found in metadata.")), | ||||||
| @ -73,8 +73,7 @@ pub fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> | |||||||
|     let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds(); |     let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds(); | ||||||
|     if !(-4..=4).contains(&seconds_elapsed) { |     if !(-4..=4).contains(&seconds_elapsed) { | ||||||
|         return Err(Status::unauthenticated(format!( |         return Err(Status::unauthenticated(format!( | ||||||
|             "Date is not within 4 sec of the time of the server: CLI {} vs Server {}", |             "Date is not within 4 sec of the time of the server: CLI {parsed_time} vs Server {now}", | ||||||
|             parsed_time, now |  | ||||||
|         ))); |         ))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -131,8 +130,7 @@ pub fn check_sig_from_parts(pubkey: &str, time: &str, msg: &str, sig: &str) -> R | |||||||
|     let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds(); |     let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds(); | ||||||
|     if !(-4..=4).contains(&seconds_elapsed) { |     if !(-4..=4).contains(&seconds_elapsed) { | ||||||
|         return Err(Status::unauthenticated(format!( |         return Err(Status::unauthenticated(format!( | ||||||
|             "Date is not within 4 sec of the time of the server: CLI {} vs Server {}", |             "Date is not within 4 sec of the time of the server: CLI {parsed_time} vs Server {now}", | ||||||
|             parsed_time, now |  | ||||||
|         ))); |         ))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ use crate::constants::{ACCOUNT, APP_NODE, ID_ALPHABET, NEW_APP_REQ, NEW_VM_REQ, | |||||||
| use crate::db::prelude as db; | use crate::db::prelude as db; | ||||||
| use detee_shared::app_proto::AppNodeListResp; | use detee_shared::app_proto::AppNodeListResp; | ||||||
| use detee_shared::common_proto::MappedPort; | use detee_shared::common_proto::MappedPort; | ||||||
| use detee_shared::general_proto::{AccountBalance, ListOperatorsResp}; | use detee_shared::general_proto::{Account, AccountBalance, ListOperatorsResp}; | ||||||
| use detee_shared::{app_proto::*, vm_proto::*}; | use detee_shared::{app_proto::*, vm_proto::*}; | ||||||
| use nanoid::nanoid; | use nanoid::nanoid; | ||||||
| 
 | 
 | ||||||
| @ -14,6 +14,16 @@ impl From<db::Account> for AccountBalance { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<db::Account> for Account { | ||||||
|  |     fn from(account: db::Account) -> Self { | ||||||
|  |         Account { | ||||||
|  |             pubkey: account.id.to_string(), | ||||||
|  |             balance: account.balance, | ||||||
|  |             tmp_locked: account.tmp_locked, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl From<NewVmReq> for db::NewVmReq { | impl From<NewVmReq> for db::NewVmReq { | ||||||
|     fn from(new_vm_req: NewVmReq) -> Self { |     fn from(new_vm_req: NewVmReq) -> Self { | ||||||
|         Self { |         Self { | ||||||
| @ -263,15 +273,15 @@ impl From<db::ActiveAppWithNode> for AppContract { | |||||||
|             node_pubkey: value.app_node.id.key().to_string(), |             node_pubkey: value.app_node.id.key().to_string(), | ||||||
|             public_ipv4: value.host_ipv4, |             public_ipv4: value.host_ipv4, | ||||||
|             resource: Some(AppResource { |             resource: Some(AppResource { | ||||||
|                 memory_mb: value.memory_mb as u32, |                 memory_mb: value.memory_mb, | ||||||
|                 disk_mb: value.disk_size_gb as u32, |                 disk_mb: value.disk_size_gb, | ||||||
|                 vcpu: value.vcpus as u32, |                 vcpu: value.vcpus, | ||||||
|                 ports: value.mapped_ports.iter().map(|(_, g)| *g as u32).collect(), |                 ports: value.mapped_ports.iter().map(|(_, g)| *g).collect(), | ||||||
|             }), |             }), | ||||||
|             mapped_ports: value |             mapped_ports: value | ||||||
|                 .mapped_ports |                 .mapped_ports | ||||||
|                 .iter() |                 .iter() | ||||||
|                 .map(|(h, g)| MappedPort { host_port: *h as u32, guest_port: *g as u32 }) |                 .map(|(h, g)| MappedPort { host_port: *h, guest_port: *g }) | ||||||
|                 .collect(), |                 .collect(), | ||||||
| 
 | 
 | ||||||
|             created_at: value.created_at.to_rfc3339(), |             created_at: value.created_at.to_rfc3339(), | ||||||
| @ -294,7 +304,7 @@ impl From<NewAppReq> for db::NewAppReq { | |||||||
|             .public_package_mr_enclave |             .public_package_mr_enclave | ||||||
|             .unwrap_or_default() |             .unwrap_or_default() | ||||||
|             .iter() |             .iter() | ||||||
|             .fold(String::new(), |acc, x| acc + &format!("{:02x?}", x)); |             .fold(String::new(), |acc, x| acc + &format!("{x:02x?}")); | ||||||
| 
 | 
 | ||||||
|         Self { |         Self { | ||||||
|             id: RecordId::from((NEW_APP_REQ, nanoid!(40, &ID_ALPHABET))), |             id: RecordId::from((NEW_APP_REQ, nanoid!(40, &ID_ALPHABET))), | ||||||
| @ -377,7 +387,7 @@ impl From<db::ActiveApp> for NewAppRes { | |||||||
|         let mapped_ports = val |         let mapped_ports = val | ||||||
|             .mapped_ports |             .mapped_ports | ||||||
|             .iter() |             .iter() | ||||||
|             .map(|(h, g)| MappedPort { host_port: *h as u32, guest_port: *g as u32 }) |             .map(|(h, g)| MappedPort { host_port: *h, guest_port: *g }) | ||||||
|             .collect(); |             .collect(); | ||||||
|         Self { |         Self { | ||||||
|             uuid: val.id.key().to_string(), |             uuid: val.id.key().to_string(), | ||||||
|  | |||||||
| @ -25,3 +25,13 @@ DEFINE FUNCTION OVERWRITE fn::delete_vm( | |||||||
|     DELETE $vm.id; |     DELETE $vm.id; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | DEFINE FUNCTION OVERWRITE fn::app_price_per_minute( | ||||||
|  | 	$app_id: record | ||||||
|  | ) { | ||||||
|  |     LET $app = (select * from $app_id)[0]; | ||||||
|  |     RETURN  | ||||||
|  |      (($app.vcpus * 5) +  | ||||||
|  |         ($app.memory_mb / 200) +  | ||||||
|  |         ($app.disk_size_gb / 10)) | ||||||
|  |     * $app.price_per_unit; | ||||||
|  | }; | ||||||
| @ -129,21 +129,20 @@ DEFINE FIELD vcpus ON TABLE deleted_app TYPE int; | |||||||
| DEFINE FIELD memory_mb 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 disk_size_gb ON TABLE deleted_app TYPE int; | ||||||
| DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime; | DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime; | ||||||
| DEFINE FIELD deleted_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; | DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int; | ||||||
| DEFINE FIELD locked_nano ON TABLE deleted_app TYPE int; |  | ||||||
| DEFINE FIELD collected_at ON TABLE deleted_app TYPE datetime; |  | ||||||
| DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string; | DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string; | ||||||
| DEFINE FIELD package_url ON TABLE deleted_app TYPE string; | DEFINE FIELD package_url ON TABLE deleted_app TYPE string; | ||||||
| DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string; | DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string; | ||||||
| 
 | 
 | ||||||
| DEFINE TABLE ban TYPE RELATION FROM account TO account; | DEFINE TABLE ban TYPE RELATION FROM account TO account; | ||||||
| DEFINE FIELD created_at ON TABLE ban TYPE datetime; | DEFINE FIELD created_at ON TABLE ban TYPE datetime DEFAULT time::now();;  | ||||||
| 
 | 
 | ||||||
| DEFINE TABLE kick TYPE RELATION FROM account TO account; | DEFINE TABLE kick TYPE RELATION FROM account TO account; | ||||||
| DEFINE FIELD created_at ON TABLE kick TYPE datetime; | DEFINE FIELD created_at ON TABLE kick TYPE datetime; | ||||||
| DEFINE FIELD reason ON TABLE kick TYPE string; | DEFINE FIELD reason ON TABLE kick TYPE string; | ||||||
| DEFINE FIELD contract ON TABLE kick TYPE record<deleted_vm|deleted_app>; | DEFINE FIELD contract ON TABLE kick TYPE record<deleted_vm|deleted_app>; | ||||||
|  | DEFINE FIELD node ON TABLE kick TYPE record<vm_node|app_node>; | ||||||
| 
 | 
 | ||||||
| DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node; | DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node; | ||||||
| DEFINE FIELD created_at ON TABLE report TYPE datetime; | DEFINE FIELD created_at ON TABLE report TYPE datetime; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use anyhow::{anyhow, Result}; | use anyhow::Result; | ||||||
| use detee_shared::general_proto::brain_general_cli_server::BrainGeneralCliServer; | use detee_shared::general_proto::brain_general_cli_server::BrainGeneralCliServer; | ||||||
| use detee_shared::vm_proto::brain_vm_cli_server::BrainVmCliServer; | use detee_shared::vm_proto::brain_vm_cli_server::BrainVmCliServer; | ||||||
| use detee_shared::vm_proto::brain_vm_daemon_server::BrainVmDaemonServer; | use detee_shared::vm_proto::brain_vm_daemon_server::BrainVmDaemonServer; | ||||||
| @ -6,6 +6,7 @@ use dotenv::dotenv; | |||||||
| use hyper_util::rt::TokioIo; | use hyper_util::rt::TokioIo; | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  | use surreal_brain::constants::DB_SCHEMA_FILES; | ||||||
| use surreal_brain::grpc::general::GeneralCliServer; | use surreal_brain::grpc::general::GeneralCliServer; | ||||||
| use surreal_brain::grpc::vm::{VmCliServer, VmDaemonServer}; | use surreal_brain::grpc::vm::{VmCliServer, VmDaemonServer}; | ||||||
| use surrealdb::engine::remote::ws::Client; | use surrealdb::engine::remote::ws::Client; | ||||||
| @ -30,12 +31,15 @@ pub async fn prepare_test_db() -> Result<Surreal<Client>> { | |||||||
|     let db = surreal_brain::db::db_connection(&db_url, &db_user, &db_pass, db_ns, db_name).await?; |     let db = surreal_brain::db::db_connection(&db_url, &db_user, &db_pass, db_ns, db_name).await?; | ||||||
|     DB_STATE |     DB_STATE | ||||||
|         .get_or_init(|| async { |         .get_or_init(|| async { | ||||||
|             let old_brain_data = surreal_brain::old_brain::BrainData::load_from_disk() |             let raw_mock_data = std::fs::read_to_string("tests/mock_data.yaml")?; | ||||||
|                 .map_err(|e| anyhow!(e.to_string()))?; |             let mock_data: surreal_brain::old_brain::BrainData = | ||||||
|  |                 serde_yaml::from_str(&raw_mock_data)?; | ||||||
| 
 | 
 | ||||||
|             db.query(format!("REMOVE DATABASE {db_name}")).await?; |             db.query(format!("REMOVE DATABASE {db_name}")).await?; | ||||||
|             db.query(std::fs::read_to_string("interim_tables.surql")?).await?; |             for schema in DB_SCHEMA_FILES.map(std::fs::read_to_string) { | ||||||
|             surreal_brain::db::migration0(&db, &old_brain_data).await?; |                 db.query(schema?).await?; | ||||||
|  |             } | ||||||
|  |             surreal_brain::db::migration0(&db, &mock_data).await?; | ||||||
|             Ok::<(), anyhow::Error>(()) |             Ok::<(), anyhow::Error>(()) | ||||||
|         }) |         }) | ||||||
|         .await; |         .await; | ||||||
|  | |||||||
| @ -1,9 +1,24 @@ | |||||||
| use anyhow::Result; | use anyhow::Result; | ||||||
| use detee_shared::vm_proto as snp_proto; | use detee_shared::vm_proto as snp_proto; | ||||||
| use ed25519_dalek::{Signer, SigningKey}; | use ed25519_dalek::{Signer, SigningKey}; | ||||||
|  | use itertools::Itertools; | ||||||
|  | use std::sync::OnceLock; | ||||||
| use tonic::metadata::AsciiMetadataValue; | use tonic::metadata::AsciiMetadataValue; | ||||||
| use tonic::Request; | use tonic::Request; | ||||||
| 
 | 
 | ||||||
|  | pub static ADMIN_KEYS: OnceLock<Vec<Key>> = OnceLock::new(); | ||||||
|  | 
 | ||||||
|  | pub fn admin_keys() -> Vec<Key> { | ||||||
|  |     let admin_keys = ADMIN_KEYS.get_or_init(|| { | ||||||
|  |         let admin_keys = vec![Key::new(), Key::new(), Key::new()]; | ||||||
|  |         let admin_pub_keys = admin_keys.iter().map(|k| k.pubkey.clone()).join(", "); | ||||||
|  |         std::env::set_var("ADMIN_PUB_KEYS", admin_pub_keys); | ||||||
|  |         admin_keys.clone() | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     admin_keys.clone() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub struct Key { | pub struct Key { | ||||||
|     pub sg_key: SigningKey, |     pub sg_key: SigningKey, | ||||||
| @ -17,6 +32,17 @@ impl Key { | |||||||
|         Key { sg_key: sk, pubkey } |         Key { sg_key: sk, pubkey } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn from(private_key: &str) -> Self { | ||||||
|  |         let signing_key: SigningKey = bs58::decode(private_key) | ||||||
|  |             .into_vec() | ||||||
|  |             .expect("Failed to decode private key") | ||||||
|  |             .as_slice() | ||||||
|  |             .try_into() | ||||||
|  |             .expect("Failed to convert to SigningKey"); | ||||||
|  |         let pubkey = bs58::encode(signing_key.verifying_key().to_bytes()).into_string(); | ||||||
|  |         Key { sg_key: signing_key, pubkey } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn sign_request<T: std::fmt::Debug>(&self, req: T) -> Result<Request<T>> { |     pub fn sign_request<T: std::fmt::Debug>(&self, req: T) -> Result<Request<T>> { | ||||||
|         let pubkey = self.pubkey.clone(); |         let pubkey = self.pubkey.clone(); | ||||||
|         let timestamp = chrono::Utc::now().to_rfc3339(); |         let timestamp = chrono::Utc::now().to_rfc3339(); | ||||||
|  | |||||||
| @ -1,22 +1,23 @@ | |||||||
| use super::test_utils::Key; | use super::test_utils::{admin_keys, Key}; | ||||||
| use anyhow::{anyhow, Result}; | use anyhow::{anyhow, Result}; | ||||||
|  | use detee_shared::app_proto; | ||||||
| use detee_shared::common_proto::Empty; | use detee_shared::common_proto::Empty; | ||||||
| use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; | use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; | ||||||
| use detee_shared::general_proto::{AirdropReq, ReportNodeReq}; | use detee_shared::general_proto::{Account, AirdropReq, RegOperatorReq, ReportNodeReq}; | ||||||
| use detee_shared::vm_proto; | use detee_shared::vm_proto; | ||||||
| use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | ||||||
|  | use futures::StreamExt; | ||||||
| use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ}; | use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ}; | ||||||
| use surreal_brain::db::prelude as db; | use surreal_brain::db::prelude as db; | ||||||
| use surrealdb::engine::remote::ws::Client; | use surrealdb::engine::remote::ws::Client; | ||||||
| use surrealdb::Surreal; | use surrealdb::Surreal; | ||||||
| use tonic::transport::Channel; | use tonic::transport::Channel; | ||||||
| 
 | 
 | ||||||
| async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> { | pub async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> { | ||||||
|     let mut client = BrainGeneralCliClient::new(brain_channel.clone()); |     let mut client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|     let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount }; |     let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount }; | ||||||
| 
 | 
 | ||||||
|     let admin_key = Key::new(); |     let admin_key = admin_keys()[0].clone(); | ||||||
|     std::env::set_var("ADMIN_PUB_KEYS", &admin_key.pubkey); |  | ||||||
| 
 | 
 | ||||||
|     client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?; |     client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?; | ||||||
| 
 | 
 | ||||||
| @ -78,3 +79,85 @@ pub async fn report_node( | |||||||
| 
 | 
 | ||||||
|     Ok(client_gen_cli.report_node(key.sign_request(report_req)?).await?) |     Ok(client_gen_cli.report_node(key.sign_request(report_req)?).await?) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub async fn register_operator(brain_channel: &Channel, key: &Key, escrow: u64) -> Result<()> { | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let reg_req = | ||||||
|  |         RegOperatorReq { pubkey: key.pubkey.clone(), escrow, email: "foo@bar.com".to_string() }; | ||||||
|  | 
 | ||||||
|  |     cli_client.register_operator(key.sign_request(reg_req.clone()).unwrap()).await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn list_accounts(brain_channel: &Channel, admin_key: &Key) -> Result<Vec<Account>> { | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let mut stream = | ||||||
|  |         cli_client.list_accounts(admin_key.sign_request(Empty {}).unwrap()).await?.into_inner(); | ||||||
|  | 
 | ||||||
|  |     let mut accounts = Vec::new(); | ||||||
|  | 
 | ||||||
|  |     while let Some(stream_data) = stream.next().await { | ||||||
|  |         match stream_data { | ||||||
|  |             Ok(account) => { | ||||||
|  |                 accounts.push(account); | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 panic!("Error while listing accounts: {e:?}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(accounts) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn list_all_vm_contracts( | ||||||
|  |     brain_channel: &Channel, | ||||||
|  |     admin_key: &Key, | ||||||
|  | ) -> Result<Vec<vm_proto::VmContract>> { | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let mut stream = cli_client | ||||||
|  |         .list_all_vm_contracts(admin_key.sign_request(Empty {}).unwrap()) | ||||||
|  |         .await? | ||||||
|  |         .into_inner(); | ||||||
|  | 
 | ||||||
|  |     let mut vm_contracts = Vec::new(); | ||||||
|  | 
 | ||||||
|  |     while let Some(stream_data) = stream.next().await { | ||||||
|  |         match stream_data { | ||||||
|  |             Ok(vm_contract) => { | ||||||
|  |                 vm_contracts.push(vm_contract); | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 panic!("Error while listing vm_contracts: {e:?}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(vm_contracts) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn list_all_app_contracts( | ||||||
|  |     brain_channel: &Channel, | ||||||
|  |     admin_key: &Key, | ||||||
|  | ) -> Result<Vec<app_proto::AppContract>> { | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let mut stream = cli_client | ||||||
|  |         .list_all_app_contracts(admin_key.sign_request(Empty {}).unwrap()) | ||||||
|  |         .await? | ||||||
|  |         .into_inner(); | ||||||
|  | 
 | ||||||
|  |     let mut app_contracts = Vec::new(); | ||||||
|  | 
 | ||||||
|  |     while let Some(stream_data) = stream.next().await { | ||||||
|  |         match stream_data { | ||||||
|  |             Ok(app_contract) => { | ||||||
|  |                 app_contracts.push(app_contract); | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 panic!("Error while listing app_contracts: {e:?}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(app_contracts) | ||||||
|  | } | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ pub async fn register_vm_node( | |||||||
|     client: &mut BrainVmDaemonClient<Channel>, |     client: &mut BrainVmDaemonClient<Channel>, | ||||||
|     key: &Key, |     key: &Key, | ||||||
|     operator_wallet: &str, |     operator_wallet: &str, | ||||||
| ) -> Result<Vec<vm_proto::VmContract>> { | ) -> Result<Vec<vm_proto::DeleteVmReq>> { | ||||||
|     log::info!("Registering vm_node: {}", key.pubkey); |     log::info!("Registering vm_node: {}", key.pubkey); | ||||||
|     let node_pubkey = key.pubkey.clone(); |     let node_pubkey = key.pubkey.clone(); | ||||||
| 
 | 
 | ||||||
| @ -53,18 +53,18 @@ pub async fn register_vm_node( | |||||||
| 
 | 
 | ||||||
|     let mut grpc_stream = client.register_vm_node(key.sign_request(req)?).await?.into_inner(); |     let mut grpc_stream = client.register_vm_node(key.sign_request(req)?).await?.into_inner(); | ||||||
| 
 | 
 | ||||||
|     let mut vm_contracts = Vec::new(); |     let mut deleted_vm_reqs = Vec::new(); | ||||||
|     while let Some(stream_update) = grpc_stream.next().await { |     while let Some(stream_update) = grpc_stream.next().await { | ||||||
|         match stream_update { |         match stream_update { | ||||||
|             Ok(vm_c) => { |             Ok(del_vm_rq) => { | ||||||
|                 vm_contracts.push(vm_c); |                 deleted_vm_reqs.push(del_vm_rq); | ||||||
|             } |             } | ||||||
|             Err(e) => { |             Err(e) => { | ||||||
|                 panic!("Received error instead of vm_contracts: {e:?}"); |                 panic!("Received error instead of deleted_vm_reqs: {e:?}"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     Ok(vm_contracts) |     Ok(deleted_vm_reqs) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn daemon_listener( | pub async fn daemon_listener( | ||||||
|  | |||||||
| @ -1,17 +1,19 @@ | |||||||
| use common::prepare_test_env::{ | use common::prepare_test_env::{ | ||||||
|     prepare_test_db, run_service_for_stream, run_service_in_background, |     prepare_test_db, run_service_for_stream, run_service_in_background, | ||||||
| }; | }; | ||||||
| use common::test_utils::Key; | use common::test_utils::{admin_keys, Key}; | ||||||
| use common::vm_cli_utils::{create_new_vm, report_node}; | use common::vm_cli_utils::{ | ||||||
|  |     airdrop, create_new_vm, list_accounts, list_all_app_contracts, list_all_vm_contracts, | ||||||
|  |     register_operator, report_node, | ||||||
|  | }; | ||||||
| use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | ||||||
| use detee_shared::common_proto::{Empty, Pubkey}; | use detee_shared::common_proto::{Empty, Pubkey}; | ||||||
| use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; | use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; | ||||||
| use detee_shared::general_proto::AirdropReq; | use detee_shared::general_proto::{AirdropReq, BanUserReq, SlashReq}; | ||||||
| use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; | use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; | ||||||
| use futures::StreamExt; | use futures::StreamExt; | ||||||
| use itertools::Itertools; | use surreal_brain::constants::{ACCOUNT, ACTIVE_APP, ACTIVE_VM, BAN, TOKEN_DECIMAL, VM_NODE}; | ||||||
| use std::vec; | use surreal_brain::db::prelude as db; | ||||||
| use surreal_brain::constants::VM_NODE; |  | ||||||
| use surreal_brain::db::vm::VmNodeWithReports; | use surreal_brain::db::vm::VmNodeWithReports; | ||||||
| 
 | 
 | ||||||
| mod common; | mod common; | ||||||
| @ -41,15 +43,12 @@ async fn test_general_airdrop() { | |||||||
|     // env_logger::builder().filter_level(log::LevelFilter::Trace).init();
 |     // env_logger::builder().filter_level(log::LevelFilter::Trace).init();
 | ||||||
|     prepare_test_db().await.unwrap(); |     prepare_test_db().await.unwrap(); | ||||||
| 
 | 
 | ||||||
|     const AIRDROP_MULTIPLE: u64 = 1_000_000_000; |  | ||||||
|     let airdrop_amount = 10; |     let airdrop_amount = 10; | ||||||
| 
 | 
 | ||||||
|     let addr = run_service_in_background().await.unwrap(); |     let addr = run_service_in_background().await.unwrap(); | ||||||
|     let mut client = BrainGeneralCliClient::connect(format!("http://{}", addr)).await.unwrap(); |     let mut client = BrainGeneralCliClient::connect(format!("http://{}", addr)).await.unwrap(); | ||||||
| 
 | 
 | ||||||
|     let admin_keys = vec![Key::new(), Key::new(), Key::new()]; |     let admin_keys = admin_keys(); | ||||||
|     let admin_pub_keys = admin_keys.iter().map(|k| k.pubkey.clone()).join(", "); |  | ||||||
|     std::env::set_var("ADMIN_PUB_KEYS", admin_pub_keys); |  | ||||||
| 
 | 
 | ||||||
|     let user_01_key = Key::new(); |     let user_01_key = Key::new(); | ||||||
|     let user_01_pubkey = user_01_key.pubkey.clone(); |     let user_01_pubkey = user_01_key.pubkey.clone(); | ||||||
| @ -72,7 +71,7 @@ async fn test_general_airdrop() { | |||||||
|     let bal_req = user_01_key.sign_request(bal_req_data.clone()).unwrap(); |     let bal_req = user_01_key.sign_request(bal_req_data.clone()).unwrap(); | ||||||
|     let acc_bal_user_01 = client.get_balance(bal_req).await.unwrap().into_inner(); |     let acc_bal_user_01 = client.get_balance(bal_req).await.unwrap().into_inner(); | ||||||
| 
 | 
 | ||||||
|     assert_eq!(acc_bal_user_01.balance, airdrop_amount * AIRDROP_MULTIPLE); |     assert_eq!(acc_bal_user_01.balance, airdrop_amount * TOKEN_DECIMAL); | ||||||
|     assert_eq!(acc_bal_user_01.tmp_locked, 0); |     assert_eq!(acc_bal_user_01.tmp_locked, 0); | ||||||
| 
 | 
 | ||||||
|     // second airdrop from same admin
 |     // second airdrop from same admin
 | ||||||
| @ -84,7 +83,7 @@ async fn test_general_airdrop() { | |||||||
|         .unwrap() |         .unwrap() | ||||||
|         .into_inner(); |         .into_inner(); | ||||||
| 
 | 
 | ||||||
|     assert_eq!(acc_bal_user_01.balance, 2 * airdrop_amount * AIRDROP_MULTIPLE); |     assert_eq!(acc_bal_user_01.balance, 2 * airdrop_amount * TOKEN_DECIMAL); | ||||||
| 
 | 
 | ||||||
|     // third airdrop from another admin
 |     // third airdrop from another admin
 | ||||||
|     let _ = client.airdrop(admin_keys[1].sign_request(airdrop_req.clone()).unwrap()).await.unwrap(); |     let _ = client.airdrop(admin_keys[1].sign_request(airdrop_req.clone()).unwrap()).await.unwrap(); | ||||||
| @ -95,7 +94,7 @@ async fn test_general_airdrop() { | |||||||
|         .unwrap() |         .unwrap() | ||||||
|         .into_inner(); |         .into_inner(); | ||||||
| 
 | 
 | ||||||
|     assert_eq!(acc_bal_user_01.balance, 3 * airdrop_amount * AIRDROP_MULTIPLE); |     assert_eq!(acc_bal_user_01.balance, 3 * airdrop_amount * TOKEN_DECIMAL); | ||||||
| 
 | 
 | ||||||
|     // self airdrop
 |     // self airdrop
 | ||||||
|     let airdrop_req = AirdropReq { pubkey: admin_keys[2].pubkey.clone(), tokens: airdrop_amount }; |     let airdrop_req = AirdropReq { pubkey: admin_keys[2].pubkey.clone(), tokens: airdrop_amount }; | ||||||
| @ -109,7 +108,7 @@ async fn test_general_airdrop() { | |||||||
|         .unwrap() |         .unwrap() | ||||||
|         .into_inner(); |         .into_inner(); | ||||||
| 
 | 
 | ||||||
|     assert_eq!(acc_bal_admin_3.balance, airdrop_amount * AIRDROP_MULTIPLE); |     assert_eq!(acc_bal_admin_3.balance, airdrop_amount * TOKEN_DECIMAL); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| @ -127,6 +126,8 @@ async fn test_report_node() { | |||||||
|     log::info!("Report error: {:?}", report_error); |     log::info!("Report error: {:?}", report_error); | ||||||
|     assert!(report_error.to_string().contains("No contract found by this ID.")); |     assert!(report_error.to_string().contains("No contract found by this ID.")); | ||||||
| 
 | 
 | ||||||
|  |     airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); | ||||||
|  | 
 | ||||||
|     let active_vm_id = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap(); |     let active_vm_id = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap(); | ||||||
| 
 | 
 | ||||||
|     let reason = String::from("something went wrong on vm"); |     let reason = String::from("something went wrong on vm"); | ||||||
| @ -211,3 +212,234 @@ async fn test_inspect_operator() { | |||||||
|     assert!(!inspect_response.vm_nodes.is_empty()); |     assert!(!inspect_response.vm_nodes.is_empty()); | ||||||
|     assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey); |     assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_register_operator() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  |     let key = Key::new(); | ||||||
|  | 
 | ||||||
|  |     let min_escrew_error = register_operator(&brain_channel, &key, 10).await.err().unwrap(); | ||||||
|  |     assert!(min_escrew_error.to_string().contains("Minimum escrow amount is 5000")); | ||||||
|  | 
 | ||||||
|  |     let no_balance = register_operator(&brain_channel, &key, 5000).await.err().unwrap(); | ||||||
|  |     assert!(no_balance.to_string().contains("Insufficient funds, deposit more tokens")); | ||||||
|  | 
 | ||||||
|  |     airdrop(&brain_channel, &key.pubkey, 1000).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let no_balance = register_operator(&brain_channel, &key, 5000).await.err().unwrap(); | ||||||
|  |     assert!(no_balance.to_string().contains("Insufficient funds, deposit more tokens")); | ||||||
|  | 
 | ||||||
|  |     airdrop(&brain_channel, &key.pubkey, 7000).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     register_operator(&brain_channel, &key, 6000).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let operator_account: Option<db::Account> = | ||||||
|  |         db_conn.select((ACCOUNT, key.pubkey)).await.unwrap(); | ||||||
|  |     assert!(operator_account.is_some()); | ||||||
|  |     let account = operator_account.unwrap(); | ||||||
|  |     assert_eq!(account.escrow, 6000 * TOKEN_DECIMAL); | ||||||
|  |     assert_eq!(account.balance, 2000 * TOKEN_DECIMAL); | ||||||
|  |     assert_eq!(account.tmp_locked, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_kick_contract() { | ||||||
|  |     // TODO: implement seed data to test
 | ||||||
|  |     // possibilities
 | ||||||
|  |     // 1. vm contract
 | ||||||
|  |     // 2. app contract
 | ||||||
|  |     // 3. non existent contract
 | ||||||
|  |     // 4. other operator's contract
 | ||||||
|  |     // 5. contract collected more than a week
 | ||||||
|  |     // 6. refund amount calculation
 | ||||||
|  |     // 7. refund of multiple contract kick in a day for same user
 | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     env_logger::builder() | ||||||
|  |         .filter_level(log::LevelFilter::Info) | ||||||
|  |         .filter_module("tungstenite", log::LevelFilter::Debug) | ||||||
|  |         .filter_module("tokio_tungstenite", log::LevelFilter::Debug) | ||||||
|  |         .init(); | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  |     let contract_uuid = "5af49a714c64a82ef50e574b023b2a0ef0405ed"; | ||||||
|  |     let operator_wallet = "7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB"; | ||||||
|  |     let reason = "'; THROW 'Injected error'; --"; // sql injection query
 | ||||||
|  | 
 | ||||||
|  |     let kick_response = | ||||||
|  |         surreal_brain::db::general::kick_contract(&db_conn, operator_wallet, contract_uuid, reason) | ||||||
|  |             .await; | ||||||
|  |     match kick_response { | ||||||
|  |         Ok(refund_amount) => { | ||||||
|  |             println!("Refund amount: {}", refund_amount); | ||||||
|  |         } | ||||||
|  |         Err(e) => { | ||||||
|  |             println!("Error: {}", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_ban_user() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let op_key = Key::from("6f3WcQuv8sPrxAsSh24TdXkauwVd8vFUyCCfbNqxszvR"); // "4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ"
 | ||||||
|  |     let user_key = Key::new(); | ||||||
|  |     let node_pubkey = "7fujZQeTme52RdXTLmQST5jBgAbvzic5iERtH5EWoYjk"; | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  |     base58 = "6f3WcQuv8sPrxAsSh24TdXkauwVd8vFUyCCfbNqxszvR" | ||||||
|  |     pubkey = "4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ" | ||||||
|  |     base58 = "7fujZQeTme52RdXTLmQST5jBgAbvzic5iERtH5EWoYjk" | ||||||
|  |     pubkey = "G2PetsFM8peG77rf9UTfVD7HEHNPmdjV3cebBpKuR3QT" | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  |     let user_wallet = user_key.pubkey.clone(); | ||||||
|  |     let operator_wallet = op_key.pubkey.clone(); | ||||||
|  | 
 | ||||||
|  |     let _ = cli_client | ||||||
|  |         .ban_user( | ||||||
|  |             op_key | ||||||
|  |                 .sign_request(BanUserReq { | ||||||
|  |                     operator_wallet: operator_wallet.clone(), | ||||||
|  |                     user_wallet: user_wallet.clone(), | ||||||
|  |                 }) | ||||||
|  |                 .unwrap(), | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|  |     let ban_reconrd: Option<db::Ban> = db_conn | ||||||
|  |         .query(format!("SELECT * FROM {BAN} WHERE in = account:{operator_wallet} AND out = account:{user_wallet};")) | ||||||
|  |         .await.unwrap().take(0).unwrap(); | ||||||
|  | 
 | ||||||
|  |     assert!(ban_reconrd.is_some()); | ||||||
|  | 
 | ||||||
|  |     let already_banned = cli_client | ||||||
|  |         .ban_user( | ||||||
|  |             op_key | ||||||
|  |                 .sign_request(BanUserReq { | ||||||
|  |                     operator_wallet: operator_wallet.clone(), | ||||||
|  |                     user_wallet: user_wallet.clone(), | ||||||
|  |                 }) | ||||||
|  |                 .unwrap(), | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .err() | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|  |     assert!(already_banned.message().contains("Already banned")); | ||||||
|  | 
 | ||||||
|  |     airdrop(&brain_channel, &user_wallet, 10).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let operator_banned_you = | ||||||
|  |         create_new_vm(&db_conn, &user_key, node_pubkey, &brain_channel).await.err().unwrap(); | ||||||
|  | 
 | ||||||
|  |     assert!(operator_banned_you.to_string().contains("This operator banned you")); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_slash_operator() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  |     let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone()); | ||||||
|  |     let op_key = Key::new(); | ||||||
|  | 
 | ||||||
|  |     let admin = admin_keys()[0].clone(); | ||||||
|  |     let escrew = 5500; | ||||||
|  |     let slash_amt = 2500; | ||||||
|  | 
 | ||||||
|  |     airdrop(&brain_channel, &op_key.pubkey, 10000).await.unwrap(); | ||||||
|  |     register_operator(&brain_channel, &op_key, escrew).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let raw_slash_req = SlashReq { pubkey: op_key.pubkey.clone(), tokens: 2500 }; | ||||||
|  | 
 | ||||||
|  |     let other_key = Key::new(); | ||||||
|  |     let admin_error = cli_client | ||||||
|  |         .slash(other_key.sign_request(raw_slash_req.clone()).unwrap()) | ||||||
|  |         .await | ||||||
|  |         .err() | ||||||
|  |         .unwrap(); | ||||||
|  |     assert!(admin_error.message().contains("This operation is reserved to admin accounts")); | ||||||
|  | 
 | ||||||
|  |     let admin_error = | ||||||
|  |         cli_client.slash(op_key.sign_request(raw_slash_req.clone()).unwrap()).await.err().unwrap(); | ||||||
|  |     assert!(admin_error.message().contains("This operation is reserved to admin accounts")); | ||||||
|  | 
 | ||||||
|  |     cli_client.slash(admin.sign_request(raw_slash_req.clone()).unwrap()).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let operator_account: Option<db::Account> = | ||||||
|  |         db_conn.select((ACCOUNT, op_key.pubkey)).await.unwrap(); | ||||||
|  |     assert!(operator_account.is_some()); | ||||||
|  |     let account = operator_account.unwrap(); | ||||||
|  |     assert_eq!(account.escrow, (escrew - slash_amt) * TOKEN_DECIMAL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_admin_list_account() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let admin_key = admin_keys()[0].clone(); | ||||||
|  | 
 | ||||||
|  |     let unauthenticated = list_accounts(&brain_channel, &Key::new()).await.err().unwrap(); | ||||||
|  |     assert!(unauthenticated.to_string().contains("reserved to admin accounts")); | ||||||
|  | 
 | ||||||
|  |     tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; // sync time for other db opeartion
 | ||||||
|  | 
 | ||||||
|  |     let acc_in_db = db_conn.select::<Vec<db::Account>>(ACCOUNT).await.unwrap(); | ||||||
|  |     let accounts = list_accounts(&brain_channel, &admin_key).await.unwrap(); | ||||||
|  |     assert_eq!(accounts.len(), acc_in_db.len()); | ||||||
|  | 
 | ||||||
|  |     airdrop(&brain_channel, &Key::new().pubkey, 10).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let acc_in_db = db_conn.select::<Vec<db::Account>>(ACCOUNT).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let accounts = list_accounts(&brain_channel, &admin_key).await.unwrap(); | ||||||
|  |     assert_eq!(accounts.len(), acc_in_db.len()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_admin_list_all_vm_contracts() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let admin_key = admin_keys()[0].clone(); | ||||||
|  | 
 | ||||||
|  |     let unauthenticated = list_all_vm_contracts(&brain_channel, &Key::new()).await.err().unwrap(); | ||||||
|  |     assert!(unauthenticated.to_string().contains("reserved to admin accounts")); | ||||||
|  | 
 | ||||||
|  |     let vm_in_db = db_conn.select::<Vec<db::ActiveVm>>(ACTIVE_VM).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let vm_contracts = list_all_vm_contracts(&brain_channel, &admin_key).await.unwrap(); | ||||||
|  |     assert_eq!(vm_contracts.len(), vm_in_db.len()); | ||||||
|  | 
 | ||||||
|  |     // TODO: mock vm daemon and deploy a new vm, then list again
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_admin_list_all_app_contracts() { | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let admin_key = admin_keys()[0].clone(); | ||||||
|  | 
 | ||||||
|  |     let unauthenticated = list_all_app_contracts(&brain_channel, &Key::new()).await.err().unwrap(); | ||||||
|  |     assert!(unauthenticated.to_string().contains("reserved to admin accounts")); | ||||||
|  | 
 | ||||||
|  |     let app_contracts = list_all_app_contracts(&brain_channel, &admin_key).await.unwrap(); | ||||||
|  |     assert_eq!(app_contracts.len(), 1); | ||||||
|  | 
 | ||||||
|  |     let app_in_db = db_conn.select::<Vec<db::ActiveApp>>(ACTIVE_APP).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let app_contracts = list_all_app_contracts(&brain_channel, &admin_key).await.unwrap(); | ||||||
|  |     assert_eq!(app_contracts.len(), app_in_db.len()); | ||||||
|  | 
 | ||||||
|  |     // TODO: mock app daemon and deploy a new app, then list again
 | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,12 +1,14 @@ | |||||||
| use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; | use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; | ||||||
| use common::test_utils::Key; | use common::test_utils::Key; | ||||||
| use common::vm_cli_utils::create_new_vm; | use common::vm_cli_utils::{airdrop, create_new_vm}; | ||||||
| use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | ||||||
| use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | ||||||
| use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; | use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; | ||||||
| use detee_shared::vm_proto::{ListVmContractsReq, NewVmReq}; | use detee_shared::vm_proto::{ListVmContractsReq, NewVmReq}; | ||||||
| use futures::StreamExt; | use futures::StreamExt; | ||||||
| use std::vec; | use std::vec; | ||||||
|  | use surreal_brain::constants::ACTIVE_VM; | ||||||
|  | use surreal_brain::db::vm::ActiveVm; | ||||||
| 
 | 
 | ||||||
| mod common; | mod common; | ||||||
| 
 | 
 | ||||||
| @ -26,7 +28,11 @@ async fn test_vm_creation() { | |||||||
|     let grpc_error_message = new_vm_resp.err().unwrap().to_string(); |     let grpc_error_message = new_vm_resp.err().unwrap().to_string(); | ||||||
|     assert!(grpc_error_message.contains("Insufficient funds")); |     assert!(grpc_error_message.contains("Insufficient funds")); | ||||||
| 
 | 
 | ||||||
|     // TODO: Airdrop the user and try creating the VM again
 |     airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let new_vm_id = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap(); | ||||||
|  |     let active_vm: Option<ActiveVm> = db.select((ACTIVE_VM, new_vm_id)).await.unwrap(); | ||||||
|  |     assert!(active_vm.is_some()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| @ -51,6 +57,8 @@ async fn test_timeout_vm_creation() { | |||||||
|         ..Default::default() |         ..Default::default() | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     airdrop(&brain_channel, &key.pubkey, 10).await.unwrap(); | ||||||
|  | 
 | ||||||
|     let mut client_vm_cli = BrainVmCliClient::new(brain_channel.clone()); |     let mut client_vm_cli = BrainVmCliClient::new(brain_channel.clone()); | ||||||
|     let timeout_error = |     let timeout_error = | ||||||
|         client_vm_cli.new_vm(key.sign_request(new_vm_req).unwrap()).await.err().unwrap(); |         client_vm_cli.new_vm(key.sign_request(new_vm_req).unwrap()).await.err().unwrap(); | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ use common::prepare_test_env::{ | |||||||
|     prepare_test_db, run_service_for_stream, run_service_in_background, |     prepare_test_db, run_service_for_stream, run_service_in_background, | ||||||
| }; | }; | ||||||
| use common::test_utils::Key; | use common::test_utils::Key; | ||||||
|  | use common::vm_cli_utils::airdrop; | ||||||
| use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; | ||||||
| use detee_shared::vm_proto; | use detee_shared::vm_proto; | ||||||
| use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; | ||||||
| @ -29,7 +30,7 @@ async fn test_brain_message() { | |||||||
| 
 | 
 | ||||||
|     let brain_channel = run_service_for_stream().await.unwrap(); |     let brain_channel = run_service_for_stream().await.unwrap(); | ||||||
|     let daemon_key = mock_vm_daemon(&brain_channel).await.unwrap(); |     let daemon_key = mock_vm_daemon(&brain_channel).await.unwrap(); | ||||||
|     let mut cli_client = BrainVmCliClient::new(brain_channel); |     let mut cli_client = BrainVmCliClient::new(brain_channel.clone()); | ||||||
| 
 | 
 | ||||||
|     let cli_key = Key::new(); |     let cli_key = Key::new(); | ||||||
| 
 | 
 | ||||||
| @ -41,6 +42,7 @@ async fn test_brain_message() { | |||||||
|         locked_nano: 0, |         locked_nano: 0, | ||||||
|         ..Default::default() |         ..Default::default() | ||||||
|     }; |     }; | ||||||
|  |     airdrop(&brain_channel, &cli_key.pubkey, 10).await.unwrap(); | ||||||
|     let new_vm_resp = |     let new_vm_resp = | ||||||
|         cli_client.new_vm(cli_key.sign_request(req).unwrap()).await.unwrap().into_inner(); |         cli_client.new_vm(cli_key.sign_request(req).unwrap()).await.unwrap().into_inner(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										494
									
								
								tests/mock_data.yaml
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										494
									
								
								tests/mock_data.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,494 @@ | |||||||
|  | accounts: | ||||||
|  |   DXXkYSnhP3ijsHYxkedcuMomEyc122WaAbkDX7SaGuUS: | ||||||
|  |     balance: 20293420000 | ||||||
|  |     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 | ||||||
|  |     tmp_locked: 0 | ||||||
|  |     kicked_for: [] | ||||||
|  |     last_kick: 1970-01-01T00:00:00Z | ||||||
|  |     banned_by: [] | ||||||
|  |   CLYyE6id5876DW69LHDynuH6TjJPvWRBTQC5XDZ6jfT1: | ||||||
|  |     balance: 25000000000 | ||||||
|  |     tmp_locked: 0 | ||||||
|  |     kicked_for: [] | ||||||
|  |     last_kick: 1970-01-01T00:00:00Z | ||||||
|  |     banned_by: [] | ||||||
|  |   db5ZB6uDbF1mUUgeggBZ9XKbi3mUfX6WHkBpbwUHJpB: | ||||||
|  |     balance: 25000000000 | ||||||
|  |     tmp_locked: 0 | ||||||
|  |     kicked_for: [] | ||||||
|  |     last_kick: 1970-01-01T00:00:00Z | ||||||
|  |     banned_by: [] | ||||||
|  |   fY3NNjvFTeR1FBh5nXV3ujX7zZqrm3eBUWGEiG75TK1: | ||||||
|  |     balance: 1000000000 | ||||||
|  |     tmp_locked: 0 | ||||||
|  |     kicked_for: [] | ||||||
|  |     last_kick: 1970-01-01T00:00:00Z | ||||||
|  |     banned_by: [] | ||||||
|  |   FBMWVqME3t1i4R6zWyDQGUuiTeruZ1TxLhTmhaEcFypZ: | ||||||
|  |     balance: 181560160000 | ||||||
|  |     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: [] | ||||||
|  | 
 | ||||||
|  |   4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ: | ||||||
|  |     balance: 25949200000 | ||||||
|  |     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 | ||||||
|  |     email: gheo@detee.ltd | ||||||
|  |     banned_users: [] | ||||||
|  |     vm_nodes: | ||||||
|  |       - 2Uf5pxhxKTUm6gRMnpbJHYDuyA6BWUfFsdmPyWfbMV1f | ||||||
|  |       - 7Xw3RxbP5pvfjZ8U6yA3HHVSS9YXjKH5Vkas3JRbQYd9 | ||||||
|  |     app_nodes: [] | ||||||
|  |   7V3rEuh6j8VuwMVB5PyGqWKLmjJ4fYSv6WtrTL51NZTB: | ||||||
|  |     escrow: 888888888899999 | ||||||
|  |     email: "" | ||||||
|  |     banned_users: [] | ||||||
|  |     vm_nodes: [] | ||||||
|  |     app_nodes: | ||||||
|  |       - BiqoPUEoAxYxMRXUmyofoS9H1TBQgQqvLJ6MbWh88AQg | ||||||
|  | 
 | ||||||
|  |   4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ: | ||||||
|  |     escrow: 5499700480000 | ||||||
|  |     email: "test_mock@operator" | ||||||
|  |     banned_users: [] | ||||||
|  |     vm_nodes: | ||||||
|  |       - 7fujZQeTme52RdXTLmQST5jBgAbvzic5iERtH5EWoYjk | ||||||
|  |     app_nodes: [] | ||||||
|  | 
 | ||||||
|  | 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: 7fujZQeTme52RdXTLmQST5jBgAbvzic5iERtH5EWoYjk | ||||||
|  |     operator_wallet: 4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ | ||||||
|  |     country: GB | ||||||
|  |     region: England | ||||||
|  |     city: London | ||||||
|  |     ip: 193.234.17.2 | ||||||
|  |     avail_mem_mb: 28000 | ||||||
|  |     avail_vcpus: 24 | ||||||
|  |     avail_storage_gbs: 1680 | ||||||
|  |     avail_ipv4: 1 | ||||||
|  |     avail_ipv6: 0 | ||||||
|  |     avail_ports: 19999 | ||||||
|  |     max_ports_per_vm: 10 | ||||||
|  |     price: 24000 | ||||||
|  |     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: 5af49a71-4c64-a82e-f50e574-b023-b2a0ef0405ed | ||||||
|  |     hostname: hallow-hobo | ||||||
|  |     admin_pubkey: 4qFJJJdRrSB9hCn8rrvYTXHLJg371ab36PJmZ4uxHjGQ | ||||||
|  |     node_pubkey: 7fujZQeTme52RdXTLmQST5jBgAbvzic5iERtH5EWoYjk | ||||||
|  |     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 | ||||||
|  | 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 | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user