```
chore: refactor logging module, update genesis timestamp, remove model relationships, and reorganize routers - Rename logging.py to logger.py and update import paths in poa.py and main.py - Update devnet genesis timestamp to 1766828620 - Remove SQLModel Relationship declarations from Block, Transaction, and Receipt models - Add SessionDep type alias and get_session dependency in coordinator-api deps - Reorganize coordinator-api routers: replace explorer/registry with exchange, users, marketplace
This commit is contained in:
390
docs/tutorials/custom-proposer.md
Normal file
390
docs/tutorials/custom-proposer.md
Normal file
@ -0,0 +1,390 @@
|
||||
# Building Custom Proposers in AITBC
|
||||
|
||||
This tutorial guides you through creating custom proposers for the AITBC blockchain network. Custom proposers allow you to implement specialized block proposal logic tailored to your specific use case.
|
||||
|
||||
## Overview
|
||||
|
||||
In AITBC, proposers are responsible for creating new blocks in the Proof of Authority (PoA) consensus. While the default proposer works for most cases, you might need custom logic for:
|
||||
- Priority-based transaction ordering
|
||||
- Specialized transaction selection
|
||||
- Custom block validation rules
|
||||
- Integration with external systems
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+
|
||||
- AITBC blockchain node running
|
||||
- Understanding of PoA consensus
|
||||
- Development environment set up
|
||||
|
||||
## Step 1: Create a Custom Proposer Class
|
||||
|
||||
Start by creating a new file for your custom proposer:
|
||||
|
||||
```python
|
||||
# custom_proposer.py
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
from aitbc_chain.models import Block, Transaction
|
||||
from aitbc_chain.consensus.base import BaseProposer
|
||||
from aitbc_chain.config import ProposerConfig
|
||||
|
||||
class PriorityProposer(BaseProposer):
|
||||
"""
|
||||
A custom proposer that prioritizes transactions by fee and priority score.
|
||||
"""
|
||||
|
||||
def __init__(self, config: ProposerConfig):
|
||||
super().__init__(config)
|
||||
self.min_priority_score = config.get("min_priority_score", 0)
|
||||
self.max_block_size = config.get("max_block_size", 1000)
|
||||
|
||||
async def select_transactions(
|
||||
self,
|
||||
pending_txs: List[Transaction],
|
||||
current_block: Optional[Block] = None
|
||||
) -> List[Transaction]:
|
||||
"""
|
||||
Select and order transactions based on priority.
|
||||
"""
|
||||
# Filter transactions by minimum priority
|
||||
filtered_txs = [
|
||||
tx for tx in pending_txs
|
||||
if self._calculate_priority(tx) >= self.min_priority_score
|
||||
]
|
||||
|
||||
# Sort by priority (highest first)
|
||||
sorted_txs = sorted(
|
||||
filtered_txs,
|
||||
key=self._calculate_priority,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# Limit block size
|
||||
return sorted_txs[:self.max_block_size]
|
||||
|
||||
def _calculate_priority(self, tx: Transaction) -> int:
|
||||
"""
|
||||
Calculate transaction priority score.
|
||||
"""
|
||||
# Base priority from fee
|
||||
fee_priority = tx.fee or 0
|
||||
|
||||
# Bonus for specific transaction types
|
||||
type_bonus = {
|
||||
"computation": 10,
|
||||
"settlement": 5,
|
||||
"transfer": 1
|
||||
}.get(tx.type, 0)
|
||||
|
||||
# Time-based priority (older transactions get higher priority)
|
||||
age_bonus = max(0, (datetime.utcnow() - tx.timestamp).seconds // 60)
|
||||
|
||||
return fee_priority + type_bonus + age_bonus
|
||||
```
|
||||
|
||||
## Step 2: Implement Custom Block Validation
|
||||
|
||||
Add custom validation logic for your blocks:
|
||||
|
||||
```python
|
||||
# custom_proposer.py (continued)
|
||||
from aitbc_chain.consensus.exceptions import InvalidBlockException
|
||||
|
||||
class PriorityProposer(BaseProposer):
|
||||
# ... previous code ...
|
||||
|
||||
async def validate_block(
|
||||
self,
|
||||
block: Block,
|
||||
parent_block: Optional[Block] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Validate block with custom rules.
|
||||
"""
|
||||
# Run standard validation first
|
||||
if not await super().validate_block(block, parent_block):
|
||||
return False
|
||||
|
||||
# Custom validation: check minimum priority threshold
|
||||
if block.transactions:
|
||||
min_priority = min(
|
||||
self._calculate_priority(tx)
|
||||
for tx in block.transactions
|
||||
)
|
||||
|
||||
if min_priority < self.min_priority_score:
|
||||
raise InvalidBlockException(
|
||||
f"Block contains transactions below priority threshold"
|
||||
)
|
||||
|
||||
# Custom validation: ensure proposer diversity
|
||||
if parent_block and block.proposer == parent_block.proposer:
|
||||
# Allow consecutive blocks only if underutilized
|
||||
utilization = len(block.transactions) / self.max_block_size
|
||||
if utilization > 0.5:
|
||||
raise InvalidBlockException(
|
||||
"Consecutive blocks from same proposer not allowed"
|
||||
)
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
## Step 3: Register Your Custom Proposer
|
||||
|
||||
Register your proposer with the blockchain node:
|
||||
|
||||
```python
|
||||
# node_config.py
|
||||
from custom_proposer import PriorityProposer
|
||||
from aitbc_chain.config import ProposerConfig
|
||||
|
||||
def create_custom_proposer():
|
||||
"""Create and configure the custom proposer."""
|
||||
config = ProposerConfig({
|
||||
"min_priority_score": 5,
|
||||
"max_block_size": 500,
|
||||
"proposer_address": "0xYOUR_PROPOSER_ADDRESS",
|
||||
"signing_key": "YOUR_PRIVATE_KEY"
|
||||
})
|
||||
|
||||
return PriorityProposer(config)
|
||||
|
||||
# In your node initialization
|
||||
proposer = create_custom_proposer()
|
||||
node.set_proposer(proposer)
|
||||
```
|
||||
|
||||
## Step 4: Add Monitoring and Metrics
|
||||
|
||||
Track your proposer's performance:
|
||||
|
||||
```python
|
||||
# custom_proposer.py (continued)
|
||||
from prometheus_client import Counter, Histogram, Gauge
|
||||
|
||||
class PriorityProposer(BaseProposer):
|
||||
# ... previous code ...
|
||||
|
||||
def __init__(self, config: ProposerConfig):
|
||||
super().__init__(config)
|
||||
|
||||
# Metrics
|
||||
self.blocks_proposed = Counter(
|
||||
'blocks_proposed_total',
|
||||
'Total number of blocks proposed',
|
||||
['proposer_type']
|
||||
)
|
||||
self.tx_selected = Histogram(
|
||||
'transactions_selected_per_block',
|
||||
'Number of transactions selected per block'
|
||||
)
|
||||
self.avg_priority = Gauge(
|
||||
'average_transaction_priority',
|
||||
'Average priority of selected transactions'
|
||||
)
|
||||
|
||||
async def propose_block(
|
||||
self,
|
||||
pending_txs: List[Transaction]
|
||||
) -> Optional[Block]:
|
||||
"""
|
||||
Propose a new block with metrics tracking.
|
||||
"""
|
||||
selected_txs = await self.select_transactions(pending_txs)
|
||||
|
||||
if not selected_txs:
|
||||
return None
|
||||
|
||||
# Create block
|
||||
block = await self._create_block(selected_txs)
|
||||
|
||||
# Update metrics
|
||||
self.blocks_proposed.labels(proposer_type='priority').inc()
|
||||
self.tx_selected.observe(len(selected_txs))
|
||||
|
||||
if selected_txs:
|
||||
avg_prio = sum(
|
||||
self._calculate_priority(tx)
|
||||
for tx in selected_txs
|
||||
) / len(selected_txs)
|
||||
self.avg_priority.set(avg_prio)
|
||||
|
||||
return block
|
||||
```
|
||||
|
||||
## Step 5: Test Your Custom Proposer
|
||||
|
||||
Create tests for your proposer:
|
||||
|
||||
```python
|
||||
# test_custom_proposer.py
|
||||
import pytest
|
||||
from custom_proposer import PriorityProposer
|
||||
from aitbc_chain.models import Transaction
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@pytest.fixture
|
||||
def proposer():
|
||||
config = ProposerConfig({
|
||||
"min_priority_score": 5,
|
||||
"max_block_size": 10
|
||||
})
|
||||
return PriorityProposer(config)
|
||||
|
||||
@pytest.fixture
|
||||
def sample_transactions():
|
||||
txs = []
|
||||
for i in range(20):
|
||||
tx = Transaction(
|
||||
id=f"tx_{i}",
|
||||
fee=i * 2,
|
||||
type="computation" if i % 3 == 0 else "transfer",
|
||||
timestamp=datetime.utcnow() - timedelta(minutes=i)
|
||||
)
|
||||
txs.append(tx)
|
||||
return txs
|
||||
|
||||
async def test_transaction_selection(proposer, sample_transactions):
|
||||
"""Test that high-priority transactions are selected."""
|
||||
selected = await proposer.select_transactions(sample_transactions)
|
||||
|
||||
# Should select max_block_size transactions
|
||||
assert len(selected) == 10
|
||||
|
||||
# Should be sorted by priority (highest first)
|
||||
priorities = [proposer._calculate_priority(tx) for tx in selected]
|
||||
assert priorities == sorted(priorities, reverse=True)
|
||||
|
||||
# All should meet minimum priority
|
||||
assert all(p >= 5 for p in priorities)
|
||||
|
||||
async def test_priority_calculation(proposer):
|
||||
"""Test priority calculation logic."""
|
||||
high_fee_tx = Transaction(id="1", fee=100, type="computation")
|
||||
low_fee_tx = Transaction(id="2", fee=1, type="transfer")
|
||||
|
||||
high_priority = proposer._calculate_priority(high_fee_tx)
|
||||
low_priority = proposer._calculate_priority(low_fee_tx)
|
||||
|
||||
assert high_priority > low_priority
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### 1. Dynamic Priority Adjustment
|
||||
|
||||
```python
|
||||
class AdaptiveProposer(PriorityProposer):
|
||||
"""Proposer that adjusts priority based on network conditions."""
|
||||
|
||||
async def adjust_priority_threshold(self):
|
||||
"""Dynamically adjust minimum priority based on pending transactions."""
|
||||
pending_count = await self.get_pending_transaction_count()
|
||||
|
||||
if pending_count > 1000:
|
||||
self.min_priority_score = 10 # Increase threshold
|
||||
elif pending_count < 100:
|
||||
self.min_priority_score = 1 # Lower threshold
|
||||
```
|
||||
|
||||
### 2. MEV Protection
|
||||
|
||||
```python
|
||||
class MEVProtectedProposer(PriorityProposer):
|
||||
"""Proposer with MEV (Maximum Extractable Value) protection."""
|
||||
|
||||
async def select_transactions(self, pending_txs):
|
||||
"""Select transactions while preventing MEV extraction."""
|
||||
# Group related transactions
|
||||
tx_groups = self._group_related_transactions(pending_txs)
|
||||
|
||||
# Process groups atomically
|
||||
selected = []
|
||||
for group in tx_groups:
|
||||
if self._validate_mev_safety(group):
|
||||
selected.extend(group)
|
||||
|
||||
return selected[:self.max_block_size]
|
||||
```
|
||||
|
||||
### 3. Cross-Shard Coordination
|
||||
|
||||
```python
|
||||
class ShardAwareProposer(BaseProposer):
|
||||
"""Proposer that coordinates across multiple shards."""
|
||||
|
||||
async def coordinate_with_shards(self, block):
|
||||
"""Coordinate block proposal with other shards."""
|
||||
# Get cross-shard dependencies
|
||||
dependencies = await self.get_cross_shard_deps(block.transactions)
|
||||
|
||||
# Wait for confirmations from other shards
|
||||
await self.wait_for_shard_confirmations(dependencies)
|
||||
|
||||
return block
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
1. **Package your proposer**:
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
2. **Update node configuration**:
|
||||
```yaml
|
||||
# config.yaml
|
||||
proposer:
|
||||
type: custom
|
||||
module: my_proposers.PriorityProposer
|
||||
config:
|
||||
min_priority_score: 5
|
||||
max_block_size: 500
|
||||
```
|
||||
|
||||
3. **Restart the node**:
|
||||
```bash
|
||||
sudo systemctl restart aitbc-node
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor your proposer's performance with Grafana dashboards:
|
||||
- Block proposal rate
|
||||
- Transaction selection efficiency
|
||||
- Average priority scores
|
||||
- MEV protection metrics
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep proposers simple** - Complex logic can cause delays
|
||||
2. **Test thoroughly** - Use testnet before mainnet deployment
|
||||
3. **Monitor performance** - Track metrics and optimize
|
||||
4. **Handle edge cases** - Empty blocks, network partitions
|
||||
5. **Document behavior** - Clear documentation for custom logic
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Blocks not being proposed**
|
||||
- Check proposer registration
|
||||
- Verify signing key
|
||||
- Review logs for errors
|
||||
|
||||
2. **Low transaction throughput**
|
||||
- Adjust priority thresholds
|
||||
- Check block size limits
|
||||
- Optimize selection logic
|
||||
|
||||
3. **Invalid blocks**
|
||||
- Review validation rules
|
||||
- Check transaction ordering
|
||||
- Verify signatures
|
||||
|
||||
## Conclusion
|
||||
|
||||
Custom proposers give you fine-grained control over block production in AITBC. This tutorial covered the basics of creating, testing, and deploying custom proposers. You can now extend these examples to build sophisticated consensus mechanisms tailored to your specific needs.
|
||||
|
||||
For more advanced examples and community contributions, visit the AITBC GitHub repository.
|
||||
45
docs/tutorials/index.md
Normal file
45
docs/tutorials/index.md
Normal file
@ -0,0 +1,45 @@
|
||||
# AITBC Tutorials
|
||||
|
||||
Welcome to the AITBC tutorials section. Here you'll find comprehensive guides for building on the AITBC platform.
|
||||
|
||||
## Core Platform Tutorials
|
||||
|
||||
### [Building Custom Proposers](custom-proposer.md)
|
||||
Learn how to create custom block proposers for specialized consensus logic in AITBC's Proof of Authority system.
|
||||
|
||||
### [Marketplace Extensions](marketplace-extensions.md)
|
||||
Discover how to extend the AITBC marketplace with custom auction types, service categories, and integrations.
|
||||
|
||||
## Infrastructure Tutorials
|
||||
|
||||
### [Mining Setup](mining-setup.md)
|
||||
Set up your GPU miners to participate in the AITBC network and earn rewards.
|
||||
|
||||
### [Running a Node](running-node.md)
|
||||
Deploy and operate your own AITBC blockchain node.
|
||||
|
||||
## Development Tutorials
|
||||
|
||||
### [Building a DApp](building-dapp.md)
|
||||
Create decentralized applications on top of the AITBC platform.
|
||||
|
||||
### [Integration Examples](integration-examples.md)
|
||||
Real-world examples of integrating AITBC services into your applications.
|
||||
|
||||
## Advanced Tutorials
|
||||
|
||||
### [Zero-Knowledge Applications](../zk-applications.md)
|
||||
Implement privacy-preserving features using ZK-SNARKs.
|
||||
|
||||
### [Cross-Chain Integration](../developer/cross-chain.md)
|
||||
Connect AITBC with other blockchain networks.
|
||||
|
||||
## Contributing
|
||||
|
||||
Have a tutorial idea? We'd love your contributions! Check out our [contributing guide](../developer/contributing.md) to get started.
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Join our [Discord community](https://discord.gg/aitbc)
|
||||
- Browse our [FAQ](../resources/faq.md)
|
||||
- Contact [support](../resources/support.md)
|
||||
631
docs/tutorials/marketplace-extensions.md
Normal file
631
docs/tutorials/marketplace-extensions.md
Normal file
@ -0,0 +1,631 @@
|
||||
# Building Marketplace Extensions in AITBC
|
||||
|
||||
This tutorial shows how to extend the AITBC marketplace with custom features, plugins, and integrations.
|
||||
|
||||
## Overview
|
||||
|
||||
The AITBC marketplace is designed to be extensible. You can add:
|
||||
- Custom auction types
|
||||
- Specialized service categories
|
||||
- Advanced filtering and search
|
||||
- Integration with external systems
|
||||
- Custom pricing models
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 16+
|
||||
- AITBC marketplace source code
|
||||
- Understanding of React/TypeScript
|
||||
- API development experience
|
||||
|
||||
## Step 1: Create a Custom Auction Type
|
||||
|
||||
Let's create a Dutch auction extension:
|
||||
|
||||
```typescript
|
||||
// src/extensions/DutchAuction.ts
|
||||
import { Auction, Bid, MarketplacePlugin } from '../types';
|
||||
|
||||
interface DutchAuctionConfig {
|
||||
startPrice: number;
|
||||
reservePrice: number;
|
||||
decrementRate: number;
|
||||
decrementInterval: number; // in seconds
|
||||
}
|
||||
|
||||
export class DutchAuction implements MarketplacePlugin {
|
||||
config: DutchAuctionConfig;
|
||||
currentPrice: number;
|
||||
lastDecrement: number;
|
||||
|
||||
constructor(config: DutchAuctionConfig) {
|
||||
this.config = config;
|
||||
this.currentPrice = config.startPrice;
|
||||
this.lastDecrement = Date.now();
|
||||
}
|
||||
|
||||
async updatePrice(): Promise<void> {
|
||||
const now = Date.now();
|
||||
const elapsed = (now - this.lastDecrement) / 1000;
|
||||
|
||||
if (elapsed >= this.config.decrementInterval) {
|
||||
const decrements = Math.floor(elapsed / this.config.decrementInterval);
|
||||
this.currentPrice = Math.max(
|
||||
this.config.reservePrice,
|
||||
this.currentPrice - (decrements * this.config.decrementRate)
|
||||
);
|
||||
this.lastDecrement = now;
|
||||
}
|
||||
}
|
||||
|
||||
async validateBid(bid: Bid): Promise<boolean> {
|
||||
await this.updatePrice();
|
||||
return bid.amount >= this.currentPrice;
|
||||
}
|
||||
|
||||
async getCurrentState(): Promise<any> {
|
||||
await this.updatePrice();
|
||||
return {
|
||||
type: 'dutch',
|
||||
currentPrice: this.currentPrice,
|
||||
nextDecrement: this.config.decrementInterval -
|
||||
((Date.now() - this.lastDecrement) / 1000)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Register the Extension
|
||||
|
||||
```typescript
|
||||
// src/extensions/index.ts
|
||||
import { DutchAuction } from './DutchAuction';
|
||||
import { MarketplaceRegistry } from '../core/MarketplaceRegistry';
|
||||
|
||||
const registry = new MarketplaceRegistry();
|
||||
|
||||
// Register Dutch auction
|
||||
registry.registerAuctionType('dutch', DutchAuction, {
|
||||
defaultConfig: {
|
||||
startPrice: 1000,
|
||||
reservePrice: 100,
|
||||
decrementRate: 10,
|
||||
decrementInterval: 60
|
||||
},
|
||||
validation: {
|
||||
startPrice: { type: 'number', min: 0 },
|
||||
reservePrice: { type: 'number', min: 0 },
|
||||
decrementRate: { type: 'number', min: 0 },
|
||||
decrementInterval: { type: 'number', min: 1 }
|
||||
}
|
||||
});
|
||||
|
||||
export default registry;
|
||||
```
|
||||
|
||||
## Step 3: Create UI Components
|
||||
|
||||
```tsx
|
||||
// src/components/DutchAuctionCard.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Button, Progress, Typography } from 'antd';
|
||||
import { useMarketplace } from '../hooks/useMarketplace';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface DutchAuctionCardProps {
|
||||
auction: any;
|
||||
}
|
||||
|
||||
export const DutchAuctionCard: React.FC<DutchAuctionCardProps> = ({ auction }) => {
|
||||
const [currentState, setCurrentState] = useState<any>(null);
|
||||
const [timeLeft, setTimeLeft] = useState<number>(0);
|
||||
const { placeBid } = useMarketplace();
|
||||
|
||||
useEffect(() => {
|
||||
const updateState = async () => {
|
||||
const state = await auction.getCurrentState();
|
||||
setCurrentState(state);
|
||||
setTimeLeft(state.nextDecrement);
|
||||
};
|
||||
|
||||
updateState();
|
||||
const interval = setInterval(updateState, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [auction]);
|
||||
|
||||
const handleBid = async () => {
|
||||
try {
|
||||
await placeBid(auction.id, currentState.currentPrice);
|
||||
} catch (error) {
|
||||
console.error('Bid failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!currentState) return <div>Loading...</div>;
|
||||
|
||||
const priceProgress =
|
||||
((currentState.currentPrice - auction.config.reservePrice) /
|
||||
(auction.config.startPrice - auction.config.reservePrice)) * 100;
|
||||
|
||||
return (
|
||||
<Card title={auction.title} extra={`Auction #${auction.id}`}>
|
||||
<div className="mb-4">
|
||||
<Title level={4}>Current Price: {currentState.currentPrice} AITBC</Title>
|
||||
<Progress
|
||||
percent={100 - priceProgress}
|
||||
status="active"
|
||||
format={() => `${timeLeft}s until next drop`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<Text type="secondary">
|
||||
Reserve Price: {auction.config.reservePrice} AITBC
|
||||
</Text>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleBid}
|
||||
disabled={currentState.currentPrice <= auction.config.reservePrice}
|
||||
>
|
||||
Buy Now
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Step 4: Add Backend API Support
|
||||
|
||||
```python
|
||||
# apps/coordinator-api/src/app/routers/marketplace_extensions.py
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any, List
|
||||
import asyncio
|
||||
|
||||
router = APIRouter(prefix="/marketplace/extensions", tags=["marketplace-extensions"])
|
||||
|
||||
class DutchAuctionRequest(BaseModel):
|
||||
title: str
|
||||
description: str
|
||||
start_price: float
|
||||
reserve_price: float
|
||||
decrement_rate: float
|
||||
decrement_interval: int
|
||||
|
||||
class DutchAuctionState(BaseModel):
|
||||
auction_id: str
|
||||
current_price: float
|
||||
next_decrement: int
|
||||
total_bids: int
|
||||
|
||||
@router.post("/dutch-auction/create")
|
||||
async def create_dutch_auction(request: DutchAuctionRequest) -> Dict[str, str]:
|
||||
"""Create a new Dutch auction."""
|
||||
|
||||
# Validate auction parameters
|
||||
if request.reserve_price >= request.start_price:
|
||||
raise HTTPException(400, "Reserve price must be less than start price")
|
||||
|
||||
# Create auction in database
|
||||
auction_id = await marketplace_service.create_extension_auction(
|
||||
type="dutch",
|
||||
config=request.dict()
|
||||
)
|
||||
|
||||
# Start price decrement task
|
||||
asyncio.create_task(start_price_decrement(auction_id))
|
||||
|
||||
return {"auction_id": auction_id}
|
||||
|
||||
@router.get("/dutch-auction/{auction_id}/state")
|
||||
async def get_dutch_auction_state(auction_id: str) -> DutchAuctionState:
|
||||
"""Get current state of a Dutch auction."""
|
||||
|
||||
auction = await marketplace_service.get_auction(auction_id)
|
||||
if not auction or auction.type != "dutch":
|
||||
raise HTTPException(404, "Dutch auction not found")
|
||||
|
||||
current_price = calculate_current_price(auction)
|
||||
next_decrement = calculate_next_decrement(auction)
|
||||
|
||||
return DutchAuctionState(
|
||||
auction_id=auction_id,
|
||||
current_price=current_price,
|
||||
next_decrement=next_decrement,
|
||||
total_bids=auction.bid_count
|
||||
)
|
||||
|
||||
async def start_price_decrement(auction_id: str):
|
||||
"""Background task to decrement auction price."""
|
||||
while True:
|
||||
await asyncio.sleep(60) # Check every minute
|
||||
|
||||
auction = await marketplace_service.get_auction(auction_id)
|
||||
if not auction or auction.status != "active":
|
||||
break
|
||||
|
||||
new_price = calculate_current_price(auction)
|
||||
await marketplace_service.update_auction_price(auction_id, new_price)
|
||||
|
||||
if new_price <= auction.config["reserve_price"]:
|
||||
await marketplace_service.close_auction(auction_id)
|
||||
break
|
||||
```
|
||||
|
||||
## Step 5: Add Custom Service Category
|
||||
|
||||
```typescript
|
||||
// src/extensions/ServiceCategories.ts
|
||||
export interface ServiceCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
requirements: ServiceRequirement[];
|
||||
pricing: PricingModel;
|
||||
}
|
||||
|
||||
export interface ServiceRequirement {
|
||||
type: 'gpu' | 'cpu' | 'memory' | 'storage';
|
||||
minimum: number;
|
||||
recommended: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface PricingModel {
|
||||
type: 'fixed' | 'hourly' | 'per-unit';
|
||||
basePrice: number;
|
||||
unitPrice?: number;
|
||||
}
|
||||
|
||||
export const AI_INFERENCE_CATEGORY: ServiceCategory = {
|
||||
id: 'ai-inference',
|
||||
name: 'AI Inference',
|
||||
icon: 'brain',
|
||||
description: 'Large language model and neural network inference',
|
||||
requirements: [
|
||||
{ type: 'gpu', minimum: 8, recommended: 24, unit: 'GB' },
|
||||
{ type: 'memory', minimum: 16, recommended: 64, unit: 'GB' },
|
||||
{ type: 'cpu', minimum: 4, recommended: 16, unit: 'cores' }
|
||||
],
|
||||
pricing: {
|
||||
type: 'hourly',
|
||||
basePrice: 10,
|
||||
unitPrice: 0.5
|
||||
}
|
||||
};
|
||||
|
||||
// Category registry
|
||||
export const SERVICE_CATEGORIES: Record<string, ServiceCategory> = {
|
||||
'ai-inference': AI_INFERENCE_CATEGORY,
|
||||
'video-rendering': {
|
||||
id: 'video-rendering',
|
||||
name: 'Video Rendering',
|
||||
icon: 'video',
|
||||
description: 'High-quality video rendering and encoding',
|
||||
requirements: [
|
||||
{ type: 'gpu', minimum: 12, recommended: 24, unit: 'GB' },
|
||||
{ type: 'memory', minimum: 32, recommended: 128, unit: 'GB' },
|
||||
{ type: 'storage', minimum: 100, recommended: 1000, unit: 'GB' }
|
||||
],
|
||||
pricing: {
|
||||
type: 'per-unit',
|
||||
basePrice: 5,
|
||||
unitPrice: 0.1
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Step 6: Create Advanced Search Filters
|
||||
|
||||
```tsx
|
||||
// src/components/AdvancedSearch.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { Select, Slider, Input, Button, Space } from 'antd';
|
||||
import { SERVICE_CATEGORIES } from '../extensions/ServiceCategories';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Search } = Input;
|
||||
|
||||
interface SearchFilters {
|
||||
category?: string;
|
||||
priceRange: [number, number];
|
||||
gpuMemory: [number, number];
|
||||
providerRating: number;
|
||||
}
|
||||
|
||||
export const AdvancedSearch: React.FC<{
|
||||
onSearch: (filters: SearchFilters) => void;
|
||||
}> = ({ onSearch }) => {
|
||||
const [filters, setFilters] = useState<SearchFilters>({
|
||||
priceRange: [0, 1000],
|
||||
gpuMemory: [0, 24],
|
||||
providerRating: 0
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
onSearch(filters);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<Space direction="vertical" className="w-full">
|
||||
<Search
|
||||
placeholder="Search services..."
|
||||
onSearch={(value) => setFilters({ ...filters, query: value })}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
|
||||
<Select
|
||||
placeholder="Select category"
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => setFilters({ ...filters, category: value })}
|
||||
allowClear
|
||||
>
|
||||
{Object.values(SERVICE_CATEGORIES).map(category => (
|
||||
<Option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<div>
|
||||
<label>Price Range: {filters.priceRange[0]} - {filters.priceRange[1]} AITBC</label>
|
||||
<Slider
|
||||
range
|
||||
min={0}
|
||||
max={1000}
|
||||
value={filters.priceRange}
|
||||
onChange={(value) => setFilters({ ...filters, priceRange: value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>GPU Memory: {filters.gpuMemory[0]} - {filters.gpuMemory[1]} GB</label>
|
||||
<Slider
|
||||
range
|
||||
min={0}
|
||||
max={24}
|
||||
value={filters.gpuMemory}
|
||||
onChange={(value) => setFilters({ ...filters, gpuMemory: value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Minimum Provider Rating: {filters.providerRating} stars</label>
|
||||
<Slider
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.5}
|
||||
value={filters.providerRating}
|
||||
onChange={(value) => setFilters({ ...filters, providerRating: value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="primary" onClick={handleSearch} block>
|
||||
Apply Filters
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Step 7: Add Integration with External Systems
|
||||
|
||||
```python
|
||||
# apps/coordinator-api/src/app/integrations/slack.py
|
||||
import httpx
|
||||
from typing import Dict, Any
|
||||
|
||||
class SlackIntegration:
|
||||
def __init__(self, webhook_url: str):
|
||||
self.webhook_url = webhook_url
|
||||
|
||||
async def notify_new_auction(self, auction: Dict[str, Any]) -> None:
|
||||
"""Send notification about new auction to Slack."""
|
||||
message = {
|
||||
"text": f"New auction created: {auction['title']}",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": f"*New Auction Alert*\n\n*Title:* {auction['title']}\n"
|
||||
f"*Starting Price:* {auction['start_price']} AITBC\n"
|
||||
f"*Category:* {auction.get('category', 'General')}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "actions",
|
||||
"elements": [
|
||||
{
|
||||
"type": "button",
|
||||
"text": {"type": "plain_text", "text": "View Auction"},
|
||||
"url": f"https://aitbc.bubuit.net/marketplace/auction/{auction['id']}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(self.webhook_url, json=message)
|
||||
|
||||
async def notify_bid_placed(self, auction_id: str, bid_amount: float) -> None:
|
||||
"""Notify when a bid is placed."""
|
||||
message = {
|
||||
"text": f"New bid of {bid_amount} AITBC placed on auction {auction_id}"
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(self.webhook_url, json=message)
|
||||
|
||||
# Integration with Discord
|
||||
class DiscordIntegration:
|
||||
def __init__(self, webhook_url: str):
|
||||
self.webhook_url = webhook_url
|
||||
|
||||
async def send_embed(self, title: str, description: str, fields: list) -> None:
|
||||
"""Send rich embed message to Discord."""
|
||||
embed = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"fields": fields,
|
||||
"color": 0x00ff00
|
||||
}
|
||||
|
||||
payload = {"embeds": [embed]}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(self.webhook_url, json=payload)
|
||||
```
|
||||
|
||||
## Step 8: Create Custom Pricing Model
|
||||
|
||||
```typescript
|
||||
// src/extensions/DynamicPricing.ts
|
||||
export interface PricingRule {
|
||||
condition: (context: PricingContext) => boolean;
|
||||
calculate: (basePrice: number, context: PricingContext) => number;
|
||||
}
|
||||
|
||||
export interface PricingContext {
|
||||
demand: number;
|
||||
supply: number;
|
||||
timeOfDay: number;
|
||||
dayOfWeek: number;
|
||||
providerRating: number;
|
||||
serviceCategory: string;
|
||||
}
|
||||
|
||||
export class DynamicPricingEngine {
|
||||
private rules: PricingRule[] = [];
|
||||
|
||||
addRule(rule: PricingRule) {
|
||||
this.rules.push(rule);
|
||||
}
|
||||
|
||||
calculatePrice(basePrice: number, context: PricingContext): number {
|
||||
let finalPrice = basePrice;
|
||||
|
||||
for (const rule of this.rules) {
|
||||
if (rule.condition(context)) {
|
||||
finalPrice = rule.calculate(finalPrice, context);
|
||||
}
|
||||
}
|
||||
|
||||
return Math.round(finalPrice * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Example pricing rules
|
||||
export const DEMAND_SURGE_RULE: PricingRule = {
|
||||
condition: (ctx) => ctx.demand / ctx.supply > 2,
|
||||
calculate: (price) => price * 1.5, // 50% surge
|
||||
};
|
||||
|
||||
export const PEAK_HOURS_RULE: PricingRule = {
|
||||
condition: (ctx) => ctx.timeOfDay >= 9 && ctx.timeOfDay <= 17,
|
||||
calculate: (price) => price * 1.2, // 20% peak hour premium
|
||||
};
|
||||
|
||||
export const TOP_PROVIDER_RULE: PricingRule = {
|
||||
condition: (ctx) => ctx.providerRating >= 4.5,
|
||||
calculate: (price) => price * 1.1, // 10% premium for top providers
|
||||
};
|
||||
|
||||
// Usage
|
||||
const pricingEngine = new DynamicPricingEngine();
|
||||
pricingEngine.addRule(DEMAND_SURGE_RULE);
|
||||
pricingEngine.addRule(PEAK_HOURS_RULE);
|
||||
pricingEngine.addRule(TOP_PROVIDER_RULE);
|
||||
|
||||
const finalPrice = pricingEngine.calculatePrice(100, {
|
||||
demand: 100,
|
||||
supply: 30,
|
||||
timeOfDay: 14,
|
||||
dayOfWeek: 2,
|
||||
providerRating: 4.8,
|
||||
serviceCategory: 'ai-inference'
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Your Extensions
|
||||
|
||||
```typescript
|
||||
// src/extensions/__tests__/DutchAuction.test.ts
|
||||
import { DutchAuction } from '../DutchAuction';
|
||||
|
||||
describe('DutchAuction', () => {
|
||||
let auction: DutchAuction;
|
||||
|
||||
beforeEach(() => {
|
||||
auction = new DutchAuction({
|
||||
startPrice: 1000,
|
||||
reservePrice: 100,
|
||||
decrementRate: 10,
|
||||
decrementInterval: 60
|
||||
});
|
||||
});
|
||||
|
||||
test('should start with initial price', () => {
|
||||
expect(auction.currentPrice).toBe(1000);
|
||||
});
|
||||
|
||||
test('should decrement price after interval', async () => {
|
||||
// Mock time passing
|
||||
jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 60000);
|
||||
|
||||
await auction.updatePrice();
|
||||
expect(auction.currentPrice).toBe(990);
|
||||
});
|
||||
|
||||
test('should not go below reserve price', async () => {
|
||||
// Mock significant time passing
|
||||
jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 600000);
|
||||
|
||||
await auction.updatePrice();
|
||||
expect(auction.currentPrice).toBe(100);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
1. **Build your extensions**:
|
||||
```bash
|
||||
npm run build:extensions
|
||||
```
|
||||
|
||||
2. **Deploy to production**:
|
||||
```bash
|
||||
# Copy extension files
|
||||
cp -r src/extensions/* /var/www/aitbc.bubuit.net/marketplace/extensions/
|
||||
|
||||
# Update API
|
||||
scp apps/coordinator-api/src/app/routers/marketplace_extensions.py \
|
||||
aitbc:/opt/coordinator-api/src/app/routers/
|
||||
|
||||
# Restart services
|
||||
ssh aitbc "sudo systemctl restart coordinator-api"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Modular Design** - Keep extensions independent
|
||||
2. **Backward Compatibility** - Ensure extensions work with existing marketplace
|
||||
3. **Performance** - Optimize for high-frequency operations
|
||||
4. **Security** - Validate all inputs and permissions
|
||||
5. **Documentation** - Document extension APIs and usage
|
||||
|
||||
## Conclusion
|
||||
|
||||
This tutorial covered creating marketplace extensions including custom auction types, service categories, advanced search, and external integrations. You can now build powerful extensions to enhance the AITBC marketplace functionality.
|
||||
|
||||
For more examples and community contributions, visit the marketplace extensions repository.
|
||||
Reference in New Issue
Block a user