比特币序数理论

中本聪(satoshis),这里简称,是比特币网络的原子原生货币。一个比特币可以细分为1亿个,并且不能再继续细分。

序数理论关注的是中本聪,赋予个体身份,并允许它们被追踪、转移和赋予意义。

序数理论的优势

  • 序数理论不依赖比特币之外的侧链或代币,并且可以在不对比特币网络进行任何更改的情况下使用。
  • 序数理论赋予每个独一无二的价值,使他们能够作为收藏品被收集和交易。
  • 任意资产,例如 NFT、安全代币、账户或稳定币,可以使用序数作为稳定标识符附加到上。

序数理论的劣势

  • 序数理论给单独的赋予意义,但是比特币网络不鼓励太小的UTXO(Unspend transaction output)存在,如果一个交易的输出太小(一般而言,小于330),网络不会对该交易进行打包。因此,无法仅转移单个。另外,如果想要把连续的3个分开,需要精心设计额外的交易来达成。

  • 序数理论的盛行会导致大量很小的UTXO存在,这加重了比特币网络对UTXO的管理负担,并且这些UTXO作为比特币而言价值很低,归集成本已经超出了本身的价值,因此这部分可以认为从比特币的流通中燃烧了,某种意义上减少了比特币的供应量。(也许这并不是劣势:))

的稀缺性

序数理论给每个编号,因此,某些特殊意义编号的便有了稀缺性。比特币网络中存在一些周期性的事件,有些很频繁,有些很久才出现一次,自然而然就给赋予了稀缺性。

下面是比特币网络中的周期性事件:

  • 区块 比特币网络平均大约每10分钟出一次块。
  • 难度调整 每2016个区块,大约每2周,比特币网络进行一次挖矿的难度调整。
  • 减半 每21万个区块,大约每4年,每个区块挖矿的奖励减半。
  • 循环 每6次减半,大约每24年,减半的事件会和难度调整事件重合,成为循环,第一次难度重合事件会发生在2032年。

这些周期性事件给了我们下面的稀有性定义:

  • 普通聪 每一个区块里的除了第一个聪的其它聪(索引不为0的聪)。
  • 罕见聪 每一个区块的第一个聪。
  • 稀有聪 每个难度调整周期的第一个聪。
  • 史诗聪 每个减半周期的第一个聪。
  • 传奇聪 每个循环周期的第一个聪。
  • 神话聪 创世区块的第一个聪,只有1个。

的命名

根据的稀有性,可以使用度数表示法给命名:

A°B′C″D‴
│ │ │ ╰─ 聪在区块中的索引
│ │ ╰─── 区块在难度调整周期中的索引
│ ╰───── 区块减半周期中的索引
╰─────── 位于第几个循环,数字从0开始

下面是一些例子。

普通聪:

1°1′1″1‴
│ │ │ ╰─ 不是区块中的第一个聪
│ │ ╰─── 所在区块不是难度调整周期中的第一个区块
│ ╰───── 所在区块不是减半周期里的第一个区块
╰─────── 位于第二个循环

罕见聪:

1°1′1″0‴
│ │ │ ╰─ 区块中的第一个聪
│ │ ╰─── 所在区块不是难度调整周期中的第一个区块
│ ╰───── 所在区块不是减半周期里的第一个区块
╰─────── 位于第二个循环

稀有聪:

1°1′0″0‴
│ │ │ ╰─ 区块中的第一个聪
│ │ ╰─── 难度调整周期中的第一个区块
│ ╰───── 所在区块不是减半周期里的第一个区块
╰─────── 位于第二个循环

史诗聪:

1°0′1″0‴
│ │ │ ╰─ 区块中的第一个聪
│ │ ╰─── 所在区块不是难度调整周期中的第一个区块
│ ╰───── 减半周期里的第一个区块
╰─────── 位于第二个循环

传奇聪:

1°0′0″0‴
│ │ │ ╰─ 区块中的第一个聪
│ │ ╰─── 难度调整周期中的第一个区块
│ ╰───── 减半周期里的第一个区块
╰─────── 位于第二个循环

