喂价保护技术文档

in #cn-devlast month

喂价保护技术文档

概述

Steem区块链中的喂价保护机制旨在确保STEEM/SBD价格喂价的稳定性和可靠性,防止价格操纵和异常波动。该机制通过多层保护措施,包括喂价有效性检查、中位数计算、历史价格平滑以及市值比例限制等,来维护系统的稳定性。

价格喂价系统架构

喂价发布

见证人(Witness)通过 feed_publish_operation 发布价格喂价:

// 位置: libraries/chain/steem_evaluator.cpp::feed_publish_evaluator::do_apply()

void feed_publish_evaluator::do_apply( const feed_publish_operation& o )
{
   if( _db.has_hardfork( STEEM_HARDFORK_0_20__409 ) )
      FC_ASSERT( is_asset_type( o.exchange_rate.base, SBD_SYMBOL ) && is_asset_type( o.exchange_rate.quote, STEEM_SYMBOL ),
            "Price feed must be a SBD/STEEM price" );

   const auto& witness = _db.get_witness( o.publisher );
   _db.modify( witness, [&]( witness_object& w )
   {
      w.sbd_exchange_rate = o.exchange_rate;
      w.last_sbd_exchange_update = _db.head_block_time();
   });
}

关键点

  • 硬分叉0.20.409之后,价格喂价必须是 SBD/STEEM 格式
  • 每个见证人维护自己的 sbd_exchange_ratelast_sbd_exchange_update 时间戳

喂价验证

发布喂价时进行基本验证:

// 位置: libraries/protocol/steem_operations.cpp::feed_publish_operation::validate()

void feed_publish_operation::validate()const
{
   validate_account_name( publisher );
   FC_ASSERT( ( is_asset_type( exchange_rate.base, STEEM_SYMBOL ) && is_asset_type( exchange_rate.quote, SBD_SYMBOL ) )
      || ( is_asset_type( exchange_rate.base, SBD_SYMBOL ) && is_asset_type( exchange_rate.quote, STEEM_SYMBOL ) ),
      "Price feed must be a STEEM/SBD price" );
   exchange_rate.validate();
}

中位数价格计算

更新频率

中位数价格每 STEEM_FEED_INTERVAL_BLOCKS 个区块更新一次(约每小时):

// 位置: libraries/chain/database.cpp::update_median_feed()

void database::update_median_feed() {
   if( (head_block_num() % STEEM_FEED_INTERVAL_BLOCKS) != 0 )
      return;
   // ... 计算中位数价格
}

常量定义

  • STEEM_FEED_INTERVAL_BLOCKS = STEEM_BLOCKS_PER_HOUR (约1200个区块,每小时)

有效喂价收集

系统收集所有活跃见证人的有效喂价:

// 位置: libraries/chain/database.cpp::update_median_feed()

auto now = head_block_time();
const witness_schedule_object& wso = get_witness_schedule_object();
vector<price> feeds; feeds.reserve( wso.num_scheduled_witnesses );

for( int i = 0; i < wso.num_scheduled_witnesses; i++ )
{
   const auto& wit = get_witness( wso.current_shuffled_witnesses[i] );
   if( has_hardfork( STEEM_HARDFORK_0_19__822 ) )
   {
      if( now < wit.last_sbd_exchange_update + STEEM_MAX_FEED_AGE_SECONDS
         && !wit.sbd_exchange_rate.is_null() )
      {
         feeds.push_back( wit.sbd_exchange_rate );
      }
   }
   else if( wit.last_sbd_exchange_update < now + STEEM_MAX_FEED_AGE_SECONDS &&
       !wit.sbd_exchange_rate.is_null() )
   {
      feeds.push_back( wit.sbd_exchange_rate );
   }
}

