比特币符文

比特币符文是比特币链上新资产发行的协议,它允许使用比特币交易来发行、铸造和转移比特币原生的数字货币。

符石 (Runestone)

符文协议的信息,称为符石(runestone),存储在比特币交易的输出UTXO中。

  • 符石输出的脚本(Script PublicKey)以OP_RETURN开头,后面跟OP_13,然后是0个或者多个数据推入。这些推入的数据是经过编码的128位的整数。
  • 符石包含的信息,可以声明发行一个新的符文,或者铸造已经存在的符文,以及从交易的输入UTXO转移符文到输出UTXO。
  • 一个交易的输出UTXO可以持有多个符文。
  • 符文通过ID来唯一辨识,ID由符文发行的交易索引和区块高度组成,用文本BLOCK:TX来表示。比如,在第500个区块里的第20个交易发行的符文的id是500:20
1
2
3
4
5
6
7
8
9
10
11
12
13
// 符石声明
pub struct Runestone {
pub etching: Option<Etching>, // 发行
pub edicts: Vec<Edict>, // 转移
pub mint: Option<RuneId>, // 铸币
pub pointer: Option<u32>, // 未分配符文归属哪个输出
}

// 符文ID声明
pub struct RuneId {
pub block: u64,
pub tx: u32,
}

符石解译

从交易中解译符石包含如下步骤:

  1. 找到交易中锁定脚本(script pubkey)以OP_RETURN OP_13开头的第一个输出。
  2. 把接下来的数据推入合并成一个字节数组。
  3. 把字节数据按照小端编码解码128位的整数数组。
  4. 解析整数数组得到Message信息。
  5. 解析Mesage信息得到符石。

解译过程中有可能会得到非法的符石,即纪念碑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// 无类型信息
pub(super) struct Message {
pub(super) flaw: Option<Flaw>, // 解析错误
pub(super) edicts: Vec<Edict>, // 法令:存储转移信息
pub(super) fields: HashMap<u128, VecDeque<u128>>, // 字段:存储发行和铸造信息
}

// 符石标签
enum Tag {
// Body 标签标记符石的结尾,Body之后的所有数据被解译为法令
Body = 0,
// Flags 是发行标记位,标记的位置是 1 << Flag_Value
// Flags 的值可为:
// enum Flag {
// Etching = 0,
// Terms = 1,
// Turbo = 2,
// Cenotaph = 127,
// }
Flags = 2,
// 符文名称
Rune = 4,
// 预铸造
Premine = 6,
// 允许的公开铸造次数
Cap = 8,
// 每次铸造的数量
Amount = 10,
HeightStart = 12,
HeightEnd = 14,
OffsetStart = 16,
OffsetEnd = 18,
// 铸造
Mint = 20,
Pointer = 22,
Cenotaph = 126,

Divisibility = 1,
Spacers = 3,
Symbol = 5,
Nop = 127,
}

impl Message {
// 从整数数组中解译Message
pub(super) fn from_integers(tx: &Transaction, payload: &[u128]) -> Self {
let mut edicts = Vec::new();
let mut fields: HashMap<u128, VecDeque<u128>> = HashMap::<u128, VecDeque<u128>>::new();
let mut flaw = None;

// 整数数组以key-value的方式存储
for i in (0..payload.len()).step_by(2) {
let tag = payload[i];

if Tag::Body == tag {
// Body 后的数据全部都是转移法令
let mut id = RuneId::default();
// 转移法令包含4个数:符文区块高度、符文交易索引、转移数量、输出索引
for chunk in payload[i + 1..].chunks(4) {
if chunk.len() != 4 {
flaw.get_or_insert(Flaw::TrailingIntegers);
break;
}

// 增量编码ID
let Some(next) = id.next(chunk[0], chunk[1]) else {
flaw.get_or_insert(Flaw::EdictRuneId);
break;
};

// 构造转移法令
let Some(edict) = Edict::from_integers(tx, next, chunk[2], chunk[3]) else {
flaw.get_or_insert(Flaw::EdictOutput);
break;
};

id = next;
edicts.push(edict);
}
break;
}

// 其它key-value对都只有一个
let Some(&value) = payload.get(i + 1) else {
flaw.get_or_insert(Flaw::TruncatedField);
break;
};

fields.entry(tag).or_default().push_back(value);
}

Self {
flaw,
edicts,
fields,
}
}
}

