交易有效字段 —— since
Number | Category | Status | Author | Organization | Created |
---|---|---|---|---|---|
0017 | Standards Track | Proposal | Jinyang Jiang | Nervos Foundation | 2019-03-11 |
交易有效字段 —— since
摘要
这个 RFC 建议通过添加一个新的共识规则,来防止在某个特定的区块时间戳或者区块号之前使用某个 cell。
概要
交易输入中加入一个新的 u64
(无符号 64 位整数)类型的字段 since
,用来防止这笔交易在一个绝对或者相对的时间之前被挖出来(被执行)。
since
的前八位是 flags
,剩余的 56
位代表 value
,flags
允许我们决定以下行为:
flags & (1 << 7)
代表relative_flag
。flags & (1 << 6)
和flags & (1 << 5)
一同代表metric_flag
。- 如果
metric_flag
为00
,since
使用一个基于区块的锁定期,value
可以解释为一个区块号(block number)或者一个相对区块号。 - 如果
metric_flag
为01
,since
使用一个基于区块的锁定期,value
可以解释为一个周期号(epoch number)或者一个相对周期号。 - 如果
metric_flag
为10
,since
使用一个基于时间的锁定期,value
可以解释为一个区块链时间戳(unix time)或者一个相对秒数。 metric_flag
为11
时,无效。
- 如果
flags
其余 5 位, 留作它用。
共识中确认这一字段描述如下:
- 迭代输入,并通过以下规则验证每一个输入。
- 如果
since
的 64 位都是 0,则忽略这个验证规则。 - 检查
metric_flag
的 flag:- 如果
metric_flag
为00
,since
的后 56 位,代表的是区块号。 - 如果
metric_flag
为01
,since
的后 56 位,代表的是周期号。 - 如果
metric_flag
为10
,since
的后 56 位,代表的是区块时间戳。
- 如果
- 检查
relative_flag
:- 如果
relative_flag
为0
,将字段视为绝对锁定时间。- 如果提示的区块号、周期号或者区块时间戳小于
since
字段,则验证失败。
- 如果提示的区块号、周期号或者区块时间戳小于
- 如果
relative_flag
为1
,将字段视为相对锁定时间。- 查找生成这个 input cell 的区块,根据
metric_flag
的 flag 获取区块号或周期号或区块时间戳。 - 如果提示所需的区块号或周期号或时间戳减去找到区块的区块号或周期号或时间戳,小于
since
字段,则验证失败。
- 查找生成这个 input cell 的区块,根据
- 如果
- 否则,验证应该继续。
一个 cell 的锁脚本可以检查输入的 since
字段,当 since
不满足条件时返回无效,从而间接防止该 cell 被花费使用。
这实现构造一个对资金进行基于时间锁定的脚本的能力:
# absolute time lock# cell only can be spent when block number greater than 10000.def unlock? input = CKB.load_current_input # fail if it is relative lock return false if input.since[63] == 1 # fail if metric_flag is not block_number return false (input.since & 0x6000_0000_0000_0000) != (0b0000_0000 << 56) input.since > 10000end
# relative time lock# cell only can be spent after 3 days after block that produced this cell get confirmeddef unlock? input = CKB.load_current_input # fail if it is absolute lock return false if input.since[63].zero? # fail if metric_flag is not timestamp return false (input.since & 0x6000_0000_0000_0000) != (0b0100_0000 << 56) # extract lower 56 bits and convert to seconds time = since & 0x00ffffffffffffff # check time must greater than 3 days time > 3 * 24 * 3600end
# relative time lock with epoch number# cell only can be spent in next epochdef unlock? input = CKB.load_current_input # fail if it is absolute lock return false if input.since[63].zero? # fail if metric_flag is not epoch number return false (input.since & 0x6000_0000_0000_0000) != (0b0010_0000 << 56) # extract lower 56 bits and convert to value epoch_number = since & 0x00ffffffffffffff # enforce only can unlock in next or further epochs epoch_number >= 1end
详细说明
since
应该使用过去的 11 个区块的时间戳的中位数来进行验证,而不是 metrice flag
为 10 时使用的区块时间戳,这样可以防止在时间戳上撒谎,因为矿工可能会通过在一个区块中包含更多不成熟的交易来赚取更多的手续费。
从过去的 11 个区块时间戳(在区块的父块中)中计算出区块时间的中位数,如果区块数不够且为奇数,我们选择较老的时间戳作为中位数,详细的行为定义见如下代码:
pub trait BlockMedianTimeContext { fn median_block_count(&self) -> u64;
/// Return timestamp and block_number of the corresponding bloch_hash, and hash of parent block fn timestamp_and_parent(&self, block_hash: &H256) -> (u64, BlockNumber, H256);
/// Return past block median time, **including the timestamp of the given one** fn block_median_time(&self, block_hash: &H256) -> u64 { let median_time_span = self.median_block_count(); let mut timestamps: Vec<u64> = Vec::with_capacity(median_time_span as usize); let mut block_hash = block_hash.to_owned(); for _ in 0..median_time_span { let (timestamp, block_number, parent_hash) = self.timestamp_and_parent(&block_hash); timestamps.push(timestamp); block_hash = parent_hash;
if block_number == 0 { break; } }
// return greater one if count is even. timestamps.sort(); timestamps[timestamps.len() >> 1] }}
since
交易的验证,定义如下代码:
const LOCK_TYPE_FLAG: u64 = 1 << 63;const METRIC_TYPE_FLAG_MASK: u64 = 0x6000_0000_0000_0000;const VALUE_MASK: u64 = 0x00ff_ffff_ffff_ffff;const REMAIN_FLAGS_BITS: u64 = 0x1f00_0000_0000_0000;
enum SinceMetric { BlockNumber(u64), EpochNumberWithFraction(EpochNumberWithFraction), Timestamp(u64),}
/// RFC 0017#[derive(Copy, Clone, Debug)]pub(crate) struct Since(pub(crate) u64);
impl Since { pub fn is_absolute(self) -> bool { self.0 & LOCK_TYPE_FLAG == 0 }
#[inline] pub fn is_relative(self) -> bool { !self.is_absolute() }
pub fn flags_is_valid(self) -> bool { (self.0 & REMAIN_FLAGS_BITS == 0) && ((self.0 & METRIC_TYPE_FLAG_MASK) != METRIC_TYPE_FLAG_MASK) }
fn extract_metric(self) -> Option<SinceMetric> { let value = self.0 & VALUE_MASK; match self.0 & METRIC_TYPE_FLAG_MASK { //0b0000_0000 0x0000_0000_0000_0000 => Some(SinceMetric::BlockNumber(value)), //0b0010_0000 0x2000_0000_0000_0000 => Some(SinceMetric::EpochNumberWithFraction(EpochNumberWithFraction::from_full_value(value))), //0b0100_0000 0x4000_0000_0000_0000 => Some(SinceMetric::Timestamp(value * 1000)), _ => None, } }}
/// https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0017-tx-valid-since/0017-tx-valid-since.md#detailed-specificationpub struct SinceVerifier<'a, M> { rtx: &'a ResolvedTransaction, block_median_time_context: &'a M, block_number: BlockNumber, epoch_number_with_fraction: EpochNumberWithFraction, parent_hash: Byte32, median_timestamps_cache: RefCell<LruCache<Byte32, u64>>,}
impl<'a, M> SinceVerifier<'a, M>where M: BlockMedianTimeContext, { pub fn new( rtx: &'a ResolvedTransaction, block_median_time_context: &'a M, block_number: BlockNumber, epoch_number_with_fraction: EpochNumberWithFraction, parent_hash: Byte32, ) -> Self { let median_timestamps_cache = RefCell::new(LruCache::new(rtx.resolved_inputs.len())); SinceVerifier { rtx, block_median_time_context, block_number, epoch_number_with_fraction, parent_hash, median_timestamps_cache, } } fn parent_median_time(&self, block_hash: &Byte32) -> u64 { let (_, _, parent_hash) = self .block_median_time_context .timestamp_and_parent(block_hash); self.block_median_time(&parent_hash) }
fn block_median_time(&self, block_hash: &Byte32) -> u64 { if let Some(median_time) = self.median_timestamps_cache.borrow().get(block_hash) { return *median_time; } let median_time = self.block_median_time_context.block_median_time(block_hash); self.median_timestamps_cache .borrow_mut() .insert(block_hash.clone(), median_time); median_time }
fn verify_absolute_lock(&self, since: Since) -> Result<(), Error> { if since.is_absolute() { match since.extract_metric() { Some(SinceMetric::BlockNumber(block_number)) => { if self.block_number < block_number { return Err(TransactionError::Immature).into()); } } Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => { if self.epoch_number_with_fraction < epoch_number_with_fraction { return Err(TransactionError::Immature).into()); } } Some(SinceMetric::Timestamp(timestamp)) => { let tip_timestamp = self.block_median_time(&self.parent_hash); if tip_timestamp < timestamp { return Err(TransactionError::Immature).into()); } } None => { return Err(TransactionError::InvalidSince).into()); } } } Ok(()) }
fn verify_relative_lock(&self, since: Since, cell_meta: &CellMeta) -> Result<(), Error> { if since.is_relative() { let info = match cell_meta.transaction_info { Some(ref transaction_info) => Ok(transaction_info), None => Err(TransactionError::Immature), }?; match since.extract_metric() { Some(SinceMetric::BlockNumber(block_number)) => { if self.block_number < info.block_number + block_number { return Err(TransactionError::Immature).into()); } } Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => { let a = self.epoch_number_with_fraction.to_rational(); let b = info.block_epoch.to_rational() + epoch_number_with_fraction.to_rational(); if a < b { return Err(TransactionError::Immature).into()); } } Some(SinceMetric::Timestamp(timestamp)) => { // pass_median_time(current_block) starts with tip block, which is the // parent of current block. // pass_median_time(input_cell's block) starts with cell_block_number - 1, // which is the parent of input_cell's block let cell_median_timestamp = self.parent_median_time(&info.block_hash); let current_median_time = self.block_median_time(&self.parent_hash); if current_median_time < cell_median_timestamp + timestamp { return Err(TransactionError::Immature).into()); } } None => { return Err(TransactionError::InvalidSince).into()); } } } Ok(()) }
pub fn verify(&self) -> Result<(), Error> { for (cell_meta, input) in self .rtx .resolved_inputs .iter() .zip(self.rtx.transaction.inputs()) { // ignore empty since let since: u64 = input.since().unpack(); if since == 0 { continue; } let since = Since(since); // check remain flags if !since.flags_is_valid() { return Err(TransactionError::InvalidSince).into()); }
// verify time lock self.verify_absolute_lock(since)?; self.verify_relative_lock(since, cell_meta)?; } Ok(()) } }