有效性条件

  1. 喂价不能为空(!wit.sbd_exchange_rate.is_null()
  2. 喂价年龄不能超过 STEEM_MAX_FEED_AGE_SECONDS(7天)

常量定义

  • STEEM_MAX_FEED_AGE_SECONDS = 60*60*24*7 (7天 = 604800秒)

最小喂价数量要求

系统要求至少 STEEM_MIN_FEEDS 个有效喂价才能更新中位数价格:

if( feeds.size() >= STEEM_MIN_FEEDS )
{
   std::sort( feeds.begin(), feeds.end() );
   auto median_feed = feeds[feeds.size()/2];
   // ... 更新价格历史
}

常量定义

  • STEEM_MIN_FEEDS = STEEM_MAX_WITNESSES / 3 (21 / 3 = 7个喂价)
  • STEEM_MAX_WITNESSES = 21

保护目的:防止在价格尚未建立时进行转换操作,确保有足够的见证人参与价格喂价。

价格历史窗口

历史价格存储

系统维护一个价格历史队列,用于计算历史中位数:

modify( get_feed_history(), [&]( feed_history_object& fho )
{
   fho.price_history.push_back( median_feed );
   size_t steem_feed_history_window = STEEM_FEED_HISTORY_WINDOW_PRE_HF_16;
   if( has_hardfork( STEEM_HARDFORK_0_16__551) )
      steem_feed_history_window = STEEM_FEED_HISTORY_WINDOW;

   if( fho.price_history.size() > steem_feed_history_window )
      fho.price_history.pop_front();

历史窗口大小

  • 硬分叉0.16之前STEEM_FEED_HISTORY_WINDOW_PRE_HF_16 = 24*7 (7天,168个数据点)
  • 硬分叉0.16之后STEEM_FEED_HISTORY_WINDOW = 12*7 (3.5天,84个数据点)

历史中位数计算

从价格历史中计算中位数作为最终有效价格:

if( fho.price_history.size() )
{
   std::deque< price > copy;
   for( const auto& i : fho.price_history )
   {
      copy.push_back( i );
   }

   std::sort( copy.begin(), copy.end() );
   fho.current_median_history = copy[copy.size()/2];

计算方式

  • 将价格历史复制到临时容器
  • 排序后取中位数
  • 这实现了"中位数的中位数"机制,提供额外的稳定性

市值比例保护机制

SBD市值上限限制

在硬分叉0.14.230之后,系统引入了市值比例保护机制,确保SBD市值不超过STEEM+SBD总市值的10%:

// 位置: libraries/chain/database.cpp::update_median_feed()

if( has_hardfork( STEEM_HARDFORK_0_14__230 ) )
{
   // This block limits the effective median price to force SBD to remain at or
   // below 10% of the combined market cap of STEEM and SBD.
   //
   // For example, if we have 500 STEEM and 100 SBD, the price is limited to
   // 900 SBD / 500 STEEM which works out to be $1.80.  At this price, 500 Steem
   // would be valued at 500 * $1.80 = $900.  100 SBD is by definition always $100,
   // so the combined market cap is $900 + $100 = $1000.

   const auto& gpo = get_dynamic_global_properties();

   if( gpo.current_sbd_supply.amount > 0 )
   {
      price min_price( asset( 9 * gpo.current_sbd_supply.amount, SBD_SYMBOL ), gpo.current_supply );

      if( min_price > fho.current_median_history )
         fho.current_median_history = min_price;
   }
}

数学原理

目标:确保 SBD市值 ≤ (STEEM市值 + SBD市值) × 10%

推导过程

  1. SBD市值 = SBD供应量 × $1 = SBD_supply
  2. STEEM市值 = STEEM供应量 × STEEM价格 = STEEM_supply × price
  3. 总市值 = SBD_supply + STEEM_supply × price
  4. 要求:SBD_supply ≤ (SBD_supply + STEEM_supply × price) × 0.1
  5. 化简:SBD_supply ≤ 0.1 × SBD_supply + 0.1 × STEEM_supply × price
  6. 进一步:0.9 × SBD_supply ≤ 0.1 × STEEM_supply × price
  7. 得到:price ≥ (9 × SBD_supply) / STEEM_supply

实现

  • 计算最小价格:min_price = (9 × current_sbd_supply) / current_supply
  • 如果计算出的历史中位数价格低于最小价格,则使用最小价格

示例

  • 假设有 500 STEEM 和 100 SBD
  • 最小价格 = (9 × 100) / 500 = 900 / 500 = 1.8 SBD/STEEM
  • 在此价格下:
    • STEEM市值 = 500 × 1.8 = 900 SBD(等值)
    • SBD市值 = 100 SBD
    • 总市值 = 1000 SBD
    • SBD占比 = 100 / 1000 = 10% ✓

测试网络特殊处理

在测试网络中,可以通过标志跳过价格限制检查:

#ifdef IS_TEST_NET
   if( skip_price_feed_limit_check )
      return;
#endif

这允许测试网络进行更灵活的价格测试。

转换操作保护

转换延迟

SBD到STEEM的转换操作有延迟机制,确保价格稳定:

常量定义

  • 硬分叉0.16之前STEEM_CONVERSION_DELAY_PRE_HF_16 = fc::days(7) (7天)
  • 硬分叉0.16之后STEEM_CONVERSION_DELAY = fc::hours(STEEM_FEED_HISTORY_WINDOW) (3.5天)
// 位置: libraries/chain/steem_evaluator.cpp::convert_evaluator::do_apply()

void convert_evaluator::do_apply( const convert_operation& o )
{
   _db.adjust_balance( o.owner, -o.amount );

   const auto& fhistory = _db.get_feed_history();
   FC_ASSERT( !fhistory.current_median_history.is_null(), "Cannot convert SBD because there is no price feed." );

   auto steem_conversion_delay = STEEM_CONVERSION_DELAY_PRE_HF_16;
   if( _db.has_hardfork( STEEM_HARDFORK_0_16__551) )
      steem_conversion_delay = STEEM_CONVERSION_DELAY;

   _db.create<convert_request_object>( [&]( convert_request_object& obj )
   {
      obj.owner           = o.owner;
      obj.requestid       = o.requestid;
      obj.amount          = o.amount;
      obj.conversion_date = _db.head_block_time() + steem_conversion_delay;
   });
}

保护机制

  • 转换请求创建后,需要等待延迟时间才能执行
  • 延迟时间与价格历史窗口相同,确保价格稳定后再执行转换

关键常量总结

常量名说明
STEEM_FEED_INTERVAL_BLOCKSSTEEM_BLOCKS_PER_HOUR喂价更新间隔(约每小时)
STEEM_MAX_FEED_AGE_SECONDS60*60*24*7 (604800)喂价最大有效期(7天)
STEEM_MIN_FEEDSSTEEM_MAX_WITNESSES/3 (7)最小有效喂价数量
STEEM_FEED_HISTORY_WINDOW_PRE_HF_1624*7 (168)硬分叉0.16前的历史窗口(7天)
STEEM_FEED_HISTORY_WINDOW12*7 (84)硬分叉0.16后的历史窗口(3.5天)
STEEM_CONVERSION_DELAY_PRE_HF_16fc::days(7)硬分叉0.16前的转换延迟(7天)
STEEM_CONVERSION_DELAYfc::hours(84)硬分叉0.16后的转换延迟(3.5天)
STEEM_MAX_WITNESSES21最大见证人数量

保护机制层次

喂价保护机制采用多层防护:

  1. 第一层:喂价有效性检查

    • 检查喂价是否为空
    • 检查喂价年龄是否在有效期内(7天)
  2. 第二层:最小喂价数量要求

    • 要求至少7个有效喂价才能更新中位数
    • 防止少数见证人操纵价格
  3. 第三层:中位数计算

    • 使用所有有效喂价的中位数
    • 减少异常值的影响
  4. 第四层:历史价格平滑

    • 维护3.5天的价格历史
    • 使用历史中位数作为最终价格
    • 实现"中位数的中位数"机制
  5. 第五层:市值比例限制

    • 确保SBD市值不超过总市值的10%
    • 通过设置最小价格实现
  6. 第六层:转换延迟

    • 转换操作延迟3.5天执行
    • 与价格历史窗口同步,确保价格稳定

硬分叉影响

硬分叉0.14.230

  • 引入:市值比例保护机制
  • 目的:防止SBD市值过度膨胀

硬分叉0.16.551

  • 变更:价格历史窗口从7天缩短到3.5天
  • 变更:转换延迟从7天缩短到3.5天
  • 影响:提高价格响应速度,同时保持稳定性

硬分叉0.19.822

  • 变更:优化喂价年龄检查逻辑
  • 改进:更严格的时间戳验证

硬分叉0.20.409

  • 变更:强制要求价格喂价为 SBD/STEEM 格式
  • 目的:统一价格表示格式

安全考虑

  1. 防止价格操纵:通过中位数计算和历史平滑,单个见证人无法轻易操纵价格
  2. 防止异常波动:历史窗口机制平滑短期价格波动
  3. 防止市值失衡:市值比例限制确保SBD不会过度膨胀
  4. 防止过早转换:最小喂价数量要求和转换延迟防止在价格未建立时进行转换
  5. 防止过期喂价:7天的喂价有效期确保价格信息及时更新

相关数据结构

feed_history_object

// 位置: libraries/chain/include/steem/chain/steem_objects.hpp

class feed_history_object : public object< feed_history_object_type, feed_history_object >
{
   price                    current_median_history;  // 当前历史中位数价格
   deque< price >           price_history;           // 价格历史队列
};

witness_object

// 位置: libraries/chain/include/steem/chain/witness_objects.hpp

class witness_object : public object< witness_object_type, witness_object >
{
   price                    sbd_exchange_rate;        // SBD/STEEM汇率
   time_point_sec           last_sbd_exchange_update; // 最后更新时间
};

测试用例

代码库中包含针对价格喂价的测试:

  • 位置: tests/tests/operation_time_tests.cpp
  • 测试内容: 验证价格喂价更新、中位数计算、历史窗口等功能

参考代码位置

  • 价格喂价更新: libraries/chain/database.cpp:3540-3620
  • 喂价发布评估: libraries/chain/steem_evaluator.cpp:2505-2517
  • 喂价验证: libraries/protocol/steem_operations.cpp:487-494
  • 常量定义: libraries/protocol/include/steem/protocol/config.hpp:255-261
  • 转换操作: libraries/chain/steem_evaluator.cpp:2519-2538
  • 测试用例: tests/tests/operation_time_tests.cpp

总结

Steem的喂价保护机制通过多层防护措施,确保了价格喂价的稳定性和可靠性:

保护层机制目的
有效性检查年龄限制、非空检查确保喂价及时有效
数量要求最少7个有效喂价防止少数人操纵
中位数计算取所有喂价中位数减少异常值影响
历史平滑3.5天历史中位数平滑短期波动
市值限制SBD市值≤10%总市值防止市值失衡
转换延迟3.5天延迟执行确保价格稳定

这些机制共同工作,为Steem生态系统提供了稳定可靠的价格基础。