// 二进制符石
enum Payload {
Valid(Vec<u8>),
Invalid(Flaw),
}

// 符石实现
impl Runestone {
pub const MAGIC_NUMBER: opcodes::Opcode = opcodes::all::OP_PUSHNUM_13;
pub const COMMIT_CONFIRMATIONS: u16 = 6;

// 从交易中提取符石原始数据块
fn payload(transaction: &Transaction) -> Option<Payload> {
// 搜索每个输出
for output in &transaction.output {
// 锁定脚本
let mut instructions = output.script_pubkey.instructions();

// 锁定脚本需要以 OP_RETURN 开头
if instructions.next() != Some(Ok(Instruction::Op(opcodes::all::OP_RETURN))) {
continue;
}

// 后面紧接着 OP_PUSHNUM_13
if instructions.next() != Some(Ok(Instruction::Op(Runestone::MAGIC_NUMBER))) {
continue;
}

// 后续数据push串联到一起
let mut payload = Vec::new();

for result in instructions {
match result {
Ok(Instruction::PushBytes(push)) => {
payload.extend_from_slice(push.as_bytes());
}
Ok(Instruction::Op(_)) => {
return Some(Payload::Invalid(Flaw::Opcode));
}
Err(_) => {
return Some(Payload::Invalid(Flaw::InvalidScript));
}
}
}

return Some(Payload::Valid(payload));
}

None
}

// 原始数据块 => 整数数组
fn integers(payload: &[u8]) -> Result<Vec<u128>, varint::Error> {
let mut integers = Vec::new();
let mut i = 0;

while i < payload.len() {
let (integer, length) = varint::decode(&payload[i..])?;
integers.push(integer);
i += length;
}

Ok(integers)
}

// 符石解析
pub fn decipher(transaction: &Transaction) -> Option<Artifact> {
// 获取原始数据块
let payload = match Runestone::payload(transaction) {
Some(Payload::Valid(payload)) => payload,
Some(Payload::Invalid(flaw)) => {
return Some(Artifact::Cenotaph(Cenotaph {
flaw: Some(flaw),
..default()
}));
}
None => return None,
};

// 解析数组
let Ok(integers) = Runestone::integers(&payload) else {
return Some(Artifact::Cenotaph(Cenotaph {
flaw: Some(Flaw::Varint),
..default()
}));
};

// 从数据解译裸Message
let Message {
mut flaw,
edicts,
mut fields,
} = Message::from_integers(transaction, &integers);

// 获取发行标记位
let mut flags = Tag::Flags
.take(&mut fields, |[flags]| Some(flags))
.unwrap_or_default();
// 发行信息
let etching = Flag::Etching.take(&mut flags).then(|| Etching {
divisibility: Tag::Divisibility.take(&mut fields, |[divisibility]| {
let divisibility = u8::try_from(divisibility).ok()?;
(divisibility <= Etching::MAX_DIVISIBILITY).then_some(divisibility)
}),
premine: Tag::Premine.take(&mut fields, |[premine]| Some(premine)),
rune: Tag::Rune.take(&mut fields, |[rune]| Some(Rune(rune))),
spacers: Tag::Spacers.take(&mut fields, |[spacers]| {
let spacers = u32::try_from(spacers).ok()?;
(spacers <= Etching::MAX_SPACERS).then_some(spacers)
}),
symbol: Tag::Symbol.take(&mut fields, |[symbol]| {
char::from_u32(u32::try_from(symbol).ok()?)
}),
terms: Flag::Terms.take(&mut flags).then(|| Terms {
cap: Tag::Cap.take(&mut fields, |[cap]| Some(cap)),
height: (
Tag::HeightStart.take(&mut fields, |[start_height]| {
u64::try_from(start_height).ok()
}),
Tag::HeightEnd.take(&mut fields, |[start_height]| {
u64::try_from(start_height).ok()
}),
),
amount: Tag::Amount.take(&mut fields, |[amount]| Some(amount)),
offset: (
Tag::OffsetStart.take(&mut fields, |[start_offset]| {
u64::try_from(start_offset).ok()
}),
Tag::OffsetEnd.take(&mut fields, |[end_offset]| u64::try_from(end_offset).ok()),
),
}),
turbo: Flag::Turbo.take(&mut flags),
});

// 铸币信息
let mint = Tag::Mint.take(&mut fields, |[block, tx]| {
RuneId::new(block.try_into().ok()?, tx.try_into().ok()?)
});

let pointer = Tag::Pointer.take(&mut fields, |[pointer]| {
let pointer = u32::try_from(pointer).ok()?;
(u64::from(pointer) < u64::try_from(transaction.output.len()).unwrap()).then_some(pointer)
});

if etching
.map(|etching| etching.supply().is_none())
.unwrap_or_default()
{
flaw.get_or_insert(Flaw::SupplyOverflow);
}

if flags != 0 {
flaw.get_or_insert(Flaw::UnrecognizedFlag);
}

if fields.keys().any(|tag| tag % 2 == 0) {
flaw.get_or_insert(Flaw::UnrecognizedEvenTag);
}

if let Some(flaw) = flaw {
return Some(Artifact::Cenotaph(Cenotaph {
flaw: Some(flaw),
mint,
etching: etching.and_then(|etching| etching.rune),
}));
}
// 构造符石
Some(Artifact::Runestone(Self {
edicts,
etching,
mint,
pointer,
}))
}

// 符石编码进入锁定脚本
pub fn encipher(&self) -> ScriptBuf {
let mut payload = Vec::new();

// 发行信息编码
if let Some(etching) = self.etching {
let mut flags = 0;
Flag::Etching.set(&mut flags);

if etching.terms.is_some() {
Flag::Terms.set(&mut flags);
}

if etching.turbo {
Flag::Turbo.set(&mut flags);
}
// 发行位编码,key-value写入payload
Tag::Flags.encode([flags], &mut payload);

// rune 名称编码
Tag::Rune.encode_option(etching.rune.map(|rune| rune.0), &mut payload);
Tag::Divisibility.encode_option(etching.divisibility, &mut payload);
Tag::Spacers.encode_option(etching.spacers, &mut payload);
Tag::Symbol.encode_option(etching.symbol, &mut payload);
Tag::Premine.encode_option(etching.premine, &mut payload);

// 公开铸造条件编码
if let Some(terms) = etching.terms {
Tag::Amount.encode_option(terms.amount, &mut payload);
Tag::Cap.encode_option(terms.cap, &mut payload);
Tag::HeightStart.encode_option(terms.height.0, &mut payload);
Tag::HeightEnd.encode_option(terms.height.1, &mut payload);
Tag::OffsetStart.encode_option(terms.offset.0, &mut payload);
Tag::OffsetEnd.encode_option(terms.offset.1, &mut payload);
}
}

// 铸造编码
if let Some(RuneId { block, tx }) = self.mint {
Tag::Mint.encode([block.into(), tx.into()], &mut payload);
}

// 指针编码
Tag::Pointer.encode_option(self.pointer, &mut payload);

// 转移指令编码
if !self.edicts.is_empty() {
// 以Tag::Body开头
varint::encode_to_vec(Tag::Body.into(), &mut payload);

let mut edicts = self.edicts.clone();
// 先根据id排序
edicts.sort_by_key(|edict| edict.id);

let mut previous = RuneId::default();
for edict in edicts {
// 增量编码
let (block, tx) = previous.delta(edict.id).unwrap();
// 编码4个数
varint::encode_to_vec(block, &mut payload);
varint::encode_to_vec(tx, &mut payload);
varint::encode_to_vec(edict.amount, &mut payload);
varint::encode_to_vec(edict.output.into(), &mut payload);
previous = edict.id;
}
}

// payload -> script
let mut builder = script::Builder::new()
.push_opcode(opcodes::all::OP_RETURN)
.push_opcode(Runestone::MAGIC_NUMBER);

for chunk in payload.chunks(u32::MAX.try_into().unwrap()) {
let push: &script::PushBytes = chunk.try_into().unwrap();
builder = builder.push_slice(push);
}

builder.into_script()
}
}

