喂价保护技术文档
喂价保护技术文档
概述
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_rate和last_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 );
}
}
有效性条件:
- 喂价不能为空(
!wit.sbd_exchange_rate.is_null()) - 喂价年龄不能超过
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%
推导过程:
- 设
SBD市值 = SBD供应量 × $1 = SBD_supply - 设
STEEM市值 = STEEM供应量 × STEEM价格 = STEEM_supply × price - 总市值 =
SBD_supply + STEEM_supply × price - 要求:
SBD_supply ≤ (SBD_supply + STEEM_supply × price) × 0.1 - 化简:
SBD_supply ≤ 0.1 × SBD_supply + 0.1 × STEEM_supply × price - 进一步:
0.9 × SBD_supply ≤ 0.1 × STEEM_supply × price - 得到:
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_BLOCKS | STEEM_BLOCKS_PER_HOUR | 喂价更新间隔(约每小时) |
STEEM_MAX_FEED_AGE_SECONDS | 60*60*24*7 (604800) | 喂价最大有效期(7天) |
STEEM_MIN_FEEDS | STEEM_MAX_WITNESSES/3 (7) | 最小有效喂价数量 |
STEEM_FEED_HISTORY_WINDOW_PRE_HF_16 | 24*7 (168) | 硬分叉0.16前的历史窗口(7天) |
STEEM_FEED_HISTORY_WINDOW | 12*7 (84) | 硬分叉0.16后的历史窗口(3.5天) |
STEEM_CONVERSION_DELAY_PRE_HF_16 | fc::days(7) | 硬分叉0.16前的转换延迟(7天) |
STEEM_CONVERSION_DELAY | fc::hours(84) | 硬分叉0.16后的转换延迟(3.5天) |
STEEM_MAX_WITNESSES | 21 | 最大见证人数量 |
保护机制层次
喂价保护机制采用多层防护:
第一层:喂价有效性检查
- 检查喂价是否为空
- 检查喂价年龄是否在有效期内(7天)
第二层:最小喂价数量要求
- 要求至少7个有效喂价才能更新中位数
- 防止少数见证人操纵价格
第三层:中位数计算
- 使用所有有效喂价的中位数
- 减少异常值的影响
第四层:历史价格平滑
- 维护3.5天的价格历史
- 使用历史中位数作为最终价格
- 实现"中位数的中位数"机制
第五层:市值比例限制
- 确保SBD市值不超过总市值的10%
- 通过设置最小价格实现
第六层:转换延迟
- 转换操作延迟3.5天执行
- 与价格历史窗口同步,确保价格稳定
硬分叉影响
硬分叉0.14.230
- 引入:市值比例保护机制
- 目的:防止SBD市值过度膨胀
硬分叉0.16.551
- 变更:价格历史窗口从7天缩短到3.5天
- 变更:转换延迟从7天缩短到3.5天
- 影响:提高价格响应速度,同时保持稳定性
硬分叉0.19.822
- 变更:优化喂价年龄检查逻辑
- 改进:更严格的时间戳验证
硬分叉0.20.409
- 变更:强制要求价格喂价为
SBD/STEEM格式 - 目的:统一价格表示格式
安全考虑
- 防止价格操纵:通过中位数计算和历史平滑,单个见证人无法轻易操纵价格
- 防止异常波动:历史窗口机制平滑短期价格波动
- 防止市值失衡:市值比例限制确保SBD不会过度膨胀
- 防止过早转换:最小喂价数量要求和转换延迟防止在价格未建立时进行转换
- 防止过期喂价: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生态系统提供了稳定可靠的价格基础。