区块链代币管理

区块链代币管理

背景

区块链代币种类繁多, 如何在项目里面合理地管理代币, 这是一个问题.
通常情况下项目规模不大, 代币种类也不是很多, 可以直接在代码里面硬编码, 前端和后端各自独立维护, 但是这样的话, 项目的可扩展性和维护性就比较差.
通过设计一个简单的代币管理模块, 可以有效地解决这个问题.

设计思路

明确需求

  1. 准确性有保证, 代币数据准确无误;
  2. 可维护性强, 统一管理代币, 避免重复劳动且容易出错;
  3. 便捷性较好, 通过设计灵活的接口, 可以适应不同的业务场景.
  4. 稳定性高, 单数据源容易出现异常时, 代币数据可能会丢失或无法读取;
  5. 可扩展性好, 代币种类可以动态增加, 代币属性可以动态修改;

设计方案

数据结构

代币通常需要包含以下属性:

  • 代币名称
  • 代币符号
  • 代币精度
  • 代币合约地址
  • 代币图标
  • 链类型
  • 协议类型

在同一条链上, 代币符号常常无法保证唯一性, 例如: USDT, 代币名称可以保证唯一性, 例如: Tether USD.
在不同链上, 代币符号也可能相同, 例如: USDT.
所以我们需要一个唯一的标识来区分不同链上的代币, 这里我们使用代币名称+链类型来作为唯一标识.

数据存储

代币数据的持久化存储, 通常有以下几种方案:

  1. 数据库
  2. 文件
  3. 缓存 对于token这类型的数据, 数据库可以选择非关系型数据库, 例如: mongodb, redis等. 缓存可以选择本地内存缓存, 规模较大的话, 可以选择分布式缓存, 例如: redis, memcached等.

仅使用单数据源的话, 会存在以下问题:

  1. 单数据源容易出现异常时, 代币数据可能会丢失或无法读取;
  2. 单数据源无法保证数据的准确性, 例如: 代币数据被恶意篡改, 或者数据更新不同步等;

为了解决这个问题, 我们可以使用多数据源的方案, 例如: 数据库+缓存, 数据库+文件等.

不过多数据源的方案, 会存在数据同步的问题, 例如: 数据库+缓存, 数据库中的数据被修改后, 缓存中的数据没有及时更新, 会导致数据不一致. 通过设计一个简单的数据同步机制, 可以有效地解决这个问题. 同步机制的设计思路如下:

  1. 数据库中的数据被修改后, 通过消息队列发送消息;
  2. 缓存中的数据订阅消息队列, 收到消息后, 更新缓存中的数据;

接口设计

代币管理系统的接口设计, 通常需要包含以下几个方面:

  1. 数据初始化
  2. 数据的读取
  3. 数据的更新
  4. 数据的删除
  5. 数据的同步

对象设计

实现方案

数据结构

代币数据的基础结构如下:

@dataclass
class Token:
    chain: str
    token: str
    token_addr: str
    precision: int
    protocol: str
    logo: str

数据存储

代币数据的存储方案如下:

  1. 数据库: mongodb
  2. 缓存: LocalCache

接口实现

代币管理系统的接口设计如下:

  1. 数据初始化: 从数据库中读取数据, 并写入缓存, 如果数据库中没有数据, 则从读取预先硬编码的代币数据, 并写入数据库和缓存;
  2. 数据的读取: 从缓存中读取数据, 如果缓存中没有数据, 则从数据库中读取数据, 并写入缓存;
  3. 数据的更新: 更新数据库中的数据, 并通知缓存更新数据;
  4. 数据的删除: 删除数据库中的数据, 并通知缓存删除数据;
  5. 数据的同步: 定时检查数据库和缓存中的数据是否一致, 如果不一致, 则更新缓存中的数据; 定时检查数据库中的数据和外部数据(区块链浏览器,服务提供商等)是否一致, 如果不一致, 则更新数据库中的数据;

对象实现

以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来提升网络接口表现.

安全性

代币数据的安全性关键就是保证数据的准确性, 具体来说就是数据的非预期篡改, 例如: 代币数据被恶意篡改, 或者数据更新错误等.
不过代币管理虽然贯穿了大部分的业务流程, 但是错误的代币地址通常导致的是业务流程的失败, 而不是错误的业务流程(例如资产流向错误目标等), 所以对于安全性的提升可以更加侧重于密钥管理/收款方确认等方面.

后续优化方案

  • 外部数据同步
  • 异常处理
  • 告警机制
  • 数据存储简化