区块链代币管理
背景
区块链代币种类繁多, 如何在项目里面合理地管理代币, 这是一个问题.
通常情况下项目规模不大, 代币种类也不是很多, 可以直接在代码里面硬编码, 前端和后端各自独立维护, 但是这样的话, 项目的可扩展性和维护性就比较差.
通过设计一个简单的代币管理模块, 可以有效地解决这个问题.
设计思路
明确需求
- 准确性有保证, 代币数据准确无误;
 - 可维护性强, 统一管理代币, 避免重复劳动且容易出错;
 - 便捷性较好, 通过设计灵活的接口, 可以适应不同的业务场景.
 - 稳定性高, 单数据源容易出现异常时, 代币数据可能会丢失或无法读取;
 - 可扩展性好, 代币种类可以动态增加, 代币属性可以动态修改;
 
设计方案
数据结构
代币通常需要包含以下属性:
- 代币名称
 - 代币符号
 - 代币精度
 - 代币合约地址
 - 代币图标
 - 链类型
 - 协议类型
 
在同一条链上, 代币符号常常无法保证唯一性, 例如: 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来提升网络接口表现.
安全性
代币数据的安全性关键就是保证数据的准确性, 具体来说就是数据的非预期篡改, 例如: 代币数据被恶意篡改, 或者数据更新错误等.
不过代币管理虽然贯穿了大部分的业务流程, 但是错误的代币地址通常导致的是业务流程的失败, 而不是错误的业务流程(例如资产流向错误目标等), 所以对于安全性的提升可以更加侧重于密钥管理/收款方确认等方面.
后续优化方案
- 外部数据同步
 - 异常处理
 - 告警机制
 - 数据存储简化