区块链代币管理
背景
区块链代币种类繁多, 如何在项目里面合理地管理代币, 这是一个问题.
通常情况下项目规模不大, 代币种类也不是很多, 可以直接在代码里面硬编码, 前端和后端各自独立维护, 但是这样的话, 项目的可扩展性和维护性就比较差.
通过设计一个简单的代币管理模块, 可以有效地解决这个问题.
设计思路
明确需求
- 准确性有保证, 代币数据准确无误;
- 可维护性强, 统一管理代币, 避免重复劳动且容易出错;
- 便捷性较好, 通过设计灵活的接口, 可以适应不同的业务场景.
- 稳定性高, 单数据源容易出现异常时, 代币数据可能会丢失或无法读取;
- 可扩展性好, 代币种类可以动态增加, 代币属性可以动态修改;
设计方案
数据结构
代币通常需要包含以下属性:
- 代币名称
- 代币符号
- 代币精度
- 代币合约地址
- 代币图标
- 链类型
- 协议类型
在同一条链上, 代币符号常常无法保证唯一性, 例如: USDT, 代币名称可以保证唯一性, 例如: Tether USD.
在不同链上, 代币符号也可能相同, 例如: USDT.
所以我们需要一个唯一的标识来区分不同链上的代币, 这里我们使用代币名称+链类型来作为唯一标识.
数据存储
代币数据的持久化存储, 通常有以下几种方案:
- 数据库
- 文件
- 缓存 对于token这类型的数据, 数据库可以选择非关系型数据库, 例如: mongodb, redis等. 缓存可以选择本地内存缓存, 规模较大的话, 可以选择分布式缓存, 例如: redis, memcached等.
仅使用单数据源的话, 会存在以下问题:
- 单数据源容易出现异常时, 代币数据可能会丢失或无法读取;
- 单数据源无法保证数据的准确性, 例如: 代币数据被恶意篡改, 或者数据更新不同步等;
为了解决这个问题, 我们可以使用多数据源的方案, 例如: 数据库+缓存, 数据库+文件等.
不过多数据源的方案, 会存在数据同步的问题, 例如: 数据库+缓存, 数据库中的数据被修改后, 缓存中的数据没有及时更新, 会导致数据不一致. 通过设计一个简单的数据同步机制, 可以有效地解决这个问题. 同步机制的设计思路如下:
- 数据库中的数据被修改后, 通过消息队列发送消息;
- 缓存中的数据订阅消息队列, 收到消息后, 更新缓存中的数据;
接口设计
代币管理系统的接口设计, 通常需要包含以下几个方面:
- 数据初始化
- 数据的读取
- 数据的更新
- 数据的删除
- 数据的同步
对象设计
实现方案
数据结构
代币数据的基础结构如下:
@dataclass
class Token:
chain: str
token: str
token_addr: str
precision: int
protocol: str
logo: str
数据存储
代币数据的存储方案如下:
- 数据库: mongodb
- 缓存: LocalCache
接口实现
代币管理系统的接口设计如下:
- 数据初始化: 从数据库中读取数据, 并写入缓存, 如果数据库中没有数据, 则从读取预先硬编码的代币数据, 并写入数据库和缓存;
- 数据的读取: 从缓存中读取数据, 如果缓存中没有数据, 则从数据库中读取数据, 并写入缓存;
- 数据的更新: 更新数据库中的数据, 并通知缓存更新数据;
- 数据的删除: 删除数据库中的数据, 并通知缓存删除数据;
- 数据的同步: 定时检查数据库和缓存中的数据是否一致, 如果不一致, 则更新缓存中的数据; 定时检查数据库中的数据和外部数据(区块链浏览器,服务提供商等)是否一致, 如果不一致, 则更新数据库中的数据;
对象实现
以python为例, 代币管理系统的对象如下:
class CryptoTokenManager:
def __init__(self, db: Database, cache: Cache):
self.db = db
self.cache = cache
def restore(self) -> list:
"""恢复数据
外部数据(暂无) > 数据库 > 默认数据 > 内存
Returns:
list: 恢复的数据
"""
pass
def get_token(self, token: str, chain: str) -> dict:
"""获取token信息
Args:
token (str): token名称
chain (str): 链
Returns:
dict: token信息
"""
pass
def get_token_list(self, token: str='', chain: str='', protocol: str='') -> list:
"""获取token列表
Args:
token (str, optional): token名称. Defaults to ''.
chain (str, optional): 链. Defaults to ''.
protocol (str, optional): 协议. Defaults to ''.
Returns:
list: token列表
"""
pass
def add_token(self, token_info: dict) -> bool:
"""添加token(本地)
Args:
token_info (dict): token信息
Returns:
bool: 是否添加成功
"""
pass
def remove_token(self, token: str, chain: str) -> bool:
"""移除token(本地)
Args:
token (str): token名称
chain (str): 链
Returns:
bool: 是否移除成功
"""
pass
def save(self) -> bool:
"""保存至数据库
Returns:
bool: 是否保存成功
"""
pass
def check_data(self) -> bool:
"""检查数据是否正确
Returns:
bool: 是否正确
"""
pass
思考🤔
性能优化
因为代币数据的读取频率远远大于写入频率, 所以我们可以通过缓存来提高读取性能. 作为一个相对静态的数据, 后续还可以考虑通过CDN来提升网络接口表现.
安全性
代币数据的安全性关键就是保证数据的准确性, 具体来说就是数据的非预期篡改, 例如: 代币数据被恶意篡改, 或者数据更新错误等.
不过代币管理虽然贯穿了大部分的业务流程, 但是错误的代币地址通常导致的是业务流程的失败, 而不是错误的业务流程(例如资产流向错误目标等), 所以对于安全性的提升可以更加侧重于密钥管理/收款方确认等方面.
后续优化方案
- 外部数据同步
- 异常处理
- 告警机制
- 数据存储简化