17 KiB
17 KiB
Интеграция с Optovia Platform
Руководство по интеграции блокчейна с основной платформой.
Архитектура интеграции
┌─────────────────────────────────────────────────────────────┐
│ Optovia Platform │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Odoo │─────▶│ Backend │─────▶│ Blockchain │ │
│ │ │ │ Service │ │ Service │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Database │ │ Besu │ │
│ │ (Postgres) │ │ Nodes │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Python Integration
1. Установка зависимостей
pip install web3 eth-account python-dotenv
2. Blockchain Service
Создайте backends/blockchain_service.py:
import os
import json
from typing import Dict, Optional, List
from web3 import Web3
from web3.middleware import geth_poa_middleware
from eth_account import Account
from dotenv import load_dotenv
load_dotenv()
class BlockchainService:
"""Service for interacting with Optovia blockchain"""
def __init__(self):
# Initialize Web3
self.w3 = Web3(Web3.HTTPProvider(os.getenv('BLOCKCHAIN_RPC_URL', 'http://localhost:8545')))
# Add PoA middleware for private networks
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
# Load account from private key
private_key = os.getenv('BLOCKCHAIN_PRIVATE_KEY')
if private_key:
self.account = Account.from_key(private_key)
self.w3.eth.default_account = self.account.address
else:
raise ValueError("BLOCKCHAIN_PRIVATE_KEY not set")
# Load contract addresses and ABIs
self._load_contracts()
def _load_contracts(self):
"""Load contract addresses and ABIs"""
# Load deployment info
deployment_path = os.getenv(
'BLOCKCHAIN_DEPLOYMENT_FILE',
'blockchain/deployments/optovia-private.json'
)
with open(deployment_path) as f:
deployment = json.load(f)
# Load ShipmentTracker
self.shipment_tracker_address = deployment['contracts']['ShipmentTracker']['address']
with open('blockchain/artifacts/contracts/ShipmentTracker.sol/ShipmentTracker.json') as f:
abi = json.load(f)['abi']
self.shipment_tracker = self.w3.eth.contract(
address=self.shipment_tracker_address,
abi=abi
)
# Load ShipmentNFT
self.shipment_nft_address = deployment['contracts']['ShipmentNFT']['address']
with open('blockchain/artifacts/contracts/ShipmentNFT.sol/ShipmentNFT.json') as f:
abi = json.load(f)['abi']
self.shipment_nft = self.w3.eth.contract(
address=self.shipment_nft_address,
abi=abi
)
def create_shipment(
self,
external_id: str,
manufacturer_address: str,
manufacturer_name: str,
manufacturer_company_id: str,
buyer_address: str,
buyer_name: str,
buyer_company_id: str,
data_dict: Dict
) -> Dict:
"""
Create a shipment on blockchain
Args:
external_id: External reference ID (from Odoo)
manufacturer_address: Manufacturer wallet address
manufacturer_name: Manufacturer name
manufacturer_company_id: Manufacturer company ID
buyer_address: Buyer wallet address
buyer_name: Buyer name
buyer_company_id: Buyer company ID
data_dict: Dictionary with shipment data to hash
Returns:
Dict with transaction hash and shipment ID
"""
# Create data hash
data_str = json.dumps(data_dict, sort_keys=True)
data_hash = self.w3.keccak(text=data_str)
# Build transaction
tx = self.shipment_tracker.functions.createShipment(
external_id,
manufacturer_address,
manufacturer_name,
manufacturer_company_id,
buyer_address,
buyer_name,
buyer_company_id,
data_hash
).build_transaction({
'from': self.account.address,
'nonce': self.w3.eth.get_transaction_count(self.account.address),
'gas': 500000,
'gasPrice': self.w3.eth.gas_price
})
# Sign and send transaction
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
# Wait for receipt
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
# Get shipment ID from event
shipment_id = self.shipment_tracker.functions.getShipmentByExternalId(external_id).call()
return {
'tx_hash': tx_hash.hex(),
'shipment_id': shipment_id,
'block_number': receipt['blockNumber'],
'status': 'success' if receipt['status'] == 1 else 'failed'
}
def update_logistics_info(
self,
shipment_id: int,
logistics_address: str,
logistics_name: str,
logistics_company_id: str,
data_dict: Dict
) -> Dict:
"""Update logistics information for a shipment"""
data_hash = self.w3.keccak(text=json.dumps(data_dict, sort_keys=True))
tx = self.shipment_tracker.functions.updateLogisticsInfo(
shipment_id,
logistics_address,
logistics_name,
logistics_company_id,
data_hash
).build_transaction({
'from': self.account.address,
'nonce': self.w3.eth.get_transaction_count(self.account.address),
'gas': 300000,
'gasPrice': self.w3.eth.gas_price
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return {
'tx_hash': tx_hash.hex(),
'status': 'success' if receipt['status'] == 1 else 'failed'
}
def update_shipment_status(self, shipment_id: int, status: int) -> Dict:
"""
Update shipment status
Status values:
0 - Created
1 - ManufacturerConfirmed
2 - InTransit
3 - Delivered
4 - Cancelled
"""
tx = self.shipment_tracker.functions.updateShipmentStatus(
shipment_id,
status
).build_transaction({
'from': self.account.address,
'nonce': self.w3.eth.get_transaction_count(self.account.address),
'gas': 200000,
'gasPrice': self.w3.eth.gas_price
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return {
'tx_hash': tx_hash.hex(),
'status': 'success' if receipt['status'] == 1 else 'failed'
}
def add_document(self, shipment_id: int, document_hash: str) -> Dict:
"""Add a document (IPFS hash) to a shipment"""
tx = self.shipment_tracker.functions.addDocument(
shipment_id,
document_hash
).build_transaction({
'from': self.account.address,
'nonce': self.w3.eth.get_transaction_count(self.account.address),
'gas': 200000,
'gasPrice': self.w3.eth.gas_price
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return {
'tx_hash': tx_hash.hex(),
'status': 'success' if receipt['status'] == 1 else 'failed'
}
def get_shipment(self, shipment_id: int) -> Dict:
"""Get shipment details from blockchain"""
result = self.shipment_tracker.functions.getShipment(shipment_id).call()
return {
'id': result[0],
'external_id': result[1],
'status': result[2],
'manufacturer': {
'address': result[3][0],
'name': result[3][1],
'company_id': result[3][2],
'timestamp': result[3][3],
'data_hash': result[3][4].hex()
},
'buyer': {
'address': result[4][0],
'name': result[4][1],
'company_id': result[4][2],
'timestamp': result[4][3],
'data_hash': result[4][4].hex()
},
'logistics': {
'address': result[5][0],
'name': result[5][1],
'company_id': result[5][2],
'timestamp': result[5][3],
'data_hash': result[5][4].hex()
},
'created_at': result[6],
'updated_at': result[7],
'data_hash': result[8].hex()
}
def get_shipment_by_external_id(self, external_id: str) -> Optional[int]:
"""Get shipment ID by external ID"""
try:
return self.shipment_tracker.functions.getShipmentByExternalId(external_id).call()
except Exception:
return None
def mint_nft_certificate(
self,
recipient_address: str,
shipment_external_id: str,
manufacturer_address: str,
buyer_address: str,
logistics_address: str,
data_hash: bytes,
private_tx_hash: str,
token_uri: str
) -> Dict:
"""Mint NFT certificate on public chain"""
tx = self.shipment_nft.functions.mintShipmentCertificate(
recipient_address,
shipment_external_id,
manufacturer_address,
buyer_address,
logistics_address,
data_hash,
private_tx_hash,
token_uri
).build_transaction({
'from': self.account.address,
'nonce': self.w3.eth.get_transaction_count(self.account.address),
'gas': 500000,
'gasPrice': self.w3.eth.gas_price
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return {
'tx_hash': tx_hash.hex(),
'status': 'success' if receipt['status'] == 1 else 'failed',
'block_number': receipt['blockNumber']
}
3. Django Integration
Создайте Django app для блокчейна:
cd backends/odoo
python manage.py startapp blockchain
В backends/odoo/blockchain/models.py:
from django.db import models
class BlockchainShipment(models.Model):
"""Track blockchain shipments"""
STATUS_CHOICES = [
(0, 'Created'),
(1, 'Manufacturer Confirmed'),
(2, 'In Transit'),
(3, 'Delivered'),
(4, 'Cancelled'),
]
# Odoo reference
odoo_shipment_id = models.CharField(max_length=100, unique=True)
# Blockchain data
blockchain_id = models.BigIntegerField(null=True, blank=True)
tx_hash = models.CharField(max_length=66, blank=True)
block_number = models.BigIntegerField(null=True, blank=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=0)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
synced_at = models.DateTimeField(null=True, blank=True)
# NFT data (if minted on public chain)
nft_minted = models.BooleanField(default=False)
nft_tx_hash = models.CharField(max_length=66, blank=True)
nft_token_id = models.BigIntegerField(null=True, blank=True)
class Meta:
db_table = 'blockchain_shipment'
ordering = ['-created_at']
def __str__(self):
return f"Shipment {self.odoo_shipment_id} (Blockchain ID: {self.blockchain_id})"
4. API Endpoint
В backends/odoo/blockchain/views.py:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import BlockchainShipment
from blockchain_service import BlockchainService
@api_view(['POST'])
def create_blockchain_shipment(request):
"""Create shipment on blockchain"""
blockchain_service = BlockchainService()
try:
result = blockchain_service.create_shipment(
external_id=request.data['external_id'],
manufacturer_address=request.data['manufacturer_address'],
manufacturer_name=request.data['manufacturer_name'],
manufacturer_company_id=request.data['manufacturer_company_id'],
buyer_address=request.data['buyer_address'],
buyer_name=request.data['buyer_name'],
buyer_company_id=request.data['buyer_company_id'],
data_dict=request.data.get('metadata', {})
)
# Save to database
BlockchainShipment.objects.create(
odoo_shipment_id=request.data['external_id'],
blockchain_id=result['shipment_id'],
tx_hash=result['tx_hash'],
block_number=result['block_number'],
status=0
)
return Response(result, status=status.HTTP_201_CREATED)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
Docker Integration
Добавьте в основной docker-compose.yml:
services:
# ... existing services
backend:
# ... existing config
environment:
- BLOCKCHAIN_RPC_URL=http://besu-node1:8545
- BLOCKCHAIN_CHAIN_ID=1337
- BLOCKCHAIN_PRIVATE_KEY=${BLOCKCHAIN_PRIVATE_KEY}
- BLOCKCHAIN_DEPLOYMENT_FILE=/app/blockchain/deployments/optovia-private.json
volumes:
- ./blockchain:/app/blockchain:ro
networks:
- default
- optovia-blockchain
networks:
optovia-blockchain:
external: true
Environment Variables
Добавьте в Infisical или .env:
# Blockchain Configuration
BLOCKCHAIN_RPC_URL=http://besu-node1:8545
BLOCKCHAIN_CHAIN_ID=1337
BLOCKCHAIN_PRIVATE_KEY=<secure_private_key>
BLOCKCHAIN_DEPLOYMENT_FILE=blockchain/deployments/optovia-private.json
# Public Chain (for NFT minting)
POLYGON_RPC_URL=https://polygon-rpc.com
POLYGON_CHAIN_ID=137
Workflow Example
1. Создание отгрузки в Odoo
# В Odoo модуле
shipment = create_shipment_in_odoo(...)
# Отправить в блокчейн
blockchain_result = blockchain_service.create_shipment(
external_id=shipment.id,
manufacturer_address=shipment.manufacturer.wallet,
# ... other data
)
2. Обновление статуса
# При изменении статуса в Odoo
blockchain_service.update_shipment_status(
shipment_id=blockchain_shipment.blockchain_id,
status=2 # InTransit
)
3. Минтинг NFT при доставке
# После успешной доставки
if shipment.status == 'delivered':
blockchain_service.mint_nft_certificate(
recipient_address=buyer.wallet,
shipment_external_id=shipment.id,
# ... other data
)
Next Steps
- Добавьте очереди задач (Celery) для асинхронной обработки
- Настройте IPFS для хранения документов
- Создайте webhooks для событий блокчейна
- Добавьте мониторинг транзакций