神话聪:

0°0′0″0‴
│ │ │ ╰─ 区块中的第一个聪
│ │ ╰─── 难度调整周期中的第一个区块
│ ╰───── 减半周期里的第一个区块
╰─────── 位于第一个循环

如果区块索引为0,可以忽略,下面是罕见聪的表示:

1°1′1″
│ │ ╰─── 所在区块不是难度调整周期中的第一个区块
│ ╰───── 所在区块不是减半周期里的第一个区块
╰─────── 位于第二个循环

除了按照度的表示方法命名,聪最直接的命名方式是用序数来表示,每个聪的序数如何获取将在后面说明。另外还可以用a-z的字符对的序号进行编码得到字符名称等。

其它稀缺性的定义

除了根据比特币的周期性事件来定义的稀缺性,还有其它的维度,比如:

  • 中本聪本人挖掘出来的
  • 序数对称或者回文
  • 序数末尾有多个0
  • 序数在数学上有特殊意义的
  • 和特定历史事件关联的
  • 每个区块的最后一个
  • 等等

如何给编号

每个都按其开采顺序从0开始进行序列编号。这些数字被称为序数,给出了总供应中每个聪的顺序。 序数这个词非常明确,因为它在比特币协议的其它地方没有使用。

根据交易中输入和输出的大小和顺序,交易输入中的聪序号以先进先出的顺序传输到输出。在普通交易中,并没有创建新的,因此只是的转移。

如果使用与当前UTXO集合中的输出相同的交易ID来挖掘交易,则遵循比特币核心的行为,新的交易输出将取代旧的UTXO集合条目,从而破坏第一笔交易的任何未使用输出中包含的。该规则需要处理具有重复交易ID的两对主网交易,即区块91812/91842和91722/91880的coinbase交易,这些交易是在BIP-34使得不可能创建具有重复ID的交易之前开采的。

出于算法实现的目的,Coinbase 交易被认为具有大小等于区块奖励的隐式输入,然后是块中每个付费交易的输入,按照这些交易出现在块中的顺序。隐试区块奖励输入包含该区块新创建的。隐试费用输入则包含在区块交易中作为费用支付的

Coinbase中少付区块奖励不会改变后续区块中开采的序数。序号仅取决于可以开采的数量,而不是实际开采的数量。

源码阅读

首先是一些基本结构的定义:

  • 减半周期(Epoch)
// 减半周期用u32表示
pub struct Epoch(pub u32);

impl Epoch {
// 34个减半周期的第一个聪的序号
// 第一个减半周期挖出来的聪 = 每个周期2100万个区块 * 每个区块50个比特币奖励;随后每个减半周期奖励逐次减半...
pub const STARTING_SATS: [Sat; 34] = [
Sat(0),
Sat(1050000000000000),
Sat(1575000000000000),
Sat(1837500000000000),
// ......
Sat(2099999997060000),
Sat(2099999997480000),
Sat(Sat::SUPPLY),
];

// 奖励结束后的第一个周期
pub const FIRST_POST_SUBSIDY: Epoch = Self(33);

// 减半周期的奖励
pub fn subsidy(self) -> u64 {
if self < Self::FIRST_POST_SUBSIDY {
// 每次减半,奖励减半
// COIN_VALUE = 100,000,000
(50 * COIN_VALUE) >> self.0
} else {
// 33次减半后,区块奖励为0
0
}
}

// 减半周期的第一个聪
pub fn starting_sat(self) -> Sat {
*Self::STARTING_SATS.get(usize::try_from(self.0).unwrap()).unwrap_or_else(|| Self::STARTING_SATS.last().unwrap())
}

// 减半周期的第一个区块高度
pub fn starting_height(self) -> Height {
Height(self.0 * SUBSIDY_HALVING_INTERVAL)
}
}

// 类型转换 Sat -> Epoch
// 聪所位于的减半周期
impl From<Sat> for Epoch {
fn from(sat: Sat) -> Self {
if sat < Self::STARTING_SATS[1] {
Epoch(0)
} else if sat < Self::STARTING_SATS[2] {
Epoch(1)
}
// ......
else if sat < Self::STARTING_SATS[33] {
Epoch(32)
} else {
Epoch(33)
}
}
}

