Files
optovia/blockchain/INTEGRATION.md
2026-01-07 09:20:11 +07:00

17 KiB
Raw Blame History

Интеграция с 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

  1. Добавьте очереди задач (Celery) для асинхронной обработки
  2. Настройте IPFS для хранения документов
  3. Создайте webhooks для событий блокчейна
  4. Добавьте мониторинг транзакций