发行 (Etching)

通过发行可以创造符文。在发行的时候可以设置符文的属性,一旦设置好了,任何人,包括发行人都不可以改变这些属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub struct Etching {
pub divisibility: Option<u8>, // 可分性
pub premine: Option<u128>, // 预挖掘
pub rune: Option<Rune>, // 符文名称
pub spacers: Option<u32>, // 分隔符
pub symbol: Option<char>, // 符号
pub terms: Option<Terms>, // 公开铸造条款
pub turbo: bool, // 是否可升级
}

impl Etching {
pub const MAX_DIVISIBILITY: u8 = 38;
pub const MAX_SPACERS: u32 = 0b00000111_11111111_11111111_11111111;

// 供应量 = 预铸造 + 每次铸造数量 x 最大铸造次数
pub fn supply(&self) -> Option<u128> {
let premine = self.premine.unwrap_or_default();
let cap = self.terms.and_then(|terms| terms.cap).unwrap_or_default();
let amount = self
.terms
.and_then(|terms| terms.amount)
.unwrap_or_default();
premine.checked_add(cap.checked_mul(amount)?)
}
}

名称

符文名称由A-Z的字母组成,在1-16个字符长度之间。名字可以包含分隔符来增强可读性。注意,分隔符和名字唯一性不相干,只是增强可读性而已,相同字母顺序的符文,即使有不同的分隔符,都被看成是相同的。分隔符不算在符文名字的长度里。