// 类型转换 Height -> Epoch
// 区块所位于的减半周期
impl From<Height> for Epoch {
// SUBSIDY_HALVING_INTERVAL = 210,000
Self(height.0 / SUBSIDY_HALVING_INTERVAL)
}
  • 区块高度
pub struct Height(pub u32);

impl Height {
// 区块高度的u32表示
pub fn n(self) -> u32 {
self.0
}

// 区块奖励
pub fn subsidy(self) -> u64 {
Epoch::from(self).subsidy()
}

// 区块的第一个聪
pub fn starting_sat(self) -> Sat {
let epoch = Epoch:::from(self);
let epoch_starting_sat = epoch.starting_sat();
let epoch_starting_hegiht = epoch.starting_height();
// 减半周期第一个聪索引 + 减半周期内的区块数 * 区块奖励
epoch_starting_sat + u64::from(self.n() - epoch_starting_height.n()) * epoch.subsidy()
}

// 区块在难度调整周期内的索引
pub fn period_offset(self) -> u32 {
// DIFFCHANGE_INTERVAL = 2016
self.0 % DIFFCHANGE_INTERVAL
}
}
// 使用整数序数来表示聪
pub struct Sat(pub u64);

impl Sat {
// 比特币总量无限接近2100万个
pub const SUPPLY: u64 = 2099999997690000;

// 最后一个聪的索引
pub const LAST: Self = Self(Self::SUPPLY - 1);

// 聪的序数表示
pub fn n(self) -> u64 {
self.0
}

// 聪的度表示
pub fn degree(self) -> Degree {
self.into()
}

// 聪在当前减半周期中的位置
pub fn epoch_position(self) -> u64 {
self.0 - self.epoch().starting_sat().0
}

// 聪所位于的区块高度
pub fn height(self) -> Height {
// 当前减半周期起始区块索引 + 聪在当前减半周期索引/每个区块奖励
self.epoch().starting_height()
+ u32::try_from(self.epoch_position() / self.epoch().subsidy()).unwrap()
}

// 聪位于第几个循环
pub fn cycle(self) -> u32 {
Epoch::from(self).0 / CYCLE_EPOCHS
}

// 聪位于第几个减半周期
pub fn epoch(self) -> Epoch {
self.into()
}

// 聪位于第几个难度调整周期
pub fn period(self) -> u32 {
self.height().n() / DIFFCHANGE_INTERVAL
}

// 聪在区块中的索引
pub fn third(self) -> u64 {
self.epoch_position() % self.epoch().subsidy()
}
}
  • 的度表示法
pub struct Degree {
pub hour: u32,
pub minute: u32,
pub second: u32,
pub third: u64,
}

impl From<Sat> for Degree {
fn from(sat: Sat) -> Self {
// 区块高度
let height = sat.height().n();
Degree {
// CYCLE_EPOCHS = 6
hour: height / (CYCLE_EPOCHS * SUBSIDY_HALVING_INTERVAL),
minute: height % SUBSIDY_HALVING_INTERVAL,
second: height % DIFFCHANGE_INTERVAL,
third: sat.third(),
}
}
}
  • 稀有性
pub enum Rarity {
Common,
Uncommon,
Rare,
Epic,
Legendary,
Mythic,
}

impl From<Sat> from Rarity {
fn from(sat: Sat) -> Self {
let Degree {
hour,
minute,
second,
third
} = sat.degree();
}

if hour == 0 && minute == 0 && second == 0 && third == 0 {
Self::Mythic
} else if minute == 0 && second == 0 && third == 0 {
Self::Legendary
} else if minute == 0 && third == 0 {
self::Epic
} else if second == 0 && third == 0 {
Self::Rare
} else if third == 0 {
Self::Uncommon
} else {
Self::Common
}
}

为比特币网络建立序数索引

