20 KiB
20 KiB
ThreeFold Grid Deployment Automation Specification
Document Purpose: Technical specification for automated deployment pipeline from TFC payment to ThreeFold Grid service activation.
Last Updated: 2025-08-04
Status: Implementation Ready
Overview
This document outlines the automated deployment pipeline that converts TFC credits to TFT tokens and deploys services on the ThreeFold Grid, providing seamless Web2-to-Web3 bridge functionality.
Deployment Pipeline Architecture
graph TD
A[User Purchase Complete] --> B[TFC Deducted]
B --> C[Deployment Queued]
C --> D[Commission Calculated]
D --> E[USD → TFT Conversion]
E --> F[Grid Node Selection]
F --> G[Resource Allocation]
G --> H[Service Deployment]
H --> I[Health Check]
I --> J{Deployment Success?}
J -->|Yes| K[Service Active]
J -->|No| L[Rollback & Refund]
K --> M[User Notification]
L --> N[Error Notification]
Core Components
1. Deployment Queue Service
// src/services/deployment_queue.rs
use tokio::sync::mpsc;
use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentJob {
pub id: String,
pub user_id: String,
pub service_spec: ServiceSpec,
pub tfc_amount: Decimal,
pub commission_amount: Decimal,
pub grid_payment_amount: Decimal,
pub priority: DeploymentPriority,
pub created_at: DateTime<Utc>,
pub max_retries: u32,
pub retry_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeploymentPriority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceSpec {
pub service_type: ServiceType,
pub cpu_cores: u32,
pub memory_gb: u32,
pub storage_gb: u32,
pub network_config: NetworkConfig,
pub environment_vars: HashMap<String, String>,
pub docker_image: Option<String>,
pub kubernetes_manifest: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceType {
VM { os: String, version: String },
Container { image: String, ports: Vec<u16> },
KubernetesCluster { node_count: u32 },
Storage { storage_type: String },
Application { app_id: String },
}
pub struct DeploymentQueueService {
queue: Arc<Mutex<VecDeque<DeploymentJob>>>,
workers: Vec<DeploymentWorker>,
tx: mpsc::UnboundedSender<DeploymentJob>,
rx: Arc<Mutex<mpsc::UnboundedReceiver<DeploymentJob>>>,
}
impl DeploymentQueueService {
pub fn new(worker_count: usize) -> Self {
let (tx, rx) = mpsc::unbounded_channel();
let queue = Arc::new(Mutex::new(VecDeque::new()));
let mut workers = Vec::new();
for i in 0..worker_count {
workers.push(DeploymentWorker::new(i, rx.clone()));
}
Self {
queue,
workers,
tx,
rx: Arc::new(Mutex::new(rx)),
}
}
pub async fn queue_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
// Add to persistent queue
self.queue.lock().await.push_back(job.clone());
// Send to worker pool
self.tx.send(job).map_err(|_| DeploymentError::QueueFull)?;
Ok(())
}
pub async fn start_workers(&self) {
for worker in &self.workers {
worker.start().await;
}
}
}
2. ThreeFold Grid Integration
// src/services/threefold_grid.rs
use reqwest::Client;
use serde_json::json;
pub struct ThreeFoldGridService {
client: Client,
grid_proxy_url: String,
tft_wallet: TFTWallet,
}
impl ThreeFoldGridService {
pub async fn deploy_vm(
&self,
spec: &ServiceSpec,
tft_amount: Decimal,
) -> Result<GridDeployment, GridError> {
// 1. Find suitable nodes
let nodes = self.find_suitable_nodes(spec).await?;
let selected_node = self.select_best_node(&nodes, spec)?;
// 2. Create deployment contract
let contract = self.create_deployment_contract(spec, &selected_node, tft_amount).await?;
// 3. Deploy on grid
let deployment = self.execute_deployment(contract).await?;
// 4. Wait for deployment to be ready
self.wait_for_deployment_ready(&deployment.id, Duration::from_secs(300)).await?;
// 5. Get connection details
let connection_info = self.get_deployment_info(&deployment.id).await?;
Ok(GridDeployment {
id: deployment.id,
node_id: selected_node.id,
contract_id: contract.id,
connection_info,
status: DeploymentStatus::Active,
created_at: Utc::now(),
})
}
async fn find_suitable_nodes(&self, spec: &ServiceSpec) -> Result<Vec<GridNode>, GridError> {
let query = json!({
"cpu": spec.cpu_cores,
"memory": spec.memory_gb * 1024 * 1024 * 1024, // Convert GB to bytes
"storage": spec.storage_gb * 1024 * 1024 * 1024,
"status": "up",
"available": true
});
let response = self.client
.post(&format!("{}/nodes/search", self.grid_proxy_url))
.json(&query)
.send()
.await?;
let nodes: Vec<GridNode> = response.json().await?;
Ok(nodes)
}
fn select_best_node(&self, nodes: &[GridNode], spec: &ServiceSpec) -> Result<GridNode, GridError> {
// Node selection algorithm:
// 1. Filter by resource availability
// 2. Prefer nodes with better uptime
// 3. Consider geographic proximity
// 4. Balance load across nodes
let suitable_nodes: Vec<_> = nodes.iter()
.filter(|node| {
node.available_cpu >= spec.cpu_cores &&
node.available_memory >= spec.memory_gb * 1024 * 1024 * 1024 &&
node.available_storage >= spec.storage_gb * 1024 * 1024 * 1024 &&
node.uptime_percentage > 95.0
})
.collect();
if suitable_nodes.is_empty() {
return Err(GridError::NoSuitableNodes);
}
// Select node with best score (uptime + available resources)
let best_node = suitable_nodes.iter()
.max_by(|a, b| {
let score_a = a.uptime_percentage + (a.available_cpu as f64 * 10.0);
let score_b = b.uptime_percentage + (b.available_cpu as f64 * 10.0);
score_a.partial_cmp(&score_b).unwrap()
})
.unwrap();
Ok((*best_node).clone())
}
async fn create_deployment_contract(
&self,
spec: &ServiceSpec,
node: &GridNode,
tft_amount: Decimal,
) -> Result<DeploymentContract, GridError> {
match &spec.service_type {
ServiceType::VM { os, version } => {
self.create_vm_contract(spec, node, os, version, tft_amount).await
}
ServiceType::Container { image, ports } => {
self.create_container_contract(spec, node, image, ports, tft_amount).await
}
ServiceType::KubernetesCluster { node_count } => {
self.create_k8s_contract(spec, node, *node_count, tft_amount).await
}
ServiceType::Storage { storage_type } => {
self.create_storage_contract(spec, node, storage_type, tft_amount).await
}
ServiceType::Application { app_id } => {
self.create_app_contract(spec, node, app_id, tft_amount).await
}
}
}
async fn create_vm_contract(
&self,
spec: &ServiceSpec,
node: &GridNode,
os: &str,
version: &str,
tft_amount: Decimal,
) -> Result<DeploymentContract, GridError> {
let vm_config = json!({
"type": "vm",
"node_id": node.id,
"cpu": spec.cpu_cores,
"memory": spec.memory_gb,
"storage": spec.storage_gb,
"os": os,
"version": version,
"network": spec.network_config,
"environment": spec.environment_vars,
"payment": {
"amount": tft_amount,
"currency": "TFT"
}
});
let response = self.client
.post(&format!("{}/deployments/vm", self.grid_proxy_url))
.json(&vm_config)
.send()
.await?;
let contract: DeploymentContract = response.json().await?;
Ok(contract)
}
}
3. TFT Conversion Service
// src/services/tft_conversion.rs
use rust_decimal::Decimal;
pub struct TFTConversionService {
grid_client: ThreeFoldGridService,
price_oracle: TFTPriceOracle,
}
impl TFTConversionService {
pub async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<TFTConversion, ConversionError> {
// Get current TFT/USD rate
let tft_rate = self.price_oracle.get_tft_usd_rate().await?;
let tft_amount = usd_amount / tft_rate;
// Add small buffer for price fluctuations (2%)
let tft_with_buffer = tft_amount * Decimal::from_str("1.02")?;
Ok(TFTConversion {
usd_amount,
tft_rate,
tft_amount: tft_with_buffer,
conversion_timestamp: Utc::now(),
})
}
pub async fn execute_conversion(&self, conversion: &TFTConversion) -> Result<TFTTransaction, ConversionError> {
// Execute the actual TFT purchase/conversion
// This would integrate with TFT DEX or direct wallet operations
let transaction = self.grid_client.tft_wallet.convert_usd_to_tft(
conversion.usd_amount,
conversion.tft_amount,
).await?;
Ok(transaction)
}
}
#[derive(Debug, Clone)]
pub struct TFTConversion {
pub usd_amount: Decimal,
pub tft_rate: Decimal,
pub tft_amount: Decimal,
pub conversion_timestamp: DateTime<Utc>,
}
pub struct TFTPriceOracle {
client: Client,
}
impl TFTPriceOracle {
pub async fn get_tft_usd_rate(&self) -> Result<Decimal, PriceError> {
// Get TFT price from multiple sources and average
let sources = vec![
self.get_rate_from_dex().await,
self.get_rate_from_coingecko().await,
self.get_rate_from_grid_stats().await,
];
let valid_rates: Vec<Decimal> = sources.into_iter()
.filter_map(|r| r.ok())
.collect();
if valid_rates.is_empty() {
return Err(PriceError::NoValidSources);
}
// Calculate average rate
let sum: Decimal = valid_rates.iter().sum();
let average = sum / Decimal::from(valid_rates.len());
Ok(average)
}
}
4. Deployment Worker
// src/services/deployment_worker.rs
pub struct DeploymentWorker {
id: usize,
grid_service: Arc<ThreeFoldGridService>,
tft_service: Arc<TFTConversionService>,
notification_service: Arc<NotificationService>,
}
impl DeploymentWorker {
pub async fn process_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
log::info!("Worker {} processing deployment {}", self.id, job.id);
// Update status to "processing"
self.update_deployment_status(&job.id, DeploymentStatus::Processing).await?;
// 1. Convert USD to TFT
let conversion = self.tft_service.convert_usd_to_tft(job.grid_payment_amount).await?;
let tft_transaction = self.tft_service.execute_conversion(&conversion).await?;
// 2. Deploy on ThreeFold Grid
let grid_deployment = self.grid_service.deploy_vm(&job.service_spec, conversion.tft_amount).await?;
// 3. Update deployment record
self.update_deployment_with_grid_info(&job.id, &grid_deployment, &tft_transaction).await?;
// 4. Verify deployment health
self.verify_deployment_health(&grid_deployment).await?;
// 5. Update status to "active"
self.update_deployment_status(&job.id, DeploymentStatus::Active).await?;
// 6. Notify user
self.notification_service.send_deployment_success(&job.user_id, &job.id).await?;
log::info!("Worker {} completed deployment {}", self.id, job.id);
Ok(())
}
async fn handle_deployment_failure(&self, job: &DeploymentJob, error: &DeploymentError) {
log::error!("Deployment {} failed: {:?}", job.id, error);
// Update status to failed
self.update_deployment_status(&job.id, DeploymentStatus::Failed).await.ok();
// Refund TFC credits to user
if let Err(refund_error) = self.refund_tfc_credits(job).await {
log::error!("Failed to refund TFC for deployment {}: {:?}", job.id, refund_error);
}
// Notify user of failure
self.notification_service.send_deployment_failure(&job.user_id, &job.id, error).await.ok();
}
async fn refund_tfc_credits(&self, job: &DeploymentJob) -> Result<(), RefundError> {
// Refund the full TFC amount back to user
self.tfc_service.add_tfc_credits(
&job.user_id,
job.tfc_amount,
&format!("Refund for failed deployment {}", job.id),
).await?;
Ok(())
}
}
Service Management Dashboard
1. Real-time Deployment Status
<!-- Deployment Status Component -->
<div class="deployment-status-card">
<div class="card-header">
<h5>Deployment Status</h5>
<span class="status-badge status-{{deployment.status}}">{{deployment.status}}</span>
</div>
<div class="card-body">
<div class="progress-timeline">
<div class="timeline-step {{#if deployment.payment_completed}}completed{{/if}}">
<i class="bi bi-credit-card"></i>
<span>Payment Processed</span>
</div>
<div class="timeline-step {{#if deployment.queued}}completed{{/if}}">
<i class="bi bi-clock"></i>
<span>Queued for Deployment</span>
</div>
<div class="timeline-step {{#if deployment.converting}}completed{{/if}}">
<i class="bi bi-arrow-left-right"></i>
<span>Converting TFC → TFT</span>
</div>
<div class="timeline-step {{#if deployment.deploying}}completed{{/if}}">
<i class="bi bi-cloud-upload"></i>
<span>Deploying on Grid</span>
</div>
<div class="timeline-step {{#if deployment.active}}completed{{/if}}">
<i class="bi bi-check-circle"></i>
<span>Service Active</span>
</div>
</div>
{{#if deployment.active}}
<div class="connection-details">
<h6>Connection Details</h6>
<div class="detail-item">
<label>IP Address:</label>
<span class="copyable">{{deployment.ip_address}}</span>
</div>
<div class="detail-item">
<label>SSH Access:</label>
<code class="copyable">ssh root@{{deployment.ip_address}}</code>
</div>
{{#if deployment.web_url}}
<div class="detail-item">
<label>Web Interface:</label>
<a href="{{deployment.web_url}}" target="_blank">{{deployment.web_url}}</a>
</div>
{{/if}}
</div>
{{/if}}
<div class="deployment-metrics">
<div class="metric">
<label>TFC Spent:</label>
<span>{{deployment.tfc_amount}} TFC</span>
</div>
<div class="metric">
<label>TFT Converted:</label>
<span>{{deployment.tft_amount}} TFT</span>
</div>
<div class="metric">
<label>Grid Node:</label>
<span>{{deployment.node_id}}</span>
</div>
</div>
</div>
</div>
2. Service Management Controls
// Service Management Functions
class ServiceManager {
constructor(deploymentId) {
this.deploymentId = deploymentId;
this.statusPolling = null;
}
startStatusPolling() {
this.statusPolling = setInterval(async () => {
await this.updateDeploymentStatus();
}, 5000); // Poll every 5 seconds
}
async updateDeploymentStatus() {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/status`);
const deployment = await response.json();
this.updateStatusDisplay(deployment);
// Stop polling if deployment is complete or failed
if (deployment.status === 'Active' || deployment.status === 'Failed') {
this.stopStatusPolling();
}
} catch (error) {
console.error('Failed to update deployment status:', error);
}
}
updateStatusDisplay(deployment) {
// Update progress timeline
document.querySelectorAll('.timeline-step').forEach((step, index) => {
const stepStates = ['payment_completed', 'queued', 'converting', 'deploying', 'active'];
if (deployment[stepStates[index]]) {
step.classList.add('completed');
}
});
// Update status badge
const statusBadge = document.querySelector('.status-badge');
statusBadge.className = `status-badge status-${deployment.status.toLowerCase()}`;
statusBadge.textContent = deployment.status;
// Update connection details if active
if (deployment.status === 'Active' && deployment.connection_info) {
this.showConnectionDetails(deployment.connection_info);
}
}
async restartService() {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/restart`, {
method: 'POST'
});
if (response.ok) {
showSuccessToast('Service restart initiated');
this.startStatusPolling();
} else {
showErrorToast('Failed to restart service');
}
} catch (error) {
showErrorToast('Failed to restart service');
}
}
async stopService() {
if (confirm('Are you sure you want to stop this service? This action cannot be undone.')) {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/stop`, {
method: 'POST'
});
if (response.ok) {
showSuccessToast('Service stopped successfully');
window.location.href = '/dashboard/deployments';
} else {
showErrorToast('Failed to stop service');
}
} catch (error) {
showErrorToast('Failed to stop service');
}
}
}
}
Configuration & Environment
# config/deployment.toml
[deployment_queue]
worker_count = 4
max_queue_size = 1000
retry_attempts = 3
retry_delay_seconds = 30
[threefold_grid]
grid_proxy_url = "https://gridproxy.grid.tf"
substrate_url = "wss://tfchain.grid.tf/ws"
relay_url = "wss://relay.grid.tf"
[tft_conversion]
price_sources = ["dex", "coingecko", "grid_stats"]
conversion_buffer_percent = 2.0
max_slippage_percent = 5.0
[notifications]
email_enabled = true
webhook_enabled = true
slack_enabled = false
This deployment automation specification provides the complete technical foundation for seamless TFC-to-Grid deployment automation, ensuring reliable and scalable service provisioning on the ThreeFold Grid.