符石的名称可以通过26进制的整数来编码:

Name Encoding
A 0
B 1
Y 24
Z 25
AA 26
AB 27
AY 50
AZ 51
BA 52

依次类推。

如果发行的时候忽略符文名称,会按照下述方式生成默认的名称:

1
2
3
4
fn reserve(block: u64, tx: u32) -> Rune {
Rune(6402364363415443603228541259936211926
+ (u128::from(block) << 32 | u128::from(tx))
}

6402364363415443603228541259936211926对应的是符文名称AAAAAAAAAAAAAAAAAAAAAAAAAA。最开始的时候,小于AAAAAAAAAAAAAAAAAAAAAAAAAAA及其的符文名称被锁定。新的符文名称必须在其被锁定的区块到来后才能使用。在最开始,所有的符文名称都不小于13个字符,新的符文名称在840,000区块开始解锁,这个区块也是符文协议生效的区块。在这之后,每17,500个区块周期,下一个最短的符文名称陆续被解锁。所以,在区块840,000和区块857,500之间,12字符的符文名字被解锁;在区块857,500和区块 875,000 之间,11字符的符文名字被解锁,依此类推,直到在区块1,032,500和区块1,050,000之间,1个字符的符文名字被解锁。

为了防止提前发布发行交易,如果一个非预留名称的符文被发行,发行的交易中必须包含一个已经提交的符文名称证明。这个证明需要在输入的见证中包含一个以小端整数编码的符文名称,而这个输入花费的输出必须要有6个以上的区块链确认。如果不存在这样的一个证明,那么发行会被认为无效。

可分性

可分性定义了符文的原子大小。可分性表示为符文数量中小数点后允许的位数。可分性为0的符文不可以被细分;可分性为1的符文可以被细分为10个原子单位;可分性为1的符文可以被细分为100个原子单位,一次类推。

符号

符文的货币符号是一个单独的Unicode编码,比如$,或者🧿,在符文数量的后面展示。

一个可分性为2的符号为🧿的符文的101个原子货币表示为 1.01🧿
如果一个符文没定义符号,默认的符号是通用货币符号¤,这个符号也称为圣甲虫。

预铸造

发行符文的时候可以把供应量的部分预留给发行人,这个称为预铸造。

条款(Terms)

1
2
3
4
5
6
pub struct Terms {
pub amount: Option<u128>,
pub cap: Option<u128>,
pub height: (Option<u64>, Option<u64>),
pub offset: (Option<u64>, Option<u64>),
}

符文可以公开铸造,允许任何人去获取一部分供应。公开铸造涉及到条款,条款在发行的时候来指定。

只有在所有条款都满足的条件下才可以铸造,任何一个条款不满足的情况下铸造终止。比如,一个铸造可以要求在某个起始区块和某个终止区块内进行,并且设定了铸造次数的上限。那么,只有在这两个区块之间,并且铸造次数没达到设定的上限时,铸造才可以进行。

最大铸造次数 (Cap)

一个符文可以铸造的最大次数,达到最大铸造次数后,铸造关闭。

单次铸造量 (Amount)

每个铸造创造固定量的货币。

开始区块 (Start Height)

铸造从该区块开始

结束区块 (End Height)

在该区块及之后的区块,铸造都不能进行

开始区块偏移 (Start Offset)

指定符文可以开始铸造的相对区块,相对于符文被发行的区块而言。

结束区块偏移 (End Offset)

指定服务不可以铸造的相对区块,相对于符文被发行的区块而言

下面是发行的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 发行 rune
fn etched(
&mut self,
tx_index: u32,
tx: &Transaction,
artifact: &Artifact,
) -> Result<Option<(RuneId, Rune)>> {
let rune = match artifact {
Artifact::Runestone(runestone) => match runestone.etching {
Some(etching) => etching.rune,
None => return Ok(None),
},
// 纪念碑里的发行也是有效的
Artifact::Cenotaph(cenotaph) => match cenotaph.etching {
Some(rune) => Some(rune),
None => return Ok(None),
},
};

let rune = if let Some(rune) = rune {
if rune < self.minimum // 最小长度要求
|| rune.is_reserved() // 符文名称是否是保留的
|| self.rune_to_id.get(rune.0)?.is_some() // 符文是否已经存在
|| !self.tx_commits_to_rune(tx, rune)? // 是否抢跑
{
return Ok(None);
}
rune
} else {
let reserved_runes = self
.statistic_to_count
.get(&Statistic::ReservedRunes.into())?
.map(|entry| entry.value())
.unwrap_or_default();

self
.statistic_to_count
.insert(&Statistic::ReservedRunes.into(), reserved_runes + 1)?;

Rune::reserved(self.height.into(), tx_index)
};

Ok(Some((
RuneId {
block: self.height.into(),
tx: tx_index,
},
rune,
)))
}

// 检查符文名称证明
fn tx_commits_to_rune(&self, tx: &Transaction, rune: Rune) -> Result<bool> {
// 符文名称编码
// 该编码需要提前commit
let commitment = rune.commitment();

for input in &tx.input {
// extracting a tapscript does not indicate that the input being spent
// was actually a taproot output. this is checked below, when we load the
// output's entry from the database
let Some(tapscript) = input.witness.tapscript() else {
continue;
};

for instruction in tapscript.instructions() {
// ignore errors, since the extracted script may not be valid
let Ok(instruction) = instruction else {
break;
};

let Some(pushbytes) = instruction.push_bytes() else {
continue;
};

// 找到commit名称的交易输出
if pushbytes.as_bytes() != commitment {
continue;
}

let Some(tx_info) = self
.client
.get_raw_transaction_info(&input.previous_output.txid, None)
.into_option()?
else {
panic!(
"can't get input transaction: {}",
input.previous_output.txid
);
};

let taproot = tx_info.vout[input.previous_output.vout.into_usize()]
.script_pub_key
.script()?
.is_p2tr();

if !taproot {
continue;
}

let commit_tx_height = self
.client
.get_block_header_info(&tx_info.blockhash.unwrap())
.into_option()?
.unwrap()
.height;

let confirmations = self
.height
.checked_sub(commit_tx_height.try_into().unwrap())
.unwrap()
+ 1;

if confirmations >= Runestone::COMMIT_CONFIRMATIONS.into() {
return Ok(true);
}
}
}

Ok(false)
}

铸造 (Minting)

当一个符文可以公开铸造时,任何人都可以通过铸造交易创造固定量的该符文的新货币。

  • 符石通过在Mint字段指定符文ID来铸造符文。
  • 如果铸造是公开的,铸造的符文会加到交易输入的未分配符文中。
  • 这些符文可以使用edicts来进行转移,否则的话,会被转移到第一个非OP_RETURN输出,或者使用Pointer字段来指定输出。
  • 铸造可以在发行后在任意的交易里进行,包括在发行的区块。

下面是铸造的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 铸造
fn mint(&mut self, id: RuneId) -> Result<Option<Lot>> {
// id_to_entry是rune id -> rune的存储表
let Some(entry) = self.id_to_entry.get(&id.store())? else {
return Ok(None);
};

// 加载符文
let mut rune_entry = RuneEntry::load(entry.value());

// 判断是否处于公开铸造
let Ok(amount) = rune_entry.mintable(self.height.into()) else {
return Ok(None);
};

drop(entry);

// 更新铸造信息
rune_entry.mints += 1;

self.id_to_entry.insert(&id.store(), rune_entry.store())?;

Ok(Some(Lot(amount)))
}

转移 (Transfering)

当交易的输入包含符文,或者新的符文被预铸造或者公开铸造时,这些符文被转移到了交易的输出。符文石中的法令Edicts负责定义输入如何转移到输出。

法令 (Edicts)

一个符文石可以包含任意数量的法令。法令由符文ID,符文数量以及一个输出数量组成。法令按顺序进行处理,将未分配的符文分配到交易输出。

1
2
3
4
5
pub struct Edict {
pub id: RuneId,
pub amount: u128,
pub output: u32,
}

符文ID中的区块高度和交易索引都采用增量编码。法令中的第一个符文ID的区块高度是绝对高度,接下来的ID编码分别是和上个ID的差值。如果差值为0,交易ID的索引也为差值,否则就是绝对索引。

下面是一个包含4个法令的例子:

block TX amount output
10 5 5 1
50 1 25 4
10 7 1 8
10 5 10 3

首先对法令按照区块高度和交易索引进行排序:

block TX amount output
10 5 5 1
10 5 10 3
10 7 1 8
50 1 25 4

接着进行增量编码:

block TX amount output
10 5 5 1
0 0 10 3
0 2 1 8
50 1 25 4

下面是符文转移的一些规则:

  • 如果一个法令分配了大于未分配状态的符文,amount 会被缩减为可分配的最大数量。
  • 在发行符文的时候还不知道符文的ID,因此可以用0:0来表示当前交易里发行的符文。
  • 指令中如果amount指定为0,那么会分配所有的未分配符文。
  • 指令中output如果等于输出utxo数量,会按次序给每个非OP_RETURN输出分配amount数量的符文。
  • 指令中如果amount指定为0,output等于utxo数量,会把该符文平均分配给每一个非OP_RETURN的输出。如果符文不可整除的话,前面R个输出会多1个符文,R是未分配符文除以非OP_RETURN输出的取余。
  • 如果任何指令的符文IDblock为0,且tx大于0,或者output大于交易输出utxo个数,这个符石被称为纪念碑。
  • 注意到,纪念碑中的指令不会被处理,交易中的所有输入符文都将被燃烧。

指针 (Pointer)

处理完所有法令后,剩余的未分配符文将传输到交易的第一个非 OP_RETURN 输出。符石可以选择包含一个指针替代默认输出的指针。

燃烧 (Burning)

可以通过使用法令或指针将符文传输到OP_RETURN 输出来燃烧符文。

纪念碑

符石可能因多种原因而出现格式错误,包括符石OP_RETURN中的非推送数据操作码、无效的变体或无法识别的符石字段等,格式错误的符文石称为纪念碑.

在包含纪念碑的交易中的符文会被烧毁。在包含纪念碑的交易中蚀刻的符文被设置为不可铸造。在包含纪念碑的交易中的铸币计入铸币上限,但铸造的符文会被烧毁。

纪念碑是一种升级机制,允许符文被赋予新的语义,从而改变符文的创建和传输方式,同时不会误导未升级的客户端这些符文的位置,因为未升级的客户端会看到这些符文已被烧毁。

索引符文

通过索引符文,可以快速查询某个UTXO拥有哪些符文,以及对应的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// rune 索引更新结构体
pub(super) struct RuneUpdater<'a, 'tx, 'client> {
pub(super) block_time: u32,
pub(super) burned: HashMap<RuneId, Lot>,
pub(super) client: &'client Client,
pub(super) event_sender: Option<&'a mpsc::Sender<Event>>,
pub(super) height: u32,
pub(super) id_to_entry: &'a mut Table<'tx, RuneIdValue, RuneEntryValue>,
pub(super) inscription_id_to_sequence_number: &'a Table<'tx, InscriptionIdValue, u32>,
pub(super) minimum: Rune,
pub(super) outpoint_to_balances: &'a mut Table<'tx, &'static OutPointValue, &'static [u8]>,
pub(super) rune_to_id: &'a mut Table<'tx, u128, RuneIdValue>,
pub(super) runes: u64,
pub(super) sequence_number_to_rune_id: &'a mut Table<'tx, u32, RuneIdValue>,
pub(super) statistic_to_count: &'a mut Table<'tx, u64, u64>,
pub(super) transaction_id_to_rune: &'a mut Table<'tx, &'static TxidValue, u128>,
}

impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
// 索引指定交易
pub(super) fn index_runes(&mut self, tx_index: u32, tx: &Transaction, txid: Txid) -> Result<()> {
// 解码该交易内的符石,一个交易只会有一个符石,按照交易输出的顺序进行查找
let artifact = Runestone::decipher(tx);

// 获取输入UTXO里面所有未分配的符文数量
let mut unallocated: HashMap<RuneId, Lot> = self.unallocated(tx)?;

// 先构造输出UTXO需要分配的符文数据结构
let mut allocated: Vec<HashMap<RuneId, Lot>> = vec![HashMap::new(); tx.output.len()];

if let Some(artifact) = &artifact {
if let Some(id) = artifact.mint() {
if let Some(amount) = self.mint(id)? {
// 对于包含铸造事件的符石,在未分配的符文总量中加入铸造的符文
*unallocated.entry(id).or_default() += amount;

if let Some(sender) = self.event_sender {
sender.blocking_send(Event::RuneMinted {
block_height: self.height,
txid,
rune_id: id,
amount: amount.n(),
})?;
}
}
}

// 发行符文
let etched = self.etched(tx_index, tx, artifact)?;

if let Artifact::Runestone(runestone) = artifact {
if let Some((id, ..)) = etched {
// 未分配符文中加入预铸造的符文
*unallocated.entry(id).or_default() +=
runestone.etching.unwrap().premine.unwrap_or_default();
}

// 转移符文
for Edict { id, amount, output } in runestone.edicts.iter().copied() {
let amount = Lot(amount);

// edicts with output values greater than the number of outputs
// should never be produced by the edict parser
let output = usize::try_from(output).unwrap();
assert!(output <= tx.output.len());

let id = if id == RuneId::default() {
// id == 0 并且符石有发行动作
let Some((id, ..)) = etched else {
continue;
};

id
} else {
id
};

// 未分配的符文需要存在
let Some(balance) = unallocated.get_mut(&id) else {
continue;
};

let mut allocate = |balance: &mut Lot, amount: Lot, output: usize| {
if amount > 0 {
*balance -= amount;
*allocated[output].entry(id).or_default() += amount;
}
};

// 如果output指定为输出的个数,会触发特殊效果
if output == tx.output.len() {
// find non-OP_RETURN outputs
let destinations = tx
.output
.iter()
.enumerate()
.filter_map(|(output, tx_out)| {
// 对于所有不是OP_RETURN的输出
(!tx_out.script_pubkey.is_op_return()).then_some(output)
})
.collect::<Vec<usize>>();

if !destinations.is_empty() {
if amount == 0 {
// if amount is zero, divide balance between eligible outputs
let amount = *balance / destinations.len() as u128;
let remainder = usize::try_from(*balance % destinations.len() as u128).unwrap();

for (i, output) in destinations.iter().enumerate() {
allocate(
balance,
if i < remainder { amount + 1 } else { amount },
*output,
);
}
} else {
// if amount is non-zero, distribute amount to eligible outputs
for output in destinations {
allocate(balance, amount.min(*balance), output);
}
}
}
} else {
// Get the allocatable amount
// 如果 amount为0,则分配所有的未分配符文
let amount = if amount == 0 {
*balance
} else {
// 否则指定的amount,如果超过最大可分配量,会截断
amount.min(*balance)
};

allocate(balance, amount, output);
}
}
}

if let Some((id, rune)) = etched {
// 索引新的符文
self.create_rune_entry(txid, artifact, id, rune)?;
}
}

// 燃烧吧,符文
let mut burned: HashMap<RuneId, Lot> = HashMap::new();

if let Some(Artifact::Cenotaph(_)) = artifact {
// 符石如果解译成了纪念碑,燃烧所有未分配符文
for (id, balance) in unallocated {
*burned.entry(id).or_default() += balance;
}
} else {
let pointer = artifact
.map(|artifact| match artifact {
Artifact::Runestone(runestone) => runestone.pointer,
Artifact::Cenotaph(_) => unreachable!(),
})
.unwrap_or_default();

// assign all un-allocated runes to the default output, or the first non
// OP_RETURN output if there is no default
if let Some(vout) = pointer
.map(|pointer| pointer.into_usize())
.inspect(|&pointer| assert!(pointer < allocated.len()))
.or_else(|| {
tx.output
.iter()
.enumerate()
.find(|(_vout, tx_out)| !tx_out.script_pubkey.is_op_return())
.map(|(vout, _tx_out)| vout)
})
{
for (id, balance) in unallocated {
if balance > 0 {
*allocated[vout].entry(id).or_default() += balance;
}
}
} else {
for (id, balance) in unallocated {
if balance > 0 {
*burned.entry(id).or_default() += balance;
}
}
}
}

// update outpoint balances
let mut buffer: Vec<u8> = Vec::new();
for (vout, balances) in allocated.into_iter().enumerate() {
if balances.is_empty() {
continue;
}

// increment burned balances
if tx.output[vout].script_pubkey.is_op_return() {
// 通过pointer可以指定输出balance到OP_RETURN的输出
for (id, balance) in &balances {
*burned.entry(*id).or_default() += *balance;
}
continue;
}

buffer.clear();

let mut balances = balances.into_iter().collect::<Vec<(RuneId, Lot)>>();

// Sort balances by id so tests can assert balances in a fixed order
balances.sort();

let outpoint = OutPoint {
txid,
vout: vout.try_into().unwrap(),
};

for (id, balance) in balances {
Index::encode_rune_balance(id, balance.n(), &mut buffer);

if let Some(sender) = self.event_sender {
sender.blocking_send(Event::RuneTransferred {
outpoint,
block_height: self.height,
txid,
rune_id: id,
amount: balance.0,
})?;
}
}

self
.outpoint_to_balances
.insert(&outpoint.store(), buffer.as_slice())?;
}

// increment entries with burned runes
for (id, amount) in burned {
*self.burned.entry(id).or_default() += amount;

if let Some(sender) = self.event_sender {
sender.blocking_send(Event::RuneBurned {
block_height: self.height,
txid,
rune_id: id,
amount: amount.n(),
})?;
}
}

Ok(())
}
}