forked from yuange1024/ethereum_yellowpaper
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ethereum_yellow_paper_cn.tex
2595 lines (2143 loc) · 199 KB
/
ethereum_yellow_paper_cn.tex
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[9pt,oneside]{amsart}
\usepackage{xeCJK}
\setCJKmainfont{SimSun}
\setCJKmonofont{SimSun}
\usepackage{url}
\usepackage{cancel}
\usepackage{xspace}
\usepackage{graphicx}
\usepackage{multicol}
\usepackage{subfig}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage[a4paper,width=170mm,top=18mm,bottom=22mm,includeheadfoot]{geometry}
\usepackage{booktabs}
\usepackage{array}
\usepackage{verbatim}
\usepackage{caption}
\usepackage{natbib}
\usepackage{float}
\usepackage{pdflscape}
\usepackage{mathtools}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{afterpage}
\usepackage{tikz}
\usepackage[bookmarks=true, unicode=true, pdftitle={Ethereum Yellow Paper: a formal specification of Ethereum, a programmable blockchain}, pdfauthor={Dr. Gavin Wood},pdfkeywords={Ethereum, Yellow Paper, blockchain, virtual machine, cryptography, decentralised, singleton, transaction, generalised},pdfborder={0 0 0.5 [1 3]}]{hyperref}
%,pagebackref=true
\usepackage{tabu} %requires array.
%This should be the last package before \input{Version.tex}
\PassOptionsToPackage{hyphens}{url}\usepackage{hyperref}
% "hyperref loads the url package internally. Use \PassOptionsToPackage{hyphens}{url}\usepackage{hyperref} to pass the option to the url package when it is loaded by hyperref. This avoids any package option clashes." Source: <https://tex.stackexchange.com/questions/3033/forcing-linebreaks-in-url/3034#comment44478_3034>.
% Note also this: "If the \PassOptionsToPackage{hyphens}{url} approach does not work, maybe it's "because you're trying to load the url package with a specific option, but it's being loaded by one of your packages before that with a different set of options. Try loading the url package earlier than the package that requires it. If it's loaded by the document class, try using \RequirePackage[hyphens]{url} before the document class." Source: <https://tex.stackexchange.com/questions/3033/forcing-linebreaks-in-url/3034#comment555944_3034>.
% For more information on using the hyperref package, refer to e.g. https://en.wikibooks.org/w/index.php?title=LaTeX/Hyperlinks&stable=0#Hyperlink_and_Hypertarget.
\makeatletter
\newcommand{\linkdest}[1]{\Hy@raisedlink{\hypertarget{#1}{}}}
\makeatother
\usepackage{seqsplit}
% For formatting
%\usepackage{underscore}
%\usepackage{lipsum} % to generate filler text for testing of document rendering
\usepackage[english]{babel}
\usepackage[autostyle]{csquotes}
\MakeOuterQuote{"}
\usepackage[final]{microtype} % https://tex.stackexchange.com/questions/75140/is-it-possible-to-make-latex-mark-overfull-boxes-in-the-output#comment382776_75142
\input{Version.tex}
% Default rendering options
\definecolor{pagecolor}{rgb}{1,1,1}
\def\YellowPaperVersionNumber{unknown revision}
\IfFileExists{Options.tex}{\input{Options.tex}}
\newcommand{\hcancel}[1]{%
\tikz[baseline=(tocancel.base)]{
\node[inner sep=0pt,outer sep=0pt] (tocancel) {#1};
\draw[black] (tocancel.south west) -- (tocancel.north east);
}%
}%
\DeclarePairedDelimiter{\ceil}{\lceil}{\rceil}
\newcommand*\eg{e.g.\@\xspace}
\newcommand*\Eg{e.g.\@\xspace}
\newcommand*\ie{i.e.\@\xspace}
%\renewcommand{\itemhook}{\setlength{\topsep}{0pt} \setlength{\itemsep}{0pt}\setlength{\leftmargin}{15pt}}
\title{以太坊:一种安全去中心化的通用交易账本 \\ {\smaller \textbf{拜占庭版本 \YellowPaperVersionNumber}}}
\author{
原文作者:Dr. Gavin Wood, [email protected]\\
中文版翻译:万山(微信号:wanshan256);高天露([email protected]);杨镇([email protected])
}
\begin{document}
\pagecolor{pagecolor}
\begin{abstract}
由密码学安全交易(cryptographically-secured transactions)所构成的区块链范式,已经通过一系列项目展示了它的实用性,不止是比特币。每一个这类项目都可以看作是一个基于去中心化的单实例计算资源所构建的简单应用程序。我们可以把这种范式称为具有共享状态(shared-state)的交易化单例状态机(transactional singleton machine)。
以太坊以更通用的方式实现了这种范式。它提供了大量的资源,每一个资源都拥有独立的状态和操作码,并且可以通过消息传递方式和其它资源交互。我们将讨论它的设计、实现上的问题、它所提供的机遇以及我们所设想的未来障碍。
\end{abstract}
\maketitle
\setlength{\columnsep}{20pt}
\begin{multicols}{2}
\section{简介}\label{sec:introduction}
随着互联网连接了世界上绝大多数地方,全球信息共享的成本越来越低。比特币网络通过共识机制、自愿遵守的社会合约,实现了一个去中心化的价值转移系统且可以在全球范围内自由使用,这样的技术改革展示了它的巨大力量。这样的系统可以说是加密安全、基于交易的状态机的一种具体应用。尽管还很简陋,但类似域名币(Namecoin)这样的后续系统,把这种技术从最初的“货币应用”发展到了其它领域。
以太坊是一个尝试达到通用性的技术项目,可以构建任何基于交易的状态机。而且以太坊致力于为开发者提供一个紧密整合的端到端系统,这个系统提供了一种可信对象消息传递计算框架(a trustful object messaging compute framework),使开发者可以用一种前所未有的计算范式来构建软件。
\subsection{驱动因素} \label{ch:driving}
这个项目有很多目标,其中最重要的目标是为了促成那些本来无法信任对方的个体之间的交易。这种不信任可能是因为地理上的分隔、对接的困难,或者是不兼容、不称职、不情愿、费用、不确定、不方便,或现有法律系统的腐败。于是我们想用一个丰富且清晰的语言去实现一个状态变化的系统,期望协议可以自动被执行,我们可以为此提供一种实现方式。
这里所提议的系统中的交易,有一些在现实世界中并不常见的属性。公允的裁判通常很难找到,但无私的算法解释器却可以天然地提供这种特性。自然语言必然是模糊的,会导致信息的缺失,同时那些平白的、旧有的成见很难被动摇,所以透明性,或者说通过交易日志和规则或代码指令来清晰地查看状态变化或者裁判结果的能力,在基于人来构建的系统中从来无法完美实现。
总的来说,我希望能提供一个系统,来给用户提供一些保证:无论是与其他个体、系统还是组织进行交互,他们都能完全信任可能的结果以及产生结果的过程。
\subsection{前人工作} \label{ch:previous}
\cite{buterin2013ethereum} 在 2013 年 11 月下旬第一次提出了这种系统的核心机制。虽然现在已经在很多方面都有了进化,但其核心特性没有变化。那就是:这是一个具有图灵完备的语言和实质上具有无限的内部交易数据存储容量的区块链。
\cite{dwork92pricingvia} 第一次提出了使用计算开销的密码学证明(“proof-of-work”,即工作量证明)来作为在互联网上传递价值信号的一种手段。这里所使用的价值信号,是作为对抗垃圾邮件的震慑机制的,并不是任何形式的货币;但这却极大地论证了一种承载强大的经济信号的基本数据通道的潜在可能,这种通道允许数据接受者不依赖任何信任就可以做出物理断言。\cite{back2002hashcash} 后来设计了一个类似的系统。
\cite{vishnumurthy03karma:a} 最早使用工作量证明作为强大的经济信号保证货币安全。在这个案例中,代币被用来在点到点(peer-to-peer)文件交易中检查并确保“消费者”可以向服务“提供商”进行小额支付。通过在工作量证明中增加数字签名和账本,一种新的安全模型产生了。这种模型是为了防止历史记录被篡改,并使恶意用户无法伪造交易或者不公平的抗议服务的交付。五年后(2008年),中本聪 \cite{nakamoto2008bitcoin} 引入了另一种更广泛的工作量证明安全价值代币。这个项目的成果就是比特币,它也成为了第一个被广泛认可的全球化去中心化交易账本。
由于比特币的成功,竞争币(alt-coins)开始兴起。通过修改比特币的协议,人们创建了许多其他的数字货币。比较知名的有莱特币(Litecoin)和素数币(Primecoin),参见 \cite{sprankel2013technical} 。一些项目使用比特币的核心机制并重新改造以应用在其它领域,例如域名币(Namecoin)致力于提供一个去中心化的名称解析系统,参见 \cite{aron2012bitcoin} 。
其它在比特币网络之上构建的项目,也是依赖巨大的系统价值和巨大的算力来保证共识机制。万事达币(Mastercoin)项目是在比特币协议之上,通过一系列基于核心协议的的辅助插件,构建一个包含许多高级功能的富协议,参见 \cite{mastercoin2013willett} 。彩色币(Coloured Coins,参见 \cite{colouredcoins2012rosenfeld} ,采用了类似的但更简化的协议,以实现比特币基础货币的可替代性,并允许通过色度钱包(chroma-wallet)来创建和跟踪代币。
其它一些工作通过放弃去中心化来进行。瑞波币(Ripple),参见 \cite{boutellier2014pirates} ,试图去创建一个货币兑换的联邦系统(“federated” system)和一个新的金融清算系统。这个系统展示了放弃去中心化特性可以获得性能上的提升。
\cite{szabo1997formalizing} 和 \cite{miller1997future} 进行了智能合约(smart contract)的早期工作。大约在上世纪 90 年代,人们逐渐认识到协议算法的执行可以成为人类合作的重要力量。虽然当时没有这样的系统,但可以表明法律的未来发展将会受到这种系统的影响。就此而言,以太坊或许可以视为这种加密-法律(cypto-law)系统的一种通用实现。
%E language?
本文中所使用的术语列表,请参考附录 \ref{ch:Terminology}。
\section{区块链范式} \label{ch:overview}
以太坊在整体上可以看作一个基于交易的状态机:起始于一个创世区块(Genesis)状态,然后随着交易的执行状态逐步改变一直到最终状态,这个最终状态是以太坊世界的权威“版本”。状态中包含的信息有:账户余额、名誉度、信誉度、现实世界的附属数据等;简而言之,能包含电脑可以描绘的任何信息。而交易则成为了连接两个状态的有效桥梁;“有效”非常重要——因为无效的状态改变远超过有效的状态改变。例如:无效的状态改变可能是减少账户余额,但是没有在其它账户上加上同等的额度。一个有效的状态转换是通过交易进行的。可以正式地描述为:
\begin{equation}
\linkdest{Upsilon_state_transition}\linkdest{Upsilon}\boldsymbol{\sigma}_{t+1} \equiv \Upsilon(\boldsymbol{\sigma}_{t}, T)
\end{equation}
$\Upsilon$ 是以太坊状态转换函数。在以太坊中,$\Upsilon$ 和 $\boldsymbol{\sigma}$ 比已有的任何比较系统都强; $\Upsilon$ 可以执行任意计算,而 $\boldsymbol{\sigma}$ 可以储存交易中的任意状态。
区块中记录着交易信息;区块之间通过密码学哈希(hash)以引用的方式链接起来。区块以流水账的方式组织起来,每个区块保留若干交易数据和前一个区块的引用,加上一个最终状态的标识符(最终状态本身不会保存到区块中——否则区块就太大了)。系统激励节点去挖矿(mine)。这种激励以状态转换函数的形式产生,会给指定的账户增加价值(即给指定的账户分配挖矿奖励,校订注)。
挖矿就是通过付出一定的努力(工作量)来与其它潜在的区块竞争一系列交易(一个区块)的记账权。它是通过密码学安全证明来实现的。这个机制就是工作量证明(proof-of-work),会在 \ref{ch:pow} 详细讨论。
我们可以正式地描述为:
\begin{eqnarray}
\boldsymbol{\sigma}_{t+1} & \equiv & \hyperlink{Pi}{\Pi}(\boldsymbol{\sigma}_{t}, B) \\
B & \equiv & (..., (T_0, T_1, ...) ) \\
\Pi(\boldsymbol{\sigma}, B) & \equiv & \hyperlink{Omega}{\Omega}(B, \hyperlink{Upsilon}{\Upsilon}(\Upsilon(\boldsymbol{\sigma}, T_0), T_1) ...)
\end{eqnarray}
其中 \hyperlink{Omega}{$\Omega$} 是区块定稿状态转换函数(这个函数奖励一个特定的账户);\hyperlink{block}{$B$} 表示一个区块,包含一系列交易和一些其他组成部分;$\hyperlink{Pi}{\Pi}$ 是区块级的状态转换函数。
这就是区块链范式的基础,这个模型不仅是以太坊的基础,还是迄今为止所有基于共识的去中心化交易系统的基础。
\subsection{单位}
为了激励网络中的计算,需要一种一致同意的转移价值方法。为了解决这个问题,以太坊设计了一种内置的货币——以太币(Ether),也就是我们所知的 {\small ETH},有时也用古英语中的 \DH{} 表示。以太币最小的单位是 Wei(伟),所有货币值都以 Wei 的整数倍来记录。一个以太币等于 $10^{18}$ Wei。以太币还有以下单位:
\par
\begin{center}
\begin{tabular}{rl}
\toprule
倍数 & 单位 \\
\midrule
$10^0$ & Wei(伟) \\
$10^{12}$ & Szabo(萨博) \\
$10^{15}$ & Finney(芬尼) \\
$10^{18}$ & Ether(以太) \\
\bottomrule
\end{tabular}
\end{center}
\par
在当前状况下,任何在以太币的上下文中所使用的价值标示,比如货币、余额或者支付,都应以 Wei 为单位来计算。
\subsection{如何选择历史?}
因为这是一个去中心化的系统,所有人都有机会在之前的某一个区块之后创建新的区块,这必然会形成一个区块树。为了能在这个区块树上从根节点(\hyperlink{Genesis_Block}{the genesis block})到叶节点(包含最新交易的区块)形成一个一致的区块链,必须有一个共识方案。如果各个节点所认为的从根节点到叶节点的路径(最佳区块链)不同,那么就会发生分叉。
这就意味着在一个给定的时间点,系统中会有多个状态共存:一些节点相信一个区块包含权威的交易,其它节点则相信另外的区块包含权威的交易,其中就包含彻底不同或者不兼容的交易。必须要不惜一切代价避免这点,因为它会破坏整个系统信用。
我们使用了一个简易的 GHOST 协议版本来达成共识,参见 \cite{cryptoeprint:2013:881}。我们会在第 \ref{ch:ghost} 章中详细说明。
有时会从一个特定的区块链高度启用新的协议。本文描述了协议的一个版本,如果要跟踪历史区块链路径,可能需要查看这份文档的历史版本。
\section{约定}\label{ch:conventions}
我使用了大量的排印约定来表示公式中的符号,其中一些需要特别说明:
有两个高度结构化的顶层状态值,用粗体小写希腊字母 $\boldsymbol{\sigma}$ 表示世界状态(world-state);用 $\boldsymbol{\mu}$ 表示机器状态(machine-state)。
作用在高度结构化数据上的函数,使用大写的希腊字母,例如:\hyperlink{Upsilon_state_transition}{$\Upsilon$},是以太坊中的状态转换函数。
对于大部分函数来说,通常用一个大写的字母表示,例如:$C$ 是总体费用函数。此外,可能会使用下角标表示一些特别的变量,例如:\hyperlink{C__SSTORE}{$C_\text{SSTORE}$} 是执行 \hyperlink{SSTORE}{{\tiny SSTORE}} 操作的费用函数。对于一些可能是外部定义的函数,我可能会使用打印机文字字体,例如使用 $\texttt{KEC}$ (一般指单纯的 Keccak)来表示 Keccak-256 哈希函数(这是由 \cite{Keccak} 提出的,在 SHA-3 竞争中获胜的那种算法,而不是最新的版本)。也会用 $\texttt{KEC512}$ 来表示 Keccack-512 哈希函数。
元组通常使用一个大写字母来表示,例如用 $T$ 表示一个以太坊交易。也可以通过使用下标来表示其中的一个独立组成部分,例如用 \hyperlink{transaction_nonce}{$T_{\mathrm{n}}$} 来表示交易的 nonce。下角标形式则用于表示它们的类型;例如:大写的下角标表示下角标所对应的组成部分所构成的元组。
标量和固定大小的字节序列(或数组)都使用小写字母来表示,例如 $n$ 在本文中表示 \hyperlink{transaction_nonce}{交易的 nonce}。小写的希腊字母一般表示一些特别的含义,例如 $\delta$ 表示在栈上一个给定操作所需要的条目数量。
任意长度的序列通常用粗体小写字母表示,例如 $\mathbf{o}$ 表示消息调用中输出的字节序列数据。对于特别重要的值,可能会使用粗体大写字母。
我们认为标量都是正整数且属于集合 $\mathbb{N}$。所有的字节序列属于集合 $\mathbb{B}$,附录 \ref{app:rlp} 给出了正式的定义。可以用下角标表示这样的序列集合的限定长度,因此,长度为 32 的字节序列使用 $\mathbb{B}_{32}$ 表示,所有比 $2^{256}$ 小的正整数使用 $\mathbb{N}_{256}$ 表示。详细定义参见 \hyperlink{block}{\ref{subsec:The_Block}}。
使用方括号表示序列中的一个元素或子序列,例如 $\boldsymbol{\mu}_\mathbf{s}[0]$ 表示虚拟机栈中的第一个条目。对于子序列来说,使用省略号表示一定的范围,且含首尾限制,例如 $\boldsymbol{\mu}_\mathbf{m}[0..31]$ 表示虚拟机内存中的前 32 个条目。
以全局状态 $\boldsymbol{\sigma}$ 为例,它表示一系列的账户(账户自身是元组),方括号被用来表示一个独立的账户。
当去考虑现有的变量时,我遵循在给定的范围内去定义的原则,如果我们使用占位符 $\Box$ 表示未修改的“输入”值,那么就可以使用 $\Box'$ 来表示修改过的可用值,$\Box^*$,$\Box^{**}$ \&c 表示中间值。在特殊情况下,为了提高可读性和清晰性,我可能会使用字母-数字下角标表示中间值。
当使用已有的函数时,例如一个给定函数 $f$,函数 \hyperlink{general_element_wise_sequence_transformation_f_pow_asterisk}{$f^*$} 表示一个相似的、对集合中所有元素都生效的函数映射。正式定义参见 \hyperlink{block}{\ref{subsec:The_Block}}。
以上,我定义了大量的函数。\linkdest{ell}一个最常见的函数是 $\ell$,它表示给定序列中的最后一个条目:
\begin{equation}
\ell(\mathbf{x}) \equiv \mathbf{x}[\lVert \mathbf{x} \rVert - 1]
\end{equation}
\section{区块、状态和交易} \label{ch:bst}
在介绍了以太坊的基本概念之后,我们将详细地讨论交易、区块和状态的含义。
\subsection{世界状态} \label{ch:state}
世界状态(\textit{state})是在地址(160 位的标识符)和账户状态(序列化为 RLP 的数据结构,详见附录 \ref{app:rlp})的映射。虽然世界状态没有直接储存在区块链上,但会假定在实施过程中会将这个映射维护在一个修改过的 Merkle Patricia 树上(即 \textit{trie},详见附录 \ref{app:trie})。这个 trie 需要一个简单的后端数据库去维护字节数组到字节数组的映射;我们称这个后端数据库为状态数据库。它有一系列的好处: 第一,这个结构的根节点是基于密码学依赖于所有内部数据的,它的哈希可以作为整个系统状态的一个安全标识;第二,作为一个不变的数据结构,我们可以通过简单地改变根节点哈希来召回任何一个先前的状态(在根节点哈希已知的条件下)。因为我们在区块链中储存了所以这样的根节点哈希值,所以我们可以很容易地恢复到特定的历史状态。
账户状态 $\boldsymbol{\sigma}[a]$ 包含以下四个字段:
\begin{description}
\item[nonce] \linkdest{account_nonce}这个值等于由此账户地址发出的交易数量,或者由这个账户所创建的合约数量(当这个账户有关联代码时)。$\boldsymbol{\sigma}[a]_n$ 即表示状态 $\boldsymbol{\sigma}$ 中的地址 $a$ 的 nonce 值。
\item[balance] $\boldsymbol{\sigma}[a]_{\mathrm{b}}$,表示这个账户地址拥有多少 Wei(即账户余额,译者注)。
\item[storageRoot] 保存了账户的存储内容的 Merkle Patricia 树的根节点的 256 位哈希值,这个树中保存的是 256 位整数键值的 Keccak 256 位哈希值到 256 位整数值的 RLP 编码的映射。这个哈希定义为 $\boldsymbol{\sigma}[a]_s$。
\item[codeHash] 这个账户的 EVM 代码哈希值——当这个地址接收到一个消息调用时,这些代码会被执行;它和其它字段不同,创建后不可更改。状态数据库中包含所有这样的代码片段哈希,以便后续使用。这个哈希可以定义为 $\boldsymbol{\sigma}[a]_c$,然后我们用 $\mathbf{b}$ 来表示代码,则有 $\texttt{\small KEC}(\mathbf{b}) = \boldsymbol{\sigma}[a]_c$。
\end{description}
因为我通常希望所指的并不是 Trie 的根哈希,而是其中所保存的键值对集合,所以我做了一个更方便的定义:
\begin{equation}
\texttt{\small TRIE}\big(L_{I}^*(\boldsymbol{\sigma}[a]_{\mathbf{s}})\big) \equiv \boldsymbol{\sigma}[a]_{\mathrm{s}}
\end{equation}
Trie 中的键值对集合函数 $L_{I}^*$ 被定义为适用于基础函数 $L_{I}$ 中所有元素的变换:
\begin{equation}
L_{I}\big( (k, v) \big) \equiv \big(\texttt{\small KEC}(k), \texttt{\small RLP}(v)\big)
\end{equation}
其中:
\begin{equation}
k \in \mathbb{B}_{32} \quad \wedge \quad v \in \mathbb{N}
\end{equation}
需要说明的是,$\boldsymbol{\sigma}[a]_\mathbf{s}$ 不应算作这个账户的“物理”成员,它不会参与之后的序列化。
如果 \textbf{codeHash} 字段是一个空字符串的 Keccak-256 哈希,也就是说 $\boldsymbol{\sigma}[a]_c = \texttt{\small KEC}\big(()\big)$,那么这个节点则表示一个简单账户,有时简称为“非合约”账户。
因此我们可以定义一个世界状态函数 $L_{S}$:
\begin{equation}
L_{S}(\boldsymbol{\sigma}) \equiv \{ p(a): \boldsymbol{\sigma}[a] \neq \varnothing \}
\end{equation}
其中
\begin{equation}
p(a) \equiv \big(\texttt{\small KEC}(a), \texttt{\small RLP}\big( (\boldsymbol{\sigma}[a]_{\mathrm{n}}, \boldsymbol{\sigma}[a]_{\mathrm{b}}, \boldsymbol{\sigma}[a]_{\mathrm{s}}, \boldsymbol{\sigma}[a]_{\mathrm{c}}) \big) \big)
\end{equation}
函数 $L_{S}$ 和 Trie 函数一起用来提供一个世界状态的简短标识(哈希)。我们假定:
\begin{equation}
\forall a: \boldsymbol{\sigma}[a] = \varnothing \; \vee \; (a \in \mathbb{B}_{20} \; \wedge \; v(\boldsymbol{\sigma}[a]))
\end{equation}
\linkdest{account_validity_function_v__x}{}其中,$v$ 是账户有效性验证函数:
\begin{equation}
\quad v(x) \equiv x_{\mathrm{n}} \in \mathbb{N}_{256} \wedge x_{\mathrm{b}} \in \mathbb{N}_{256} \wedge x_{\mathrm{s}} \in \mathbb{B}_{32} \wedge x_{\mathrm{c}} \in \mathbb{B}_{32}
\end{equation}
如果一个账户没有代码,它将是 \textit{empty}(空的),且 nonce 和 balance 均为 0:
\begin{equation}
\mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a) \quad\equiv\quad \boldsymbol{\sigma}[a]_{\mathrm{c}} = \texttt{\small KEC}\big(()\big) \wedge \boldsymbol{\sigma}[a]_{\mathrm{n}} = 0 \wedge \boldsymbol{\sigma}[a]_{\mathrm{b}} = 0
\end{equation}
即使是所谓的预编译合约也可能处于 empty 状态。这是因为它们的账户状态并不总是包含可以表述它们的行为的代码。
当一个账户的状态不存在或为 empty 时,就表示它 \textit{dead}(死了):
\begin{equation}
\mathtt{\tiny DEAD}(\boldsymbol{\sigma}, a) \quad\equiv\quad \boldsymbol{\sigma}[a] = \varnothing \vee \mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a)
\end{equation}
\subsection{交易} \label{ch:transaction}
交易(符号,$T$)是个单一的加密学签名的指令,通常由以太坊系统之外的操作者创建。我们假设外部的操作者是人,软件工具则用于构建和散播 \footnote{特别是,这样的“工具”最终将会从基于人类行为的初始化中剥离——或者人类可能变成中立状态——当到达某个临界点时,这种工具可能会被看作一种自治的代理。例如,合约可以向那些发送交易来启动它们的人们提供奖金。}。这里的交易有两种类型:一种表现为消息调用,另一种则通过代码创建新的账户(称为“合约创建”)。这两种类型的交易都有一些共同的字段:
\begin{description}
\item[nonce]\linkdest{tx_nonce}{} 由交易发送者发出的的交易的数量,由 $T_{\mathrm{n}}$ 表示。
\item[gasPrice]\linkdest{tx_gas_price_T__p}{} 为执行这个交易所需要进行的计算步骤消耗的每单位 \textit{gas} 的价格,以 Wei 为单位,由 $T_{\mathrm{p}}$ 表示。
\item[gasLimit]\linkdest{tx_gas_limit_T__g}{} 用于执行这个交易的最大 gas 数量。这个值须在交易开始前设置,且设定后不能再增加,由 $T_{\mathrm{g}}$ 表示。
\item[to]\linkdest{tx_to_address_T__t}{} 160 位的消息调用接收者地址;对与合约创建交易,用 $\varnothing$ 表示 $\mathbb{B}_0$ 的唯一成员。此字段由 $T_{\mathrm{t}}$ 表示
\item[value]\linkdest{tx_value_T__v}{} 转移到接收者账户的 Wei 的数量;对于合约创建,则代表给新建合约地址的初始捐款。由 $T_{\mathrm{v}}$ 表示。
\item[v, r, s] 与交易签名相符的若干数值,用于确定交易的发送者,由 \linkdest{T__w_T__r_T__s}{$T_{\mathrm{w}}$,$T_{\mathrm{r}}$ 和 $T_{\mathrm{s}}$ 表示}。详见附录 \ref{app:signing}。
\end{description}
此外,合约创建还包含以下字段:
\begin{description}
\item[init] 一个不限制大小的字节数组,用来指定账户初始化程序的 EVM 代码,由 $T_{\mathbf{i}}$ 表示。
\end{description}
\textbf{init} 是 EVM 代码片段;它将返回 \textbf{body},这是这个账户每次接收到消息调用时会执行的代码(通过一个交易或者代码的内部执行)。\textbf{init} 代码仅会在合约创建时被执行一次,然后就会被丢弃。
与此相对,一个消息调用交易包括:
\begin{description}
\item[data] 一个不限制大小的字节数组,用来指定消息调用的输入数据,由 $T_\mathbf{d}$ 表示。
\end{description}
附录 \ref{app:signing} 详细描述了将交易映射到发送者的函数 $S$。这种映射通过 SECP-256k1 曲线的 ECDSA 算法实现,使用交易哈希(除去后 3 个签名字段)作为数据来进行签名。目前我们先简单地使用 $S(T)$ 表示指定交易 $T$ 的发送者。
\begin{equation}
L_{T}(T) \equiv \begin{cases}
(T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{i}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{if} \; T_{\mathrm{t}} = \varnothing\\
(T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{d}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{otherwise}
\end{cases}
\end{equation}
在这里,我们假设除了任意长度的字节数组 $T_{\mathbf{i}}$ 和 $T_{\mathbf{d}}$ 以外,所有变量都是作为整数来进行 RLP 编码的。
\begin{equation}
\begin{array}[t]{lclclc}
T_{\mathrm{n}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{v}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{p}} \in \mathbb{N}_{256} & \wedge \\
T_{\mathrm{g}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{w}} \in \mathbb{N}_5 & \wedge & T_{\mathrm{r}} \in \mathbb{N}_{256} & \wedge \\
T_{\mathrm{s}} \in \mathbb{N}_{256} & \wedge & T_{\mathbf{d}} \in \mathbb{B} & \wedge & T_{\mathbf{i}} \in \mathbb{B}
\end{array}
\end{equation}
其中
\begin{equation}
\mathbb{N}_{\mathrm{n}} = \{ P: P \in \mathbb{N} \wedge P < 2^n \}
\end{equation}
地址哈希 $T_{\mathbf{t}}$ 稍微有些不同:它是一个 20 字节的地址哈希值,或者当创建合约时(将等于 $\varnothing$)是 RLP 空字节序列,所以是 $\mathbb{B}_0$ 的成员:
\begin{equation}
T_{\mathbf{t}} \in \begin{cases} \mathbb{B}_{20} & \text{if} \quad T_{\mathrm{t}} \neq \varnothing \\
\mathbb{B}_{0} & \text{otherwise}\end{cases}
\end{equation}
\subsection{区块}\linkdest{block}\label{subsec:The_Block}
在以太坊中,区块是由以下部分组成的:一些相关信息片段组成的集合(称为 block \textit{header},即区块头);组成区块的交易 $\mathbf{T}$ 和 \hypertarget{ommerheaders}{}其它一些区块头 $\mathbf{U}$ (这是一些父区块与当前区块的爷爷辈区块相同的区块,这样的区块称为 \textit{ommers}\footnote{\textit{ommer} 的意思和自然界中的“父母的兄弟姐妹”最相近,参见 \url{https://nonbinary.miraheze.org/wiki/Gender_neutral_language\#Aunt.2FUncle}})。区块头包含的的信息如下:
%\textit{TODO: Introduce logs}
\begin{description}
\item[parentHash]\linkdest{parent_Hash_H__p_def_words}{} 父区块头的 Keccak 256 位哈希,由 $H_{\mathrm{p}}$ 表示。
\item[ommersHash] 当前区块的 ommers 列表的 Keccak 256 位哈希,由 $H_{\mathrm{o}}$ 表示。
\item[beneficiary]\linkdest{beneficiary_H__c}{}\linkdest{H__c} 成功挖到这个区块所得到的所有交易费的 160 位接收地址,由 $H_{\mathrm{c}}$ 表示。
\item[stateRoot] 所有交易被执行完且区块定稿后的状态树(state trie)根节点的 Keccak 256 位哈希。
\item[transactionsRoot] 由当前区块中所包含的所有交易所组成的树结构(transaction trie)根节点的 Keccak 256 位哈希,由 $H_{\mathrm{t}}$ 表示。
\item[receiptsRoot]\linkdest{receipts_Root_def_words}{} 由当前区块中所有交易的收据所组成的树结构(receipt trie)根节点的 Keccak 256 位哈希。由 $H_{\mathrm{e}}$ 表示。
\item[logsBloom]\linkdest{logs_Bloom_def_words}{} 由当前区块中所有交易的收据数据中的可索引信息(产生日志的地址和日志主题)组成的 Bloom 过滤器,由 $H_{\mathrm{b}}$ 表示。
\item[difficulty] 当前区块难度水平的纯量值,它可以根据前一个区块的难度水平和时间戳计算得到,由 $H_{\mathrm{d}}$ 表示。
\item[number]\linkdest{block_number_word_def_H_i}{} 当前区块的祖先的数量,由 $H_{\mathrm{i}}$ 表示。创世区块的这个数量为 0。
\item[gasLimit] 目前每个区块的 gas 开支上限,由 $H_{\mathrm{l}}$ 表示。
\item[gasUsed]\linkdest{block_gas_used_H__g}{}\linkdest{H__g} 当前区块的所有交易所用掉的 gas 之和,由 $H_{\mathrm{g}}$ 表示。
\item[timestamp]\linkdest{block_timestamp_word_def_H__s}{} 当前区块初始化时的 Unix 时间戳,由 $H_{\mathrm{s}}$ 表示。
\item[extraData]\linkdest{block_extraData_H__x}{} 与当前区块相关的任意字节数据,但必须在 32 字节以内,由 $H_{\mathrm{x}}$ 表示。
\item[mixHash]\linkdest{mixHash_H__m}{}\linkdest{H__m} 一个 256 位的哈希值,用来与 nonce 一起证明当前区块已经承载了足够的计算量,由 $H_{\mathrm{m}}$ 表示。
\item[nonce]\linkdest{block_nonce_H__n}{}\linkdest{block_nonce} 一个 64 位的值,用来与 mixHash 一起于证明当前区块已经承载了足够的计算量,由 $H_{\mathrm{n}}$ 表示。
\end{description}
\linkdest{ommer_block_headers_B__U}{}\linkdest{block_B}{}区块的另两个组成部分就是 ommer 区块头(与以上格式相同)列表 $B_{\mathbf{U}}$ 和一系列的交易 $B_{\mathbf{T}}$。我们可以表示一个区块 $B$:
\begin{equation}
B \equiv (B_{H}, B_{\mathbf{T}}, B_{\mathbf{U}})
\end{equation}
\subsubsection{交易收据}\linkdest{Transaction_Receipt}{}
为了能使交易信息对零知识证明、索引和搜索都是有用的,我们将每个交易执行过程中的一些特定信息编码为交易收据。
我们以 $B_{\mathbf{R}}[i]$ 表示第 $i$ 个交易的收据,并把收据信息保存在一个以索引为键的树(index-keyed \hyperlink{trie}{trie})中,树的根节点用 \hyperlink{Receipts_Root_H__e}{$H_{\mathrm{e}}$} 保存到区块头中。
\linkdest{transaction_receipt_R}{}\linkdest{tx_receipt_gas_used_R__u}{}\linkdest{R__u}交易收据 $R$ 是一个包含四个条目的元组:包含交易收据的区块中当交易发生后的累积 gas 使用量 $R_{\mathrm{u}}$;交易过程中创建的日志集合 $R_{\mathrm{l}}$;由这些日志信息所构成的 Bloom 过滤器 $R_{\mathrm{b}}$ 和交易的状态代码 $R_{\mathrm{z}}$:
\begin{equation}
R \equiv (R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}}, R_{\mathrm{z}})
\end{equation}
\hypertarget{transaction_receipt_preparation_function_for_RLP_serialisation}{}\linkdest{L__R}函数 $L_{R}$ 是将交易收据转换为 RLP 序列化字节数组的预处理函数:
\begin{equation}
L_{R}(R) \equiv (0 \in \mathbb{B}_{256}, R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}})
\end{equation}
其中,$0 \in \mathbb{B}_{256}$ 代替了先前协议版本中的交易前状态根(the pre-transaction state root)。
\linkdest{R__z_assert}我们要求状态代码 $R_{\mathrm{z}}$ 是一个整数。
\begin{equation}
R_{\mathrm{z}} \in \mathbb{N}
\end{equation}
\linkdest{R__u_assert}我们要求累积的 gas 使用量 $R_{\mathrm{u}}$ 是一个正整数,且日志的 Bloom $R_{\mathrm{b}}$ 是一个 2048 位(256字节)数据的哈希:
\begin{equation}
R_{\mathrm{u}} \in \mathbb{N} \quad \wedge \quad R_{\mathrm{b}} \in \mathbb{B}_{256}
\end{equation}
%Notably $B_{\mathbf{T}}$ does not get serialised into the block by the block preparation function $L_{B}$; it is merely a convenience equivalence.
$R_{\mathbf{l}}$ 是一系列的日志项 $(O_0, O_1, ...)$。一个日志项 $O$ 是一个记录了日志产生者的地址 $O_a$;一系列 32 字节的日志主题 $O_{\mathbf{t}}$ 和一些字节数据 $O_{\mathbf{d}}$ 所组成的元组:
\begin{equation}
O \equiv (O_{\mathrm{a}}, ({O_{\mathbf{t}}}_0, {O_{\mathbf{t}}}_1, ...), O_{\mathbf{d}})
\end{equation}
\begin{equation}
O_{\mathrm{a}} \in \mathbb{B}_{20} \quad \wedge \quad \forall_{t \in O_{\mathbf{t}}}: t \in \mathbb{B}_{32} \quad \wedge \quad O_{\mathbf{d}} \in \mathbb{B}
\end{equation}
我们定义 Bloom 过滤器函数 $M$ 将一个日志项精简为一个 256 字节哈希:
\begin{equation}
M(O) \equiv \hyperlink{bigvee}{\bigvee}_{t \in \{O_{\mathrm{a}}\} \cup O_{\mathbf{t}}} \big( M_{3:2048}(t) \big)
\end{equation}
其中 $M_{3:2048}$ 是一个特别的 Bloom 过滤器,它通过设置 2048 位中的 3 位数值来给定一个随机的字节序列。这是通过取得对一个字节序列中的前 3 对字节的 Keccak-256 哈希值的低位 11 位数据实现的\footnote{11 位 2 进制数值 $= 2^{2048}$,其低位的 11 位数值也就是对操作数进行模 2048 的运算结果,这就是“取得对一个字节序列中的前 3 对字节的 Keccak-256 哈希值的低位 11 位数据”的情况。}:
\begin{eqnarray}
M_{3:2048}(\mathbf{x}: \mathbf{x} \in \mathbb{B}) & \equiv & \mathbf{y}: \mathbf{y} \in \mathbb{B}_{256} \quad \text{where:}\\
\mathbf{y} & = & (0, 0, ..., 0) \quad \text{except:}\\
\forall_{i \in \{0, 2, 4\}}&:& \mathcal{B}_{m(\mathbf{x}, i)}(\mathbf{y}) = 1\\
m(\mathbf{x}, i) &\equiv& \mathtt{\tiny KEC}(\mathbf{x})[i, i + 1] \bmod 2048
\end{eqnarray}
其中 $\mathcal{B}$ 是位引用函数,$\mathcal{B}_{\mathrm{j}}(\mathbf{x})$ 等于字节数组 $\mathbf{x}$ 中的索引 $j$(索引值从 0 开始)的位。
\subsubsection{整体有效性}
\linkdest{block_validity}{}当且仅当一个区块同时满足以下几个条件,我们才认为它是有效的:它必须由内部一致的 ommer 和交易区块哈希值所组成,且基于起始状态 $\boldsymbol{\sigma}$(由父区块的最终状态继承而来)按顺序执行完成所有的给定交易 $B_{\mathbf{T}}$(就像在 \ref{ch:finalisation} 节所介绍的那样)后所达到的标识符 $H_{\mathrm{r}}$ 的一个新的状态:
\begin{equation}
\begin{array}[t]{lclc}
\linkdest{new_state_H__r}{}H_{\mathrm{r}} &\equiv& \mathtt{\small TRIE}(L_S(\Pi(\boldsymbol{\sigma}, B))) & \wedge \\
\linkdest{Ommer_block_hash_H__o}{}H_{\mathrm{o}} &\equiv& \mathtt{\small KEC}(\mathtt{\small RLP}(L_H^*(B_{\mathbf{U}}))) & \wedge \\
\linkdest{tx_block_hash_H__t}{}H_{\mathrm{t}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{T}} \rVert, i \in \mathbb{N}: &\\&& \quad\quad p (i, L_{T}(B_{\mathbf{T}}[i]))\}) & \wedge \\
\linkdest{Receipts_Root_H__e}{}H_{\mathrm{e}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{R}} \rVert, i \in \mathbb{N}: &\\&& \quad\quad p(i, \hyperlink{transaction_receipt_preparation_function_for_RLP_serialisation}{L_{R}}(B_{\mathbf{R}}[i]))\}) & \wedge \\
\linkdest{logs_Bloom_filter_H__b}{}H_{\mathrm{b}} &\equiv& \bigvee_{\mathbf{r} \in B_{\mathbf{R}}} \big( \mathbf{r}_{\mathrm{b}} \big)
\end{array}
\end{equation}
其中 $p(k, v)$ 就是简单的 RLP 变换对,在这里,k 是交易在区块中的索引,v 是交易收据:
\begin{equation}
p(k, v) \equiv \big( \mathtt{\small RLP}(k), \mathtt{\small RLP}(v) \big)
\end{equation}
此外:
\begin{equation}
\mathtt{\small TRIE}(L_{S}(\boldsymbol{\sigma})) = {P(B_H)_H}_{\mathrm{r}}
\end{equation}
这样,$\texttt{\small TRIE}(L_{S}(\boldsymbol{\sigma}))$ 是以状态 $\boldsymbol{\sigma}$ 和以 RLP 编码的值作为键值对数据的 Merkle Patricia 树根节点哈希,$P(B_{H})$ 就是父区块 $B$。
这些源于交易计算所产生的值,具体来讲就是\hyperlink{Transaction_Receipt}{交易收据} $B_\mathbf{R}$,以及通过交易的\hyperlink{Pi}{状态累积函数所定义的 $\Pi$},会在 \ref{sec:statenoncevalidation} 节详细说明。
\subsubsection{序列化}
\hypertarget{block_preparation_function_for_RLP_serialization_L__B}{}\linkdest{L__B}\hypertarget{block_preparation_function_for_RLP_serialization_L__H}{}\linkdest{L__B}函数 $L_{B}$ 和 $L_{H}$ 分别是区块和区块头的预备函数。与\hyperlink{transaction_receipt_preparation_function_for_RLP_serialisation}{交易收据预备函数 $L_{R}$}非常相似,当需要进行 RLP 变换时,我们要求结构的类型和顺序如下:
\begin{eqnarray}
\quad L_{H}(H) & \equiv & (\begin{array}[t]{l}H_{\mathrm{p}}, H_{\mathrm{o}}, H_{\mathrm{c}}, H_{\mathrm{r}}, H_{\mathrm{t}}, H_{\mathrm{e}}, H_{\mathrm{b}}, H_{\mathrm{d}},\\ H_{\mathrm{i}}, H_{\mathrm{l}}, H_{\mathrm{g}}, H_{\mathrm{s}}, H_{\mathrm{x}}, H_{\mathrm{m}}, H_{\mathrm{n}} \; )\end{array} \\
\quad L_{B}(B) & \equiv & \big( L_{H}(B_{H}), L_{T}^*(B_{\mathbf{T}}), L_{H}^*(\hyperlink{ommer_block_headers_B__U}{B_{\mathbf{U}}}) \big)
\end{eqnarray}
\hypertarget{general_element_wise_sequence_transformation_f_pow_asterisk}{}其中 $L_T^*$ 和 $L_H^*$ 是适用于所有元素的序列转换,因此:
\begin{equation}
\hyperlink{general_element_wise_sequence_transformation_f_pow_asterisk}{f^*}\big( (x_0, x_1, ...) \big) \equiv \big( f(x_0), f(x_1), ... \big) \quad \text{对所有函数} \; f
\end{equation}
其组成要素的类型定义如下:
\begin{equation}
\begin{array}[t]{lclclcl}
\hyperlink{parent_Hash_H__p_def_words}{H_{\mathrm{p}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{o}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{c}} \in \mathbb{B}_{20} & \wedge \\
\hyperlink{new_state_H__r}{H_{\mathrm{r}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{t}} \in \mathbb{B}_{32} & \wedge & \hyperlink{Receipts_Root_H__e}{H_{\mathrm{e}}} \in \mathbb{B}_{32} & \wedge \\
\hyperlink{logs_Bloom_filter_H__b}{H_{\mathrm{b}}} \in \mathbb{B}_{256} & \wedge & H_{\mathrm{d}} \in \mathbb{N} & \wedge & \hyperlink{block_number_H__i}{H_{\mathrm{i}}} \in \mathbb{N} & \wedge \\
\hyperlink{block_gas_limit_H__l}{H_{\mathrm{l}}} \in \mathbb{N} & \wedge & H_{\mathrm{g}} \in \mathbb{N} & \wedge & \hyperlink{block_timestamp_H__s}{H_{\mathrm{s}}} \in \mathbb{N}_{256} & \wedge \\
\hyperlink{block_extraData_H__x}{H_{\mathrm{x}}} \in \mathbb{B} & \wedge & H_{\mathrm{m}} \in \mathbb{B}_{32} & \wedge & \hyperlink{block_nonce_H__n}{H_{\mathrm{n}}} \in \mathbb{B}_{8}
\end{array}
\end{equation}
其中
\begin{equation}
\mathbb{B}_{\mathrm{n}} = \{ B: B \in \mathbb{B} \wedge \lVert B \rVert = n \}
\end{equation}
我们现在有了一个严格的区块结构构造说明。RLP 函数 $\texttt{\small RLP}$(见附录 \ref{app:rlp})提供了权威的方法来把这个结构转换为一个可以通过网络传输或在本地存储的字节序列。
\subsubsection{区块头验证}
我们把 $P(B_{H})$ 定义为 $B$ 的父区块:
\begin{equation}
P(H) \equiv B': \mathtt{\tiny KEC}(\mathtt{\tiny RLP}(B'_{H})) = \hyperlink{parent_Hash_H__p_def_words}{H_{\mathrm{p}}}
\end{equation}
\hypertarget{block_number_H__i}{}其区块号等于它的父区块号加 1:
\begin{equation}
H_{\mathrm{i}} \equiv {{P(H)_{H}}_{\mathrm{i}}} + 1
\end{equation}
\newcommand{\mindifficulty}{D_0}
\newcommand{\homesteadmod}{\ensuremath{\varsigma_2}}
\newcommand{\expdiffsymb}{\ensuremath{\epsilon}}
\newcommand{\diffadjustment}{x}
\hypertarget{block_difficulty_H__d}{}\linkdest{H__d}我们将一个区块头 $H$ 的权威难度值定义为 $D(H)$:
\begin{equation}
D(H) \equiv \begin{dcases}
\mindifficulty & \text{if} \quad H_{\mathrm{i}} = 0\\
\text{max}\!\left(\mindifficulty, {P(H)_{H}}_{\mathrm{d}} + \diffadjustment\times\homesteadmod + \expdiffsymb \right) & \text{otherwise}\\
\end{dcases}
\end{equation}
其中:
\begin{equation}
\mindifficulty \equiv 131072
\end{equation}
\begin{equation}
\diffadjustment \equiv \left\lfloor\frac{{P(H)_{H}}_{\mathrm{d}}}{2048}\right\rfloor
\end{equation}
\begin{equation}
\homesteadmod \equiv \text{max}\left( y - \left\lfloor\frac{H_{\mathrm{s}} - {P(H)_{H}}_{\mathrm{s}}}{9}\right\rfloor, -99 \right)
\end{equation}
\begin{equation*}
y \equiv \begin{cases}
1 & \text{if} \, \lVert P(H)_{\mathbf{U}}\rVert = 0 \\
2 & \text{otherwise}
\end{cases}
\end{equation*}
\begin{align}
\expdiffsymb &\equiv \left\lfloor 2^{ \left\lfloor H'_{\mathrm{i}} \div 100000 \right\rfloor - 2 } \right\rfloor \\
H'_{\mathrm{i}} &\equiv \max(H_{\mathrm{i}} - 3000000, 0)
\end{align}
注意,$\mindifficulty$ 是创世区块的难度值。就像下面介绍的那样,\textit{Homestead} 难度值参数 $\homesteadmod$ 被用来影响出块时间的动态平衡,它已经通过由 \cite{EIP-2} 提出的 EIP-2 实现了。在 Homestead 版本中,难度符号 $\expdiffsymb$ 会越来越快地使难度值缓慢增长(每10万个区块),从而增加区块时间差别,也为向权益证明(proof-of-stake)的切换增加了时间压力。这个效果就是所谓的“难度炸弹”("difficulty bomb")或“冰河时期”("ice age")在由 \cite{EIP-649} 提出的 EIP-649 中进行了解释,并用来推迟早先在 EIP-2 中的实现。$\homesteadmod$ 也在 EIP-100 通过使用 $x$(即上面公式中的矫正参数)和分母 9 进行了修改,用来达到由 \cite{EIP-100} 提出的对包含叔区块(uncle blocks)在内的平均出块时间的调整效果。最终,在拜占庭版本中,伴随 EIP-649,我们通过伪造一个区块号 $H'_{\mathrm{i}}$ 来延迟冰河时期的来临。这是通过用实际区块号减去 300 万来获得的。换句话说,就是减少 $\expdiffsymb$ 和区块间的时间间隔,来为权益证明的开发争取更多的时间并防止网络被“冻结”。
\hypertarget{block_gas_limit_H__l}{}区块头 $H$ 的 gas 限制 $H_{\mathrm{l}}$ 需要满足下列条件:
\begin{eqnarray}
& & H_{\mathrm{l}} < {P(H)_{H}}_{\mathrm{l}} + \left\lfloor\frac{{P(H)_{H}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
\nonumber& & H_{\mathrm{l}} > {P(H)_{H}}_{\mathrm{l}} - \left\lfloor\frac{{P(H)_{H}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
\nonumber& & H_{\mathrm{l}} \geqslant 5000
\end{eqnarray}
\hypertarget{block_timestamp_H__s}{}$H_{\mathrm{s}}$ 是区块 $H$ 的时间戳(以 Unix 的 time() 函数的形式),需要满足下列条件:
\begin{equation}
H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}}
\end{equation}
这个机制保证了区块时间的动态平衡;如果最近的两个区块间隔较短,则会导致难度值增加,因此需要额外的计算量,大概率会延长下个区块的出块时间。相反,如果最近的两个区块间隔过长,难度值和下一个区块的预期出块时间也会减少。
这里的 nonce \linkdest{H__n}\hyperlink{block_nonce_H__n}{$H_{\mathrm{n}}$} 需满足下列关系:
\begin{equation}
n \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \quad \wedge \quad m = H_{\mathrm{m}}
\end{equation}
with $(n, m) = \mathtt{PoW}(H_{\hcancel{n}}, H_{\mathrm{n}}, \mathbf{d})$.
\hypertarget{block_header_without_nonce_and_mixmash_h__cancel_n}{}\linkdest{H_cancel_n}其中,$H_{\hcancel{n}}$ 是\textit{不包含} nonce 和 mixHash 的新区块的区块头 $H$;$\mathbf{d}$ 是当前的 DAG(即有向无环图,是用来计算 mixHash 的一种大型数据集合)和工作量证明函数 $\mathtt{PoW}$(参见 \ref{ch:pow}):这取决于一个数组的值,数组的第一个元素是用来证明使用了一个正确的 DAG 的混合哈希(mix-hash);数组的第二个元素是伪随机数,密码学依赖于 $H$ 及 $\mathbf{d}$。给定一个范围在 $[0, 2^{64})$ 的均匀分布,则求解时间和难度 $H_{\mathrm{d}}$ 是成比例变化的。
这就是区块链的安全基础,也是一个恶意节点不能用其新创建的区块来覆盖(“重写”)历史数据的重要原因。因为这个随机数必须满足这些条件,且因为条件依赖于这个区块的内容和相关交易,创建新的合法的区块是困难且耗时的,需要超过所有诚实矿工的算力总和。
\hypertarget{block_header_validity_function}{}这样,我们定义这个区块头的验证函数 $V(H)$ 为:
\begin{eqnarray}
V(H) & \equiv & n \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \wedge m = H_{\mathrm{m}} \quad \wedge \\
\nonumber & & H_{\mathrm{d}} = D(H) \quad \wedge \\
\nonumber& & H_{\mathrm{g}} \le H_{\mathrm{l}} \quad \wedge \\
\nonumber& & H_{\mathrm{l}} < {P(H)_{H}}_{\mathrm{l}} + \left\lfloor\frac{{P(H)_{H}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
\nonumber& & H_{\mathrm{l}} > {P(H)_{H}}_{\mathrm{l}} - \left\lfloor\frac{{P(H)_{H}}_{\mathrm{l}}}{1024}\right\rfloor \quad \wedge \\
\nonumber& & H_{\mathrm{l}} \geqslant 5000 \quad \wedge \\
\nonumber& & H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}} \quad \wedge \\
\nonumber& & H_{\mathrm{i}} = {P(H)_{H}}_{\mathrm{i}} +1 \quad \wedge \\
\nonumber& & \lVert H_{\mathrm{x}} \rVert \le 32
\end{eqnarray}
其中 $(n, m) = \mathtt{PoW}(H_{\hcancel{n}}, H_{\mathrm{n}}, \mathbf{d})$
此外,\textbf{extraData} 最多 32 字节。
\section{Gas 及其支付} \label{ch:payment}
为了避免网络滥用及回避由于图灵完整性而带来的一些不可避免的问题,在以太坊中所有的程序执行都需要费用。各种操作费用以 \textit{gas}(详见附录 \ref{app:fees} 中与不同计算相关的费用说明)为单位计算。任意的程序片段(包括合约创建、消息调用、分配资源以及访问账户 storage、在虚拟机上执行操作等)都会有一个普遍认同的 gas 消耗。
每一个交易都要指定一个 gas 上限:\textbf{gasLimit}。这些 gas 会从发送者的账户的 balance 中扣除。这种购买是通过同样在交易中指定的 \textbf{gasPrice} 来完成的。如果这个账户的 balance 不能支持这样的购买,交易会被视为无效交易。之所以将其命名为 \textbf{gasLimit},是因为剩余的 gas 会在交易完成后被返还(与购买时同样价格)到发送者账户。Gas 不会在交易执行之外存在,因此对于可信任的账户,应该设置一个相对较高的 gas 上限。
通常来说,以太币(Ether)是用来购买 gas 的,未返还的部分就会移交到 \textit{beneficiary} 的地址(即一般由矿工所控制的一个账户地址)。交易者可以随意指定 \textbf{gasPrice},然而矿工也可以任意地忽略某个交易。一个高 gas 价格的交易将花费发送者更多的以太币,也就将移交给矿工更多的以太币,因此这个交易自然会被更多的矿工选择打包进区块。通常来说,矿工会选择公告他们执行交易的最低 gas 价格,交易者们也就可以据此来提供一个具体的价格。因此,会有一个(加权的)最低的可接受 gas 价格分布,交易者们则需要在降低 gas 价格和使交易能最快地被矿工打包间进行权衡。
%\subsubsection{Determining Computation Costs}
\section{交易执行} \label{ch:transactions}
交易执行是以太坊协议中最复杂的部分:它定义了状态转换函数 \hyperlink{Upsilon_state_transition}{$\Upsilon$}。我们假定任意交易在执行时前都要先通过初始的基础有效性测试。包含:
\begin{enumerate}
\item 交易是 RLP 格式数据,没有多余的后缀字节;
\item 交易的签名是有效的;
\item \hyperlink{transaction_nonce}{交易的 nonce} 是有效的(等于\hyperlink{account_nonce}{发送者账户当前的 nonce});
\item gas 上限不小于交易所要使用的 gas $g_0$;
\item 发送者账户的 balance 应该不少于实际费用 $v_0$,且需要提前支付。
\end{enumerate}
这样,我们可以定义状态转移函数 \hyperlink{Upsilon_state_transition}{$\Upsilon$},其中 $T$ 表示交易,$\boldsymbol{\sigma}$ 表示状态:
\begin{equation}
\boldsymbol{\sigma}' = \Upsilon(\boldsymbol{\sigma}, T)
\end{equation}
因此 $\boldsymbol{\sigma}'$ 是交易后的状态。我们也定义 \hyperlink{tx_total_gas_used_Upsilon_pow_g}{$\Upsilon^{\mathrm{g}}$} 为交易执行所消耗的 gas 数量;\hyperlink{tx_logs_Upsilon_pow_l}{$\Upsilon^{\mathbf{l}}$} 为交易过程中累积产生的日志项;以及 \hyperlink{tx_status_Upsilon_pow_z}{$\Upsilon^{\mathrm{z}}$} 为交易结果的状态代码。这些都会在后文正式定义。
\subsection{子状态}
交易的执行过程中会累积产生一些特定的信息,我们称为\textit{交易子状态},用 $A$ 来表示,它是个元组:
\begin{equation}
A \equiv (A_{\mathbf{s}}, A_{\mathbf{l}}, A_{\mathbf{t}}, A_{\mathrm{r}})
\end{equation}
\hypertarget{self_destruct_set_wordy_defn_A__s}{}元组内容包括自毁集合 $A_{\mathbf{s}}$:一组应该在交易完成后被删除的账户。\hypertarget{tx_log_series_wordy_defn_A__l}{} $A_{\mathbf{l}}$ 是一系列的日志:这是一些列针对 VM 代码执行的归档的、可索引的‘检查点’,允许以太坊世界的外部旁观者(例如去中心化应用的前端)来简单地跟踪合约调用。\hypertarget{tx_touched_accounts_wordy_defn_A__t}{} $A_{\mathbf{t}}$ 是交易所接触过的账户集合,其中的空账户可以在交易结束时删除。\hypertarget{refund_balance_defn_words_A__r}{}最后是 $A_{\mathrm{r}}$,也就是应该返还的余额;当使用 \hyperlink{SSTORE}{{\small SSTORE}} 指令将非 0 的合约 storage 重置为 0 时,这个余额会增加。虽然不是立即返还的余额,但可以部分抵消整体执行费用。
我们定义空的子状态 $A^0$,它没有自毁、没有日志、没有接触过的账户且返还余额为 0:
\begin{equation}
A^0 \equiv (\varnothing,(), \varnothing, 0)
\end{equation}
\subsection{执行}
我们定义固有的 gas 消耗 $g_0$,它是需要在交易执行前支付的 gas 数量:
\begin{align}
g_0 \equiv {} & \sum_{i \in T_{\mathbf{i}}, T_{\mathbf{d}}} \begin{cases} \hyperlink{G__txdatazero}{G_{\mathrm{txdatazero}}} & \text{if} \quad i = 0 \\ \hyperlink{G__txdatanonzero}{G_{\mathrm{txdatanonzero}}} & \text{otherwise} \end{cases} \\
{} & + \begin{cases} \hyperlink{G__txcreate}{G_{\mathrm{txcreate}}} & \text{if} \quad T_{\mathrm{t}} = \varnothing \\ 0 & \text{otherwise} \end{cases} \\
{} & + \hyperlink{G__transaction}{G_{\mathrm{transaction}}}
\end{align}
其中,$T_{\mathbf{i}}$,$T_{\mathbf{d}}$ 是交易附带的关联数据的字节序列和 EVM 初始化代码的字节序列,具体取决于交易是合约创建还是消息调用。如果这个交易是合约创建,则会增加 $G_{\mathrm{txcreate}}$,否则不增加。$G$ 的完整定义参见附录 \ref{app:fees}。
预支付的费用 $v_0$ 计算如下:
\begin{equation}
v_0 \equiv \hyperlink{tx_gas_limit_T__g}{T_{\mathrm{g}}} \hyperlink{tx_gas_price_T__p}{T_{\mathrm{p}}} + \hyperlink{tx_value_T__v}{T_{\mathrm{v}}}
\end{equation}
我们通过下列条件检查有效性:
\begin{equation}
\begin{array}[t]{rcl}
S(T) & \neq & \varnothing \quad \wedge \\
\boldsymbol{\sigma}[S(T)] & \neq & \varnothing \quad \wedge \\
\linkdest{transaction_nonce}{}T_{\mathrm{n}} & = & \boldsymbol{\sigma}[S(T)]_{\mathrm{n}} \quad \wedge \\
g_0 & \leqslant & T_{\mathrm{g}} \quad \wedge \\
v_0 & \leqslant & \boldsymbol{\sigma}[S(T)]_{\mathrm{b}} \quad \wedge \\
T_{\mathrm{g}} & \leqslant & {B_{H}}_{\mathrm{l}} - \hyperlink{ell}{\ell}(B_{\mathbf{R}})_{\mathrm{u}}
\end{array}
\end{equation}
注意最后的条件: 交易的 gas 上限 $T_{\mathrm{g}}$ 和由 $\hyperlink{ell}{\ell}(B_{\mathbf{R}})_{\mathrm{u}}$ 所给定的这个区块先前已经使用的 gas 数量之和,不可以超过当前区块的\textbf{gasLimit} ${B_{H}}_{\mathrm{l}}$。
有效交易的执行起始于一个对状态不能撤回的改变:\hyperlink{account_nonce}{发送者账户的 nonce},即 $S(T)$ 的 nonce 会加 1,并且从账户余额扣减预付费用 $T_{\mathrm{g}}T_{\mathrm{p}}$。进行实际计算的可用 gas $g$ 被定义为 $T_{\mathrm{g}} - g_0$。无论是合约创建还是消息调用,计算都会产生一个最终状态(可能等于当前的状态),这种改变是确定的且从来不会无效:这样来看,其实并不存在无效的交易。
我们定义检查点状态 $\boldsymbol{\sigma}_0$:
\begin{eqnarray}
\boldsymbol{\sigma}_0 & \equiv & \boldsymbol{\sigma} \quad \text{except:} \\
\boldsymbol{\sigma}_0[S(T)]_{\mathrm{b}} & \equiv & \boldsymbol{\sigma}[S(T)]_{\mathrm{b}} - T_{\mathrm{g}} T_{\mathrm{p}} \\
\boldsymbol{\sigma}_0[S(T)]_{\mathrm{n}} & \equiv & \boldsymbol{\sigma}[S(T)]_{\mathrm{n}} + 1
\end{eqnarray}
从 $\boldsymbol{\sigma}_0$ 到 $\boldsymbol{\sigma}_{P}$ 的计算依赖于交易的类型。不管它是合约创建还是消息调用,我们都可以定义这样的元组,包含执行后的临时状态 $\boldsymbol{\sigma}_{P}$,剩余的 gas $g'$,子状态 $A$ 和状态代码 $z$:
\begin{equation}
(\boldsymbol{\sigma}_{P}, g', A, z) \equiv \begin{cases}
\Lambda_{4}(\boldsymbol{\sigma}_0, S(T), T_{\mathrm{o}}, g, &\\ \quad\quad T_{\mathrm{p}}, T_{\mathrm{v}}, T_{\mathbf{i}}, 0, \top) & \text{if} \quad T_{\mathrm{t}} = \varnothing \\
\Theta_{4}(\boldsymbol{\sigma}_0, S(T), T_{\mathrm{o}}, T_{\mathrm{t}}, T_{\mathrm{t}}, &\\ \quad\quad g, T_{\mathrm{p}}, T_{\mathrm{v}}, T_{\mathrm{v}}, T_{\mathbf{d}}, 0, \top) & \text{otherwise}
\end{cases}
\end{equation}
其中 $g$ 是扣除掉合约存在所需要的基本 gas 数量之后的剩余 gas 数量:
\begin{equation}
g \equiv T_{\mathrm{g}} - g_0
\end{equation}
$T_{\mathrm{o}}$ 是交易的原始发起人,当一个消息调用或合约创建不是由交易所触发,而是来自于 EVM 代码的运行所触发时,这个原始发起人会与发送者不同(即在这种情况下,$T_{\mathrm{o}}$ 会与 $S(T)$ 不同,校订注)。
注意,我们使用 $\Theta_{4}$ 和 $\Lambda_{4}$ 来表示只取用那组函数中的前 4 个函数的值,这最终表现为消息调用的输出(一个字节数组),且这个最终结果不会在交易计算的上下文中使用。
在消息调用或合约创建被处理之后,在此过程中自毁的账户的返还余额计数也已经增加完成了。
\begin{equation}
A'_{r} \equiv A_{r} + \sum_{i \in A_{s}} R_{selfdestruct}
\end{equation}
然后,就可以用剩余的 gas $g'$ 加上基于返还计数的补贴来按照原始比率确定返还给发送者的 gas 数量 $g^*$ 了。
\begin{equation}
g^* \equiv g' + \min \left\{ \Big\lfloor \dfrac{T_{\mathrm{g}} - g'}{2} \Big\rfloor, \hyperlink{refund_balance_defn_words_A__r}{A'_{\mathrm{r}}} \right\}
\end{equation}
总共可返还的数量,是合法的剩余 gas $g'$ 加上 \hyperlink{refund_balance_defn_words_A__r}{$A_{\mathrm{r}}$} 与总消耗量 $T_{\mathrm{g}} - g'$ 的一半进行四舍五入后的结果中较小的那个值。所以 $g^*$ 就是交易执行后的总剩余 gas。
交易花费的 gas 所对应的以太币会支付给矿工,它们的地址是由当前区块 $B$ 的 beneficiary 所指定的。所以我们基于临时状态 $\boldsymbol{\sigma}_{P}$ 来定义预备最终状态 $\boldsymbol{\sigma}^*$:
\begin{eqnarray}
\boldsymbol{\sigma}^* & \equiv & \boldsymbol{\sigma}_{P} \quad \text{except} \\
\boldsymbol{\sigma}^*[S(T)]_{\mathrm{b}} & \equiv & \boldsymbol{\sigma}_{P}[S(T)]_{\mathrm{b}} + g^* T_{\mathrm{p}} \\
\boldsymbol{\sigma}^*[m]_{\mathrm{b}} & \equiv & \boldsymbol{\sigma}_{P}[m]_{\mathrm{b}} + (T_{\mathrm{g}} - g^*) T_{\mathrm{p}} \\
m & \equiv & {B_{H}}_{\mathrm{c}}
\end{eqnarray}
在删除了所有出现在自毁列表中的账户或被接触过的空账户之后,即可达到最终状态 $\boldsymbol{\sigma}'$:
\begin{eqnarray}
\boldsymbol{\sigma}' & \equiv & \boldsymbol{\sigma}^* \quad \text{except} \\
\linkdest{self_destruct_list_A__s}{}\forall i \in A_{\mathbf{s}}: \boldsymbol{\sigma}'[i] & = & \varnothing \\
\linkdest{touched_A__t}{}\forall i \in A_{\mathbf{t}}: \boldsymbol{\sigma}'[i] & = & \varnothing \quad\text{if}\quad \mathtt{\tiny DEAD}(\boldsymbol{\sigma}^*\kern -2pt, i)
\end{eqnarray}
\hypertarget{tx_total_gas_used_Upsilon_pow_g}{}\linkdest{Upsilon_pow_g}\hypertarget{tx_logs_Upsilon_pow_l}{}\linkdest{Upsilon_pow_l}\hypertarget{tx_status_Upsilon_pow_z}{}\linkdest{Upsilon_pow_z}最后,我们定义这个交易中总共使用的 gas $\Upsilon^{\mathrm{g}}$、这个交易中创建的日志 $\Upsilon^{\mathbf{l}}$ 和这个交易的状态代码 $\Upsilon^{\mathrm{z}}$:
\begin{eqnarray}
\Upsilon^{\mathrm{g}}(\boldsymbol{\sigma}, T) & \equiv & T_{\mathrm{g}} - g^* \\
\Upsilon^{\mathbf{l}}(\boldsymbol{\sigma}, T) & \equiv & \hyperlink{tx_log_series_wordy_defn_A__l}{A_{\mathbf{l}}} \\
\Upsilon^{\mathrm{z}}(\boldsymbol{\sigma}, T) & \equiv & z
\end{eqnarray}
这些用来帮助我们定义 \hyperlink{Transaction_Receipt}{交易收据},并且也将用在\hyperlink{Upsilon_pow_g2}{后续的}状态和 nonce 校验中。
\section{合约创建}\label{ch:create}\hypertarget{endow}{}
创建一个账户需要很多固有参数:发送者($s$)、原始交易人($o$)、可用的 gas($g$)、gas 价格($p$)、endowment($v$,即初始捐款)、任意长度的字节数组(即EVM初始化代码)$\mathbf{i}$、消息调用/合约创建的当前栈深度($e$)以及对状态进行修改的许可($w$)。
我们定义创建函数为函数 $\Lambda$,它将使用上述参数,和状态 $\boldsymbol{\sigma}$ 一起来计算出一个新的元组。就像第 \ref{ch:transactions} 节所介绍的那样,这个新的元组包含新的状态、剩余的 gas、交易子状态以及一个错误消息 $(\boldsymbol{\sigma}', g', A, \mathbf{o})$:
\begin{equation}
(\boldsymbol{\sigma}', g', A, z, \mathbf{o}) \equiv \Lambda(\boldsymbol{\sigma}, s, o, g, p, v, \mathbf{i}, e, w)
\end{equation}
新账户的地址是一个哈希值的最右边 160 位,这个哈希值是由一个仅包含发送者地址和\hyperlink{account_nonce}{其账户 nonce} 的结构进行 \hyperlink{rlp}{RLP} 编码之后再进行 Keccak 哈希计算所得到的。我们可以定义由此得来的新帐户的地址 $a$:
\begin{equation}
a \equiv \mathcal{B}_{96..255}\Big(\mathtt{\tiny KEC}\Big(\mathtt{\tiny RLP}\big(\;(s, \boldsymbol{\sigma}[s]_{\mathrm{n}} - 1)\;\big)\Big)\Big)
\end{equation}
其中 $\mathtt{\tiny KEC}$ 是 Keccak 256 哈希函数,$\mathtt{\tiny RLP}$ 是 RLP 编码函数,$\mathcal{B}_{a..b}(X)$ 表示取二进制数据 $X$ 的位数范围 $[a, b]$ 的值,$\boldsymbol{\sigma}[x]$ 则是地址 $x$ 的状态,或者 $\varnothing$ 表示什么都不存在。注意,我们使用的是一个比发送者 nonce 要小的值(减了 1);因为我们认定我们已经在这个调用之前对发送者的 nonce 加了 1,因此所用的值是发送者在该交易或 VM 操作开始时的 nonce。
账户的 nonce 被初始定义为 1,balance 为交易传入的值,storage 为空,codeHash 为空字符串的 Keccak 256 位哈希值;发送者的 balance 会减去转账值。于是这个变化的状态就成为 $\boldsymbol{\sigma}^*$:
\begin{equation}
\boldsymbol{\sigma}^* \equiv \boldsymbol{\sigma} \quad \text{except:}
\end{equation}
\begin{eqnarray}
\boldsymbol{\sigma}^*[a] &=& \big( 1, v + v', \mathtt{\tiny \hyperlink{trie}{TRIE}}(\varnothing), \mathtt{\tiny KEC}\big(()\big) \big) \\
\boldsymbol{\sigma}^*[s] &=& \begin{cases}
\varnothing & \text{if}\ \boldsymbol{\sigma}[s] = \varnothing \ \wedge\ v = 0 \\
\mathbf{a}^* & \text{otherwise}
\end{cases} \\
\mathbf{a}^* &\equiv& (\boldsymbol{\sigma}[s]_{\mathrm{n}}, \boldsymbol{\sigma}[s]_{\mathrm{b}} - v, \boldsymbol{\sigma}[s]_{\mathbf{s}}, \boldsymbol{\sigma}[s]_{\mathrm{c}})
\end{eqnarray}
其中 $v'$ 是账户在交易之前就有的余额,对应于它先前就已经存在的情况:
\begin{equation}
v' \equiv \begin{cases}
0 & \text{if} \quad \boldsymbol{\sigma}[a] = \varnothing\\
\boldsymbol{\sigma}[a]_{\mathrm{b}} & \text{otherwise}
\end{cases}
\end{equation}
%It is asserted that the state database will also change such that it defines the pair $(\mathtt{\tiny KEC}(\mathbf{b}), \mathbf{b})$.
最终,账户是根据执行模型(参见第 \ref{ch:model} 章)通过执行账户初始化的 EVM 代码 $\mathbf{i}$ 来初始化的。代码的执行可以产生一些内部执行状态以外的事件,包括:更改账户的 storage,创建更多的账户,以及进行更多的消息调用。用代码执行函数 $\Xi$ 可以得到一个元组,包括结果状态 $\boldsymbol{\sigma}^{**}$,可用的剩余 gas $g^{**}$,累积的子状态 $A$ 以及账户的代码 $\mathbf{o}$。
\begin{equation}
(\boldsymbol{\sigma}^{**}, g^{**}, A, \mathbf{o}) \equiv \Xi(\boldsymbol{\sigma}^*, g, I, \{s, a\}) \\
\end{equation}
\pagebreak[1]其中 $I$ 包含了\hyperlink{exec_env}{执行环境}的相关参数:\pagebreak[1]
\begin{eqnarray}
I_{\mathrm{a}} & \equiv & a \\
I_{\mathrm{o}} & \equiv & o \\
I_{\mathrm{p}} & \equiv & p \\
I_{\mathbf{d}} & \equiv & () \\
I_{\mathrm{s}} & \equiv & s \\
\hyperlink{I__v}{I_{\mathrm{v}}} & \equiv & v \\
I_{\mathbf{b}} & \equiv & \mathbf{i} \\
I_{\mathrm{e}} & \equiv & e \\
I_{\mathrm{w}} & \equiv & w
\end{eqnarray}
由于这个调用没有任何输入数据,$I_\mathbf{d}$ 为空的元组。$I_H$ 则没有特别之处,由区块链所决定。
代码执行会消耗 gas,且 gas 不能低于 0,因此执行可能会在自然停止之前结束。在这个(以及其它几个)异常情况,我们称之为发生了 gas 不足(out-of-gas,OOG)异常:计算后的状态将由空集合 $\varnothing$ 表示,整个创建操作将不会影响状态,就像刚开始尝试创建时那样。
如果这个初始化代码成功地执行完,那么对应的合约创建费用也会支付。这个代码保存费用 $c$ 与创建出来的合约代码大小成正比:
\begin{equation}
c \equiv G_{codedeposit} \times |\mathbf{o}|
\end{equation}
如果没有足够的剩余 gas 来支付这个费用,也就是说如果 $g^{**} < c$,就会产生 gas 不足异常。
发生这样的异常后,剩余 gas 将变为 0。也就是说,如果合约创建是作为对交易的接受来处理的,那么它就不会影响合约创建的固有费用的支付,就是肯定会支付。然而,当 gas 不足时,交易中附带的金额并不会转移到被取消的合约地址。
如果没有出现这样的异常,那么剩余的 gas 会返还给最原始的交易发起人,对状态的改变也将永久保存。这样,我们可以指定结果状态、gas、子状态和状态代码为 $(\boldsymbol{\sigma}', g', A, z)$:
\begin{align}
\quad g' &\equiv \begin{cases}
0 & \text{if} \quad F \\
g^{**} - c & \text{otherwise} \\
\end{cases} \\
\quad \boldsymbol{\sigma}' &\equiv \begin{cases}
\boldsymbol{\sigma} & \text{if} \quad F \\
\boldsymbol{\sigma}^{**} \quad \text{except:} & \\
\quad\boldsymbol{\sigma}'[a] = \varnothing & \text{if} \quad \mathtt{\tiny DEAD}(\boldsymbol{\sigma}^{**}, a) \\
\boldsymbol{\sigma}^{**} \quad \text{except:} & \\
\quad\boldsymbol{\sigma}'[a]_{\mathrm{c}} = \texttt{\small KEC}(\mathbf{o}) & \text{otherwise}
\end{cases} \\
\quad z &\equiv \begin{cases}
0 & \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \lor g^{**} < c \\
1 & \text{otherwise}
\end{cases} \\
\nonumber \text{where} \\
F &\equiv \big((\boldsymbol{\sigma}^{**} = \varnothing \ \wedge\ \mathbf{o} = \varnothing) \vee\ g^{**} < c \ \vee\ |\mathbf{o}| > 24576\big)
\end{align}
对于确定 $\boldsymbol{\sigma}'$ 的例外,由初始化代码的执行结果字节序列 $\mathbf{o}$ 所决定,它就是新创建账户的最终代码。
注意,这是意图达到这样的结果:要么带着初始捐款(endowment)成功创建合约;要么不会创建任何合约且不会进行转账。
\subsection{细微之处}
注意,在初始化代码执行过程中,一个新创建的地址会出现,但还没有内部的代码\footnote{在初始化代码的执行过程中,当前地址上的 \texttt{EXTCODESIZE} 也就是账户代码的长度应该返回 0。而 \texttt{CODESIZE} 则应该返回初始化代码的长度。(就像\ref{subsec:instruction-set}中定义的那样。)}。因此在这段时间内,任何消息调用都不会引发代码执行。如果这个初始化执行结束于一个 {\small SELFDESTRUCT} 指令,那么这个账户会在交易完成前被删除,这种情况是否合理已在讨论中。对于一个正常的 {\small STOP} 指令代码,或者返回的代码是空的,这时候会出现一个僵尸账户,而且账户中剩余的余额将被永远地锁定在这个僵尸账户中。
\section{消息调用} \label{ch:call}
当执行消息调用时需要多个参数:发送者($s$)、交易发起人($o$)、接收者($r$)、执行代码的账户($c$,通常与接收者相同)、可用的 gas($g$)、转账金额($v$)、gas 价格($p$)、函数调用的输入数据(一个任意长度的字节数组 $\mathbf{d}$)、消息调用/合约创建的当前栈深度($e$)以及对状态修改的许可($w$)。
除了可以获得新的状态和交易子状态以外,消息调用还有一个额外的元素——由字节数组 $\mathbf{o}$ 表示的输出数据。执行交易时输出数据是被忽略的,但消息调用可以由 VM 代码执行所产生,在这种情况下就将使用这些信息。
\begin{equation}
(\boldsymbol{\sigma}', g', A, z, \mathbf{o}) \equiv \Theta(\boldsymbol{\sigma}, s, o, r, c, g, p, v, \tilde{v}, \mathbf{d}, e, w)
\end{equation}
注意,对于 {\small DELEGATECALL} 指令,我们需要区分转账金额 $v$ 和执行上下文中出现的 $\tilde{v}$。
我们定义 $\boldsymbol{\sigma}_1$ 为第一个交易状态,它是在原始状态的基础上加入由发送者向接收者的转账金额后的状态:
\begin{equation}
\boldsymbol{\sigma}_1[r]_{\mathrm{b}} \equiv \boldsymbol{\sigma}[r]_{\mathrm{b}} + v \quad\wedge\quad \boldsymbol{\sigma}_1[s]_{\mathrm{b}} \equiv \boldsymbol{\sigma}[s]_{\mathrm{b}} - v
\end{equation}
除非 $s = r$。
按照先前的论述(我们假设如果 $\boldsymbol{\sigma}_1[r]$ 还未定义),则这会创建一个没有代码或状态且 balance 和 nonce 都为 0 的账户。因此上一个公式可以替换为:
\begin{equation}
\boldsymbol{\sigma}_1 \equiv \boldsymbol{\sigma}_1' \quad \text{except:} \\
\end{equation}
\begin{equation}
\boldsymbol{\sigma}_1[s] \equiv \begin{cases}
\varnothing & \text{if}\ \boldsymbol{\sigma}_1'[s] = \varnothing \ \wedge\ v = 0 \\
\mathbf{a}_1 &\text{otherwise}
\end{cases}
\end{equation}
\begin{equation}
\mathbf{a}_1 \equiv \left(\boldsymbol{\sigma}_1'[s]_{\mathrm{n}}, \boldsymbol{\sigma}_1'[s]_{\mathrm{b}} - v, \boldsymbol{\sigma}_1'[s]_{\mathbf{s}}, \boldsymbol{\sigma}_1'[s]_{\mathrm{c}}\right)
\end{equation}
\begin{equation}
\text{and}\quad \boldsymbol{\sigma}_1' \equiv \boldsymbol{\sigma} \quad \text{except:} \\
\end{equation}
\begin{equation}
\begin{cases}
\boldsymbol{\sigma}_1'[r] \equiv (0, v, \mathtt{\tiny TRIE}(\varnothing), \mathtt{\tiny KEC}(())) & \text{if} \quad \boldsymbol{\sigma}[r] = \varnothing \wedge v \neq 0 \\
\boldsymbol{\sigma}_1'[r] \equiv \varnothing & \text{if}\quad \boldsymbol{\sigma}[r] = \varnothing \wedge v = 0 \\
\boldsymbol{\sigma}_1'[r] \equiv \mathbf{a}_1' & \text{otherwise}
\end{cases}
\end{equation}
\begin{equation}
\mathbf{a}_1' \equiv (\boldsymbol{\sigma}[r]_{\mathrm{n}}, \boldsymbol{\sigma}[r]_{\mathrm{b}} + v, \boldsymbol{\sigma}[r]_{\mathbf{s}}, \boldsymbol{\sigma}[r]_{\mathrm{c}})
\end{equation}
账户关联的代码(由 Keccak 哈希为 $\boldsymbol{\sigma}[c]_{\mathrm{c}}$ 的代码片段所标识)会依照执行模型(参见第 \ref{ch:model} 章)来执行。就像合约创建一样,如果执行因为一个异常(也就是说:因为 gas 供给不足、堆栈溢出、无效的跳转目标或者无效的指令)而停止,gas 不会被返还给调用者,并且状态也会立即恢复到转账之前的状态(也就是 $\boldsymbol{\sigma}$)。
\begin{eqnarray}
\boldsymbol{\sigma}' & \equiv & \begin{cases}
\boldsymbol{\sigma} & \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \\
\boldsymbol{\sigma}^{**} & \text{otherwise}
\end{cases} \\
g' & \equiv & \begin{cases}
0 & \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \ \wedge \\
&\quad \mathbf{o} = \varnothing \\
g^{**} & \text{otherwise}
\end{cases} \\ \nonumber
z & \equiv & \begin{cases}
0 & \text{if} \quad \boldsymbol{\sigma}^{**} = \varnothing \\
1 & \text{otherwise}
\end{cases} \\
(\boldsymbol{\sigma}^{**}, g^{**},A, \mathbf{o}) & \equiv & \Xi\\
I_{\mathrm{a}} & \equiv & r \\
I_{\mathrm{o}} & \equiv & o \\
I_{\mathrm{p}} & \equiv & p \\
I_{\mathbf{d}} & \equiv & \mathbf{d} \\
I_{\mathrm{s}} & \equiv & s \\
I_{\mathrm{v}} & \equiv & \tilde{v} \\
I_{\mathrm{e}} & \equiv & e \\
I_{\mathrm{w}} & \equiv & w \\
\mathbf{t} & \equiv & \{s, r\} \\
\end{eqnarray}
\nopagebreak[1]其中
\begin{equation}
\Xi \equiv \begin{cases}
\Xi_{\mathtt{ECREC}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 1 \\
\Xi_{\mathtt{SHA256}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 2 \\
\Xi_{\mathtt{RIP160}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 3 \\
\Xi_{\mathtt{ID}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 4 \\
\Xi_{\mathtt{EXPMOD}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 5 \\
\Xi_{\mathtt{BN\_ADD}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 6 \\
\Xi_{\mathtt{BN\_MUL}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 7 \\
\Xi_{\mathtt{SNARKV}}(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{if} \quad r = 8 \\
\Xi(\boldsymbol{\sigma}_1, g, I, \mathbf{t}) & \text{otherwise} \end{cases}
\end{equation}
\begin{equation}
\text{Let} \; \mathtt{\tiny KEC}(I_{\mathbf{b}}) = \boldsymbol{\sigma}[c]_{\mathrm{c}}
\end{equation}
我们假设客户端会保存键值对数据 $(\mathtt{\tiny KEC}(I_{\mathbf{b}}), I_{\mathbf{b}})$,来在某些情况下使获得 $I_{\mathbf{b}}$ 成为可能。
如我们所见,在消息调用的通用执行框架 $\Xi$ 中有 8 个特例:这是 8 个所谓的‘预编译’合约,它们作为最初架构中的一部分,后续可能会变成\textit{原生扩展}。在地址 1 到 8 上存在的这 8 个合约,分别是椭圆曲线公钥恢复函数、SHA2 256 位哈希方案、RIPEMD 160 位哈希方案、标识函数、任意精度的模幂运算、椭圆曲线加法、椭圆曲线纯量乘法和椭圆曲线配对检查。
它们的完整定义参见附录 \ref{app:precompiled}。
\section{执行模型} \label{ch:model}
执行模型说明了如何使用一系列的字节代码指令和一个环境数据的元组去更改系统状态。这是通过一个正规的虚拟状态机来实现的,也就是以太坊虚拟机(Ethereum Virtual Machine - EVM)。它是一个\textit{准}图灵机,这个“\textit{准}”的限定来源于其中的运算是通过参数 \textit{gas} 来限制的,也就是限定了可以执行的运算总量。
\subsection{基础}
EVM 是一个简单的基于栈的架构。其中字(Word)的大小(也就是栈中数据项的大小)是 256 位。这是为了便于执行 Keccak-256 位哈希和椭圆曲线计算。其内存(memory)模型是简单地基于字寻址(word-addressed)的字节数组。栈的最大深度为 1024。EVM 也有一个独立的存储(storage)模型;它类似于内存的概念,但并不是一个字节数组,而是一个基于字寻址(word-addressable)的字数组(word array)。与不稳定的内存不同,存储是相对稳定的且作为系统状态的一部分被维护。所有内存和存储中的数据都会初始化为 0。
EVM 不是标准的冯 诺依曼结构。程序代码被保存在一个独立的、仅能通过特定的指令进行交互的虚拟 ROM(即只读存储器,校订注)中,而不是保存在一般的可访问内存或存储中。
EVM 存在异常执行的情况,包括堆栈溢出和非法指令。在发生像 out-of-gas 这样的异常时,EVM 并不会使状态保持原样,而是会立即停止执行,并告知执行代理(交易的处理程序或递归的执行环境),由代理来独立处理这些异常。
\subsection{费用概述}
在三个不同的情况下会收取执行费用(以 gas 来结算),这三种情况都是执行操作的先决条件。第一种情况,也是最普遍的情况就是计算操作费用(详见附录 \ref{app:fees})。第二种情况,执行一个低级别的消息调用或者合约创建可能需要扣除 gas,这也就是执行 {\small CREATE},{\small CALL} 和 {\small CALLCODE} 的费用的一部分。最后,内存使用的增加也会消耗一定的 gas。
对于一个账户的执行,内存的总费用和其所有内存索引(无论是读还是写)的范围成正比;这个内存范围是 32 字节的最小倍数。这是实时(just-in-time)结算的;也就是说,任何对超出先前已索引的内存区域的访问,都会实时地结算为额外的内存使用费。由于这个费用,内存地址大概不会超过 32 位的界限,但 EVM 的实现必须能够管理这种可能性(就是说,虽然如果内存地址超过 32 位,即大于$2^{32}$,会产生很高的内存使用费,但 EVM 的实现还是应该支持这种可能性;校订注)。
存储费用则有一个细微差别的行为——激励存储的最小化使用(这直接反映在所有节点中更大的状态数据库里)。清除一个存储中的记录项的执行费用不仅被免除了,而且还会返还;实际上,这种费用返还(退款)是预先发生的,因为首次使用存储位置的费用比正常使用高出很多。
EVM gas 消耗的严格定义参见附录 \ref{app:vm}。
\subsection{执行环境}
作为系统状态 $\boldsymbol{\sigma}$ 和计算中剩余的 gas $g$ 的补充,还有一些执行代理必须提供的有关执行环境的重要信息,它们包含在元组 $I$ 中:
\begin{itemize}
\item\linkdest{address_of_account_which_owns_code_I__a_def_words}{}\linkdest{I__a} $I_{\mathrm{a}}$,拥有正在执行的代码的账户地址。
\item\linkdest{address_of_sender_of_tx_that_originated_execution_I__o}{}\linkdest{I__o} $I_{\mathrm{o}}$,触发这次执行的初始交易的发送者地址。
\item\linkdest{gas_price_in_tx_that_originated_execution_I__p}{}\linkdest{I__p} $I_{\mathrm{p}}$,触发这次执行的初始交易的 gas 价格。
\item\linkdest{execution_input_data_I__d}{}\linkdest{I__d} $I_{\mathbf{d}}$,这次执行的输入数据字节数组;如果执行代理是一个交易,这就是交易数据。
\item\linkdest{the_address_of_the_account_which_caused_the_code_to_be_executing_I__s}{} $I_{\mathrm{s}}$,触发这次执行的账户地址;如果执行代理是一个交易,则为交易发送者地址。
\item\linkdest{Wei_value_exec_I__v}{}\linkdest{I__v}{} $I_{\mathrm{v}}$,作为这次执行的一部分传到当前账户的转账金额,以 Wei 为单位;如果执行代理是一个交易, 这就是交易的转账金额。
\item\linkdest{exec_machine_code_I__b}{}\linkdest{I__b} $I_{\mathbf{b}}$,所要执行的机器代码字节数组。
\item\linkdest{exec_block_header_I__H}{}\linkdest{I__H} $I_{\mathbf{H}}$,当前区块的区块头。
\item\linkdest{exec_call_or_create_depth_I__e}{}\linkdest{I__e} $I_{\mathrm{e}}$,当前消息调用或合约创建的深度(也就是当前已经被执行的 {\small CALL} 或 {\small CREATE} 的数量)。
\item\linkdest{exec_permission_to_modify_state_I__w}{}\linkdest{I__w} $I_{\mathrm{w}}$,修改状态的许可。
\end{itemize}
执行模型定义了函数 $\Xi$,它可以用来计算结果状态 $\boldsymbol{\sigma}'$,剩余的 gas $g'$,累积的子状态 $A$ 和结果输出 $\mathbf{o}$。根据当前的上下文,我们可以把它定义为:
\begin{equation}
(\boldsymbol{\sigma}', g', A, \mathbf{o}) \equiv \Xi(\boldsymbol{\sigma}, g, I)
\end{equation}
这里,我们应该还记得这个累积的子状态 $A$ 是由自毁集合 $\mathbf{s}$、日志集 $\mathbf{l}$、接触过的账户 $\mathbf{t}$ 和返还金额 $r$ 所组成的元组:
\begin{equation}
A \equiv (\mathbf{s}, \mathbf{l}, \mathbf{t}, r)
\end{equation}
\subsection{执行概述}
我们现在必须来定义 $\Xi$ 函数了。在大多数可行的实现方案中,都应该对完整的系统状态 $\boldsymbol{\sigma}$ 和机器状态 $\boldsymbol{\mu}$ 一起进行迭代建模。我们定义一个递归函数 $X$,它使用了一个迭代函数 $O$(定义状态机中单次循环的结果),与一个用来确定当前机器状态是否是一个\hyperlink{zhalt}{异常停止}状态的函数 \hyperlink{zhalt}{$Z$} 和一个当且仅当当前机器状态是\hyperlink{hhalt}{正常停止}状态时用来指定指令的输出数据的函数 \hyperlink{hhalt}{$H$}。
\hypertarget{empty_sequence_vs_empty_set}{}由 $()$ 表示的空序列并不等于由 $\varnothing$ 表示的空集合,这对于解释 $H$ 的输出非常重要;当 $H$ 的输出是 $\varnothing$ 时需要继续执行,而当其为空序列时则应该停止执行。
\begin{eqnarray}
\Xi(\boldsymbol{\sigma}, g, I, T) & \equiv & (\boldsymbol{\sigma}'\!, \boldsymbol{\mu}'_{\mathrm{g}}, A, \mathbf{o}) \\
(\boldsymbol{\sigma}', \boldsymbol{\mu}'\!, A, ..., \mathbf{o}) & \equiv & X\big((\boldsymbol{\sigma}, \boldsymbol{\mu}, A^0\!, I)\big) \\
\boldsymbol{\mu}_{\mathrm{g}} & \equiv & g \\
\boldsymbol{\mu}_{\mathrm{pc}} & \equiv & 0 \\
\boldsymbol{\mu}_{\mathbf{m}} & \equiv & (0, 0, ...) \\
\boldsymbol{\mu}_{\mathrm{i}} & \equiv & 0 \\
\boldsymbol{\mu}_{\mathbf{s}} & \equiv & () \\
\boldsymbol{\mu}_{\mathbf{o}} & \equiv & ()
\end{eqnarray}
\begin{equation} \label{eq:X-def}
X\big( (\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \big) \equiv \begin{cases}
\big(\varnothing, \boldsymbol{\mu}, A^0, I, \varnothing\big) & \text{if} \quad Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \\
\big(\varnothing, \boldsymbol{\mu}', A^0, I, \mathbf{o}\big) & \text{if} \quad w = \text{\small REVERT} \\
O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I) \cdot \mathbf{o} & \text{if} \quad \mathbf{o} \neq \varnothing \\
X\big(O(\boldsymbol{\sigma}, \boldsymbol{\mu}, A, I)\big) & \text{otherwise} \\
\end{cases}
\end{equation}
其中
\begin{eqnarray}
\mathbf{o} & \equiv & H(\boldsymbol{\mu}, I) \\
(a, b, c, d) \cdot e & \equiv & (a, b, c, d, e) \\
\boldsymbol{\mu}' & \equiv & \boldsymbol{\mu}\ \text{except:} \\
\boldsymbol{\mu}'_{\mathrm{g}} & \equiv & \boldsymbol{\mu}_{\mathrm{g}} - C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I)
\end{eqnarray}
注意,在推导 $\Xi$ 时,我们去掉了第 4 个元素 $I'$ 并从机器状态的结果 $\boldsymbol{\mu}'$ 中提取了剩余的 gas $\boldsymbol{\mu}'_{\mathrm{g}}$。
$X$ 是被循环调用的(这里是递归,但是在实现方案中通常是去执行一个简单的迭代循环)直到 \hyperlink{zhalt}{$Z$} 变为 true,表示当前状态有异常,必须停止执行,并且所有的改动都会被舍弃;或者直到 \hyperlink{hhalt}{$H$} 变为一个序列(不是空集合),表示机器达到了正常控制的停止状态。
\subsubsection{机器状态}
机器状态 $\boldsymbol{\mu}$ 是由一个元组 $(g, pc, \mathbf{m}, i, \mathbf{s})$ 所定义的,其中包括可用的 gas $g$,程序计数器 $pc \in \mathbb{N}_{256}$,内存的内容 $\mathbf{m}$,内存中激活的字数(从 0 开始的连续计数) $i$,以及栈的内容 $\mathbf{s}$。内存的内容 $\boldsymbol{\mu}_\mathbf{m}$ 是大小为 $2^{256}$ 的全 0 序列。
为了提高可读性,应该使用大写字母简写(例如 {\small ADD})的指令助记符来推演数字等式。完整的指令列表和它们的定义参见附录 \ref{app:vm}。
为了定义 $Z$、$H$ 和 $O$,我们将当前要执行的操作定义为 $w$:
\begin{equation}\label{eq:currentoperation}
w \equiv \begin{cases} I_{\mathbf{b}}[\boldsymbol{\mu}_{\mathrm{pc}}] & \text{if} \quad \boldsymbol{\mu}_{\mathrm{pc}} < \lVert I_{\mathbf{b}} \rVert \\
\text{\hyperlink{stop}{\small STOP}} & \text{otherwise}
\end{cases}
\end{equation}
我们也假定从栈中移除和添加的条目的固定数量为 $\mathbf{\delta}$ 和 $\mathbf{\alpha}$,它们都可以作为具体指令的下标;并且指令费用函数 $C$ 可以算出给定指令的全部费用(以 gas 为单位)。
\subsubsection{异常停止}\hypertarget{Exceptional_Halting_function_Z}{}\linkdest{zhalt}
异常停止函数 $Z$ 定义如下:
\begin{equation}
Z(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \equiv
\begin{array}[t]{l}
\boldsymbol{\mu}_g < C(\boldsymbol{\sigma}, \boldsymbol{\mu}, I) \quad \vee \\
\mathbf{\delta}_w = \varnothing \quad \vee \\
\lVert\boldsymbol{\mu}_\mathbf{s}\rVert < \mathbf{\delta}_w \quad \vee \\
( w = \text{\small JUMP} \quad \wedge \quad \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\
( w = \text{\small JUMPI} \quad \wedge \quad \boldsymbol{\mu}_\mathbf{s}[1] \neq 0 \quad \wedge \\
\quad \boldsymbol{\mu}_\mathbf{s}[0] \notin D(I_\mathbf{b}) ) \quad \vee \\
( w = \text{\small RETURNDATACOPY} \wedge \\ \quad \boldsymbol{\mu}_{\mathbf{s}}[1] + \boldsymbol{\mu}_{\mathbf{s}}[2] > \lVert\boldsymbol{\mu}_{\mathbf{o}}\rVert) \quad \vee \\
\lVert\boldsymbol{\mu}_\mathbf{s}\rVert - \mathbf{\delta}_w + \mathbf{\alpha}_w > 1024 \vee
(\neg I_{\mathrm{w}} \wedge W(w, \boldsymbol{\mu}))
\end{array}
\end{equation}
其中
\begin{equation}
W(w, \boldsymbol{\mu}) \equiv \\
\begin{array}[t]{l}
w \in \{\text{\small CREATE}, \text{\small SSTORE},\\ \text{\small SELFDESTRUCT}\} \ \vee \\
\text{\small LOG0} \le w \wedge w \le \text{\small LOG4} \quad \vee \\
w \in \{\text{\small CALL}, \text{\small CALLCODE}\} \wedge \boldsymbol{\mu}_{\mathbf{s}}[2] \neq 0
\end{array}