在比特币网络中,位于UTXO中,很自然地,我们想要能够快速检索某个在哪个UTXO中,以及某个UTXO中包含哪些
对于前者,如果要给每个索引的话,会消耗巨大的存储空间,一般是记录稀有对应的UTXO。下面是ord的具体实现。

impl<'index> Updater<'index> {
pub(crate) fn update_index(&mut self, mut wtx: WriteTransaction) -> Result {
// 省略部分初始化代码...

// rx是区块数据接收channel
let rx: Receiver<BlockData> = Self::fetch_block_from(self.index. self.height)?;

// output_sender是OutPoint发送channel
// txout_receiver是TxOut接收channel
// spawn_fetcher接收output_sender发送的OutPoint,从btc网络中下载对应的TxOut
let (mut output_sender: Sender<OutPoint>, mut txout_receiver: Receiver<TxOut>) = Self::spawn_fetcher(self.index);

// utxo_cache是我们最终想得到的,OutPoint是每个交易的输出(utxo),而UtxoEntryBuf则记录了该输出包含哪些聪
let mut utxo_cache: HashMap<OutPoint, UtxoEntryBuf> = HashMap::new();
while let Ok(block: BlockData) = rx.recv() {
self.index_block(
&mut output_sender,
&mut txout_receiver,
// 数据库写操作事务
&mut wtx,
block,
// 返回结果
&mut utxo_cache,
)?;

// 省略后续持久化代码...
}
}
}

index_block函数调用了index_utxo_entries,下面是其实现:

fn index_utxo_entrie(
&mut self,
block: BlockData,
txout_receiver: &mut broadcast::Receiver<TxOut>,
output_sender: &mut mpsc::Sender<OutPoint>,
wtx: &mut WriteTransaction,
utxo_cache: &mut HashMap<OutPoint, UtxoEntryBuf>,
...
) -> Result<(), Error> {
// 省略部分代码...

// coinbase交易的输入
let mut coinbase_inputs = Vec::new();

// 丢失的聪
let mut lost_sat_ranges = Vec::new();

// 初始化coinbase的输入聪
let h = Height(self.height);
if h.subsidy() > 0 {
let start: Sat = h.starting_sat();
coinbase_inputs.extend(SatRange::store((start.n(), (start + h.subsidy()).n())));
}

// 遍历区块内的所有交易
for (tx_offset, (tx, txid)) in block
.txdata
.iter()
.enumerate()
// 把coinbase交易放到最后,之所以放到最后处理是因为coinbase交易的输入包含其它交易的输出(手续费),
// 因此需要先处理其它交易,收集到手续费输出后再处理coinbase交易
.skip(1)
.chain(block.txdata.iter().enumerate().take(1)) {
let input_utxo_entries: Vec<UtxoEntryBuf> = if tx_offset == 0 {
Vec::new()
} else {
tx.input.iter().map(|input| {
let outpoint = input.previous_output.store();
// 移除作为交易输入的utxo
let entry = if let Some(entry) = utxo_cache.remove(&OutPoint::load(output)) {
entry
} else if let Some(entry) = outpoint_to_utxo_entry.remove(&outpoint)? {
entry.value().to_buf()
}
Ok(entry)
}).collect::<Result<Vec<UtxoEntryBuf>>>()?
};

// 编码后的UtxoEntry转未编码
let input_utxo_entries: Vec<ParsedUtoxEntry<'_>> = input_utxo_entries
.iter()
.map(|entry: &UtxoEntryBuf| entry.parse(self.index))
.collect::<Vec<ParsedUtxoEntry>>();

// sat_range 本来是(u64, u64)结构,被编码为11字节的[u8]来表示
let input_sat_ranges: Option<Vec<&[u8]>>;

// 输入sats - 输出sats
let leftover_sat_ranges: &mut Vec<u8>;

if tx_offset = 0 {
// coinbase交易
input_sat_ranges = Some(vec![coinbase_inputs.as_slice()]);
// coinbase交易把所有的普通交易燃烧掉的sats都当成了手续费,如果coinbase交易本身
// 还有燃烧的话,就会记录到lost_sat_ranges里面去,这时就真的燃烧了
leftover_sat_ranges = &mut lost_sat_ranges;
} else {
input_sat_ranges = Some(
input_utxo_entrie
.iter()
.map(|entry| entry.sat_ranges())
.collect(),
);
// 对于普通交易来说,未使用的聪会作为coinbase的手续费,
// 这里通过leftover_sat_ranges来收集这些手续费
leftover_sat_ranges = &mut coinbase_inputs;
}

self.index_transaction_sats(
tx,
*txid,
&mut output_utxo_entries,
input_sat_ranges.as_ref().unwrap(),
leftover_sat_ranges,
...
)?;

// 更新燃烧的聪
if !lost_sat_ranges.is_empty() {
// lost-sats是特殊的utxo,要和之前的合并
let utxo_entry = utxo_cache
.entry(OutPoint::null())
.or_insert(UtxoEntryBuf::empty(self.index));
for chunk in lost_sat_ranges.chunks_exact(11) {
let (start, end) = SatRange::load(chunk.try_into().unwrap());
if !Sat(start).common() {
// 特殊聪索引
sat_to_satpoint.insert(
&start,
&SatPoint {
outpoint: OutPoint::null(),
offset: lost_sats,
}.store()
)?;
}
lost_sats += end - start;
}
let mut new_utxo_entry = UtxoEntryBuf::new();
new_utxo_entry.push_sat_ranges(&lost_sat_ranges, self.index);
*utxo_entry = UtxoEntryBuf::merged(utxo_entry, &new_utxo_entry, self.index);
}
}
}


fn index_transaction_sats(
&mut self,
tx: &Transaction,
txid: Txid,
output_utxo_entries: &mut [UtxoEntryBuf],
input_sat_ranges: &[&[u8]],
leftover_sat_ranges: &mut Vec<u8>,
...
) -> Result {
// 当前正在处理的输入聪范围
let mut pending_input_sat_range = None;
// 聪范围转为1维
let mut input_sat_ranges_iter = input_sat_ranges
.iter()
.flat_map(|slice| slice.chunks_exact(11));

// 预分配输出的聪数组,单个输出最大不可能超过输入之和
let mut sats = Vec::with_capacity(
input_sat_ranges
.iter()
.map(|slice| slice.len())
.sum::<usize>(),
);

// 处理每一个输出,分配聪序号
for (vout, output) in tx.output.iter().enumerate() {
// 新的 utxo
let outpoint = OutPoint {
vout: vout.try_into().unwrap(),
txid,
};

// 剩余需要分配的聪
let mut remaining = output.value.to_sat();
while remaining > 0 {
// 从当前正在处理的聪范围进行分配
let range = pending_input_sat_range.take().unwrap_or_else(|| {
// 当前聪范围从输入的聪范围数组里来
SatRange::load(
input_sat_ranges_iter
.next()
.expec("insufficient inputs for transaction outputs")
.try_into()
.unwrap(),
)
});

if !Sat(range.0).common() {
// 特殊聪索引
sat_to_satpoint.insert(
key: &range.0,
value: &SatPoint {
outpoint,
offset: output.value.to_sat() - remaining,
}.store(),
)?;
}

// 当前聪范围包含的聪有多少
if count = range.1 - range.0;
// 当前聪范围使用了多少
let assigned = if count > remaining {
let middle = range.0 + remaining;
pending_input_sat_range = Some((middle, range.1));
// 使用部分
(range.0, middle)
} else {
// 使用全部
range
};
// 更新总的输出聪
sats.extend_from_slice(&assigned.store());
// 更新当前utxo剩余需要分配的聪
remaining -= assigned.1 - assigned.0;
}

output_utxo_entries[vout].push_sat_ranges(&sats, self.index);
// 清空当前输出的聪范围对
sats.clear();
}

// 还有哪些聪剩下?剩下的要么作为手续费,要么就永久燃烧了
if let Some(range) = pending_input_sat_range {
leftover_sat_ranges.extend(&range.store());
}
leftover_sat_ranges.extend(input_sat_ranges_iter.flatten());

Ok(())
}