Day 10
综合项目与最佳实践
高级阶段 · 预计学习时间 180 分钟
学习目标
- 整合前 9 天学习内容
- 构建完整的 DApp 项目
- 掌握生产级开发最佳实践
- 实现错误处理和日志记录
- 了解安全性考量
🎉 恭喜你完成 10 天学习!
你已经掌握了 Aeternity Python SDK 的核心功能。现在让我们把所有知识整合起来!
知识回顾
10 天学习路径
| 阶段 | Day | 主题 | 核心技能 |
|---|---|---|---|
| 初级 | 1 | 环境搭建 | SDK 安装、节点连接 |
| 2 | 账户管理 | 密钥生成、钱包操作 | |
| 3 | 交易操作 | 转账、余额查询 | |
| 中级 | 4 | Sophia 语言 | 合约语法、类型系统 |
| 5 | 合约部署 | 编译、部署、调用 | |
| 6 | AEX-9 代币 | 代币标准、转账授权 | |
| 高级 | 7 | 加密签名 | 消息签名、加密通信 |
| 8 | AENS 域名 | 域名注册、解析 | |
| 9 | 预言机 | 链外数据接入 | |
| 10 | 综合项目 | DApp 开发实战 |
SDK 模块总结
核心模块
node- 节点连接signing- 账户签名wallet- 钱包管理
合约模块
contract- 合约交互compiler- 编译器
高级模块
crypto- 加密功能aens- 域名服务oracles- 预言机
综合项目:去中心化投票系统
项目概述
构建一个完整的去中心化投票 DApp,整合所有学习内容。
功能特性:
- 创建投票提案
- 基于代币的投票权重
- AENS 域名集成
- 预言机获取投票结果
智能合约
@compiler >= 6
contract VotingSystem =
record proposal = {
id : int,
title : string,
description : string,
creator : address,
yes_votes : int,
no_votes : int,
end_height : int,
executed : bool
}
record state = {
owner : address,
proposals : map(int, proposal),
proposal_count : int,
votes : map(address, map(int, bool)),
min_vote_period : int
}
datatype event =
ProposalCreated(int, string, address)
| Voted(int, address, bool)
| ProposalExecuted(int)
entrypoint init() = {
owner = Call.caller,
proposals = {},
proposal_count = 0,
votes = {},
min_vote_period = 100
}
// 创建提案
stateful entrypoint create_proposal(title: string, description: string,
vote_period: int) : int =
require(vote_period >= state.min_vote_period, "Vote period too short")
let id = state.proposal_count + 1
let new_proposal = {
id = id,
title = title,
description = description,
creator = Call.caller,
yes_votes = 0,
no_votes = 0,
end_height = Chain.block_height + vote_period,
executed = false
}
put(state{
proposals = state.proposals{[id] = new_proposal},
proposal_count = id
})
Chain.event(ProposalCreated(id, title, Call.caller))
id
// 投票
stateful entrypoint vote(proposal_id: int, support: bool) =
require(Map.member(proposal_id, state.proposals), "Proposal not found")
let p = state.proposals[proposal_id]
require(Chain.block_height < p.end_height, "Voting ended")
require(!has_voted(Call.caller, proposal_id), "Already voted")
let updated = if(support)
p{yes_votes = p.yes_votes + 1}
else
p{no_votes = p.no_votes + 1}
put(state{
proposals = state.proposals{[proposal_id] = updated},
votes = state.votes{[Call.caller] =
Map.lookup_default(Call.caller, state.votes, {}){[proposal_id] = true}}
})
Chain.event(Voted(proposal_id, Call.caller, support))
// 查询提案
entrypoint get_proposal(id: int) : proposal =
require(Map.member(id, state.proposals), "Proposal not found")
state.proposals[id]
// 检查是否已投票
entrypoint has_voted(voter: address, proposal_id: int) : bool =
switch(Map.lookup(voter, state.votes))
None => false
Some(votes) => Map.lookup_default(proposal_id, votes, false)
// 获取投票结果
entrypoint get_results(proposal_id: int) : (int, int) =
let p = state.proposals[proposal_id]
(p.yes_votes, p.no_votes)
Python 客户端
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.contract_native import ContractManager
class VotingClient:
"""投票系统客户端"""
def __init__(self, node_url: str, account: Account):
config = Config(external_url=node_url)
self.client = NodeClient(config)
self.account = account
self.manager = None
def deploy(self, source: str) -> str:
"""部署合约"""
self.manager = ContractManager(
client=self.client,
account=self.account,
source=source
)
tx = self.manager.deploy()
return self.manager.contract_id
def connect(self, contract_id: str, source: str):
"""连接已部署合约"""
self.manager = ContractManager(
client=self.client,
account=self.account,
contract_id=contract_id,
source=source
)
def create_proposal(self, title: str, description: str,
vote_period: int = 200) -> int:
"""创建提案"""
result = self.manager.call(
"create_proposal",
f'"{title}"',
f'"{description}"',
str(vote_period)
)
return result.return_value
def vote(self, proposal_id: int, support: bool):
"""投票"""
self.manager.call(
"vote",
str(proposal_id),
"true" if support else "false"
)
def get_proposal(self, proposal_id: int) -> dict:
"""获取提案信息"""
result = self.manager.call_static("get_proposal", str(proposal_id))
return result
def get_results(self, proposal_id: int) -> tuple:
"""获取投票结果"""
result = self.manager.call_static("get_results", str(proposal_id))
return result
# 使用示例
client = VotingClient(
node_url='https://testnet.aeternity.io',
account=Account.generate()
)
# 部署合约
contract_id = client.deploy(voting_source)
print(f"合约地址: {contract_id}")
# 创建提案
proposal_id = client.create_proposal(
title="增加社区基金",
description="提议将 10% 的收益分配给社区发展基金",
vote_period=500
)
print(f"提案 ID: {proposal_id}")
# 投票
client.vote(proposal_id, support=True)
# 查询结果
yes, no = client.get_results(proposal_id)
print(f"赞成: {yes}, 反对: {no}")
最佳实践
错误处理
from aeternity.exceptions import (
AEException,
TransactionError,
ContractError,
InsufficientFundsError
)
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_transaction(func):
"""交易安全装饰器"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except InsufficientFundsError as e:
logger.error(f"余额不足: {e}")
raise
except TransactionError as e:
logger.error(f"交易失败: {e}")
raise
except ContractError as e:
logger.error(f"合约错误: {e}")
raise
except AEException as e:
logger.error(f"SDK错误: {e}")
raise
return wrapper
@safe_transaction
def transfer_with_retry(client, account, to, amount, max_retries=3):
"""带重试的转账"""
for attempt in range(max_retries):
try:
tx = client.spend(account, to, amount)
logger.info(f"转账成功: {tx.hash}")
return tx
except TransactionError as e:
if attempt < max_retries - 1:
logger.warning(f"重试 {attempt + 1}/{max_retries}")
time.sleep(2)
else:
raise
配置管理
import os
from dataclasses import dataclass
from typing import Optional
@dataclass
class AppConfig:
"""应用配置"""
node_url: str
compiler_url: str
network_id: str
debug: bool = False
@classmethod
def from_env(cls, env: str = "testnet") -> "AppConfig":
"""从环境变量加载配置"""
configs = {
"mainnet": cls(
node_url="https://mainnet.aeternity.io",
compiler_url="https://compiler.aepps.com",
network_id="ae_mainnet"
),
"testnet": cls(
node_url="https://testnet.aeternity.io",
compiler_url="https://compiler.aepps.com",
network_id="ae_uat",
debug=True
)
}
return configs.get(env, configs["testnet"])
# 使用
config = AppConfig.from_env(os.getenv("NETWORK", "testnet"))
安全考量
❌ 避免
- 硬编码私钥
- 明文存储密码
- 忽略交易确认
- 跳过输入验证
✅ 推荐
- 使用环境变量
- 加密钱包存储
- 等待交易确认
- 严格参数校验
代码安全示例
# 使用环境变量存储敏感信息
import os
from getpass import getpass
def load_account_secure():
"""安全加载账户"""
keystore_path = os.getenv("KEYSTORE_PATH")
if keystore_path:
password = getpass("输入钱包密码: ")
return Account.from_keystore(keystore_path, password)
secret_key = os.getenv("SECRET_KEY")
if secret_key:
return Account.from_secret_key_string(secret_key)
raise ValueError("未配置账户信息")
# 等待交易确认
def wait_for_confirmation(client, tx_hash, confirmations=3, timeout=120):
"""等待交易确认"""
start = time.time()
while time.time() - start < timeout:
try:
tx_info = client.get_transaction_info_by_hash(tx_hash)
if tx_info.block_height:
current = client.get_current_key_block_height()
if current - tx_info.block_height >= confirmations:
return tx_info
except:
pass
time.sleep(3)
raise TimeoutError(f"交易确认超时: {tx_hash}")
课程完成
恭喜完成 Python SDK 10 天教程!
你已经掌握了 Aeternity 区块链开发的核心技能
✓ 账户管理
✓ 交易操作
✓ 智能合约
✓ 代币开发
✓ 加密签名
✓ AENS 域名
✓ 预言机