手动拆解一个block

一直以来,对比特币区块链数据存储的方式非常好奇。也许你跟我一样,看到过一些说明,但还是心存疑惑。本篇文章就深入又直观地解释一下,打消掉疑惑——我们来手动拆解一个区块。所以本篇文章是一片硬文,非常硬!

阅读本文的需要一些基础知识:比特、字节、十六进制。这些知识,在高中的计算机基础课程里都有介绍,有一些印象就足够了。

对于专业的人,你可以弄懂本文的细节,进而深刻了解区块链的数据存储方式。而对于非专业的人,你可以直观地读,读一个轮阔,有道是 “好读书,不求甚解,每有会意,便欣然忘食”。只不过看完本文的这个轮阔,你会看见真真实实的存在,不用靠别的一些比喻来“维持”理解。甚至你都可以感受到对于数据存储与处理计算机是如何工作的。

1. 区块的数据格式

我们取一个早期的区块,早期的区块包含的交易少一些,方便分析。网址为:
https://webbtc.com/block/00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
该网页里的格式formats:
- json 是用原始数据解析出来的,一种方便人阅读的格式;
- hex 是数据的16进制格式,形如3f323d…;
- binary是二进制原始数据,如01010001…

肉眼可读的json格式内容如下(https://webbtc.com/block/00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee.json

{
  "hash": "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee",
  "ver": 1,
  "prev_block": "000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55",
  "mrkl_root": "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff",
  "time": 1231731025,
  "bits": 486604799,
  "nonce": 1889418792,
  "n_tx": 2,
  "size": 490,
  "tx": [
    {
      "hash": "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082",
      "ver": 1,
      "vin_sz": 1,
      "vout_sz": 1,
      "lock_time": 0,
      "size": 134,
      "in": [
        {
          "prev_out": {
            "hash": "0000000000000000000000000000000000000000000000000000000000000000",
            "n": 4294967295
          },
          "coinbase": "04ffff001d0102"
        }
      ],
      "out": [
        {
          "value": "50.00000000",
          "scriptPubKey": "04d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725b OP_CHECKSIG"
        }
      ],
      "nid": "c56705435de47674259d6c92125907645d4fb512fa8e7f31457f5f29ba983d80"
    },
    {
      "hash": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
      "ver": 1,
      "vin_sz": 1,
      "vout_sz": 2,
      "lock_time": 0,
      "size": 275,
      "in": [
        {
          "prev_out": {
            "hash": "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9",
            "n": 0
          },
          "scriptSig": "304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"
        }
      ],
      "out": [
        {
          "value": "10.00000000",
          "scriptPubKey": "04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG",
          "next_in": {
            "hash": "ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e",
            "n": 0
          }
        },
        {
          "value": "40.00000000",
          "scriptPubKey": "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG",
          "next_in": {
            "hash": "a16f3ce4dd5deb92d98ef5cf8afeaf0775ebca408f708b2146c4fb42b41e14be",
            "n": 0
          }
        }
      ],
      "nid": "a1629e004eb3d703ecf3807f976e402a626d84c559f8eab1450adf207619f319"
    }
  ],
  "mrkl_tree": [
    "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082",
    "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
    "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
  ],
  "next_block": "00000000c9ec538cab7f38ef9c67a95742f56ab07b0a37c5be6b02808dbfb4e0"
}

针对上面的json格式,我们说几个关键的地方。”prev_block” 是前一个块的hash ID, “next_block”是后一个块的hash ID。tx 是本个区块打包的交易,第一个交易是coinbase, 就是我们之前文章《突破邓巴数字》中说的,在抢到仲裁打包后,用“上帝地址”给自己发币的那个交易,”hash”: “0000000000000000000000000000000000000000000000000000000000000000”, “value”: “50.00000000”, hash是0, 而value是50 ,也就是发了50个币。第二笔交易是一笔普通的转转帐交易,转帐完成后,两个输出分别是10个币和40个币,”value”: “10.00000000”, “value”: “40.00000000”。

实际上,区块数据真正存储的并不是以上字符的样子,其实计算机存储与运算的都是二进制数据,区块数据也是如此。但是二进制数据太长,不利于手动分析,我们选用等价的16进制格式。(https://webbtc.com/block/00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee.hex
该区块的16进制数据如下。

0100000055bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000ff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d51b96a49ffff001d283e9e700201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0102ffffffff0100f2052a01000000434104d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725bac000000000100000001c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704000000004847304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901ffffffff0200ca9a3b00000000434104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac00286bee0000000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000

我们用工具格式化一下,变为每行16字节:

0100 0000 55bd 840a 7879 8ad0 da85 3f68
974f 3d18 3e2b d1db 6a84 2c1f eecf 222a
0000 0000 ff10 4ccb 0542 1ab9 3e63 f8c3
ce5c 2c2e 9dbb 37de 2764 b3a3 175c 8166
562c ac7d 51b9 6a49 ffff 001d 283e 9e70
0201 0000 0001 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 ffff ffff 0704 ffff 001d
0102 ffff ffff 0100 f205 2a01 0000 0043
4104 d46c 4968 bde0 2899 d2aa 0963 367c
7a6c e34e ec33 2b32 e42e 5f34 07e0 52d6
4ac6 25da 6f07 18e7 b302 1404 34bd 7257
0695 7c09 2db5 3805 b821 a85b 23a7 ac61
725b ac00 0000 0001 0000 0001 c997 a5e5
6e10 4102 fa20 9c6a 852d d906 60a2 0b2d
9c35 2423 edce 2585 7fcd 3704 0000 0000
4847 3044 0220 4e45 e169 32b8 af51 4961
a1d3 a1a2 5fdf 3f4f 7732 e9d6 24c6 c615
48ab 5fb8 cd41 0220 1815 22ec 8eca 07de
4860 a4ac dd12 909d 831c c56c bbac 4622
0822 21a8 768d 1d09 01ff ffff ff02 00ca
9a3b 0000 0000 4341 04ae 1a62 fe09 c5f5
1b13 905f 07f0 6b99 a2f7 159b 2225 f374
cd37 8d71 302f a284 14e7 aab3 7397 f554
a7df 5f14 2c21 c1b7 303b 8a06 26f1 bade
d5c7 2a70 4f7e 6cd8 4cac 0028 6bee 0000
0000 4341 0411 db93 e1dc db8a 016b 4984
0f8c 53bc 1eb6 8a38 2e97 b148 2eca d7b1
48a6 909a 5cb2 e0ea ddfb 84cc f974 4464
f82e 160b fa9b 8b64 f9d4 c03f 999b 8643
f656 b412 a3ac 0000 0000 

接下来我们就要一个字节一个字节手动拆解了!

首先说明一下,计算机存储数据一般采用小端格式编码,小端的含义是字节存储的顺序与真实值正好是反着的,比如存的是 12 34,实际的值是3412。在以下分析中,我们会发现,其中区块的版本、父区块头哈希值和Merkle根等大都采用的是小端格式编码。

2. 80字节的区块头

0100 0000

“ver” ,版本号,4字节,由小端格式转换成正常的值: 00000001 。

          55bd 840a 7879 8ad0 da85 3f68
974f 3d18 3e2b d1db 6a84 2c1f eecf 222a
0000 0000

“prev_block”,32字节, 父区块头哈希值, 用SHA256(SHA256(父区块头))计算。
000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55

          ff10 4ccb 0542 1ab9 3e63 f8c3
ce5c 2c2e 9dbb 37de 2764 b3a3 175c 8166
562c ac7d

“mrkl_root”,32字节,Merkle根,同样采用SHA256(SHA256())计算。
7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff

          51b9 6a49

“time”,4字节 ,时间戳。 用python转换一下, int(0x496ab951) 十进制为 1231731025 , time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime(1381419600)) ,得到’2009-01-12 11:30:25’,这就是是这个块打包的时间。

                    ffff 001d 

“bits” ,4字节 ,难度目标。同上进行转换, int(0x1d00ffff) 为486604799 。当前区块的难度值0x1d00ffff需要采用一种特殊的编码方式才能正确转化为目标哈希值。
https://bitcoin.org/en/developer-reference#merkle-trees

                               283e 9e70

“nonce” ,709e3e28,4字节, Nonce。 int(0x709e3e28) 1889418792

3. 区块体

一个区块第一个交易称为coinbase交易。coinbase结构如下。

02

“n_tx” ,变长类型(CompactSize )1-9字节,该区块包含的交易数量,coinbase交易也计入在内。02代表本区块只有2笔交易。变长类型是长度可变的数据类型,具体解释可见https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer

3.1 第一个coinbase交易

   01 0000 00

“ver”,4字节 ,版本:这笔交易参照的规则, 0x00000001 ,版本为1

             01 

“vin_sz” ,变长类型,1-9字节,输入计数器:包含交易输入数量 ,0x01,表示只有一个输入。

               0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 

“hash”,交易哈希:不引用任何一个交易,值全部为0,总共256位 32字节,不引用任何一个UTXO 0000000000000000000000000000000000000000000000000000000000000000

               ffff ffff 

“n” ,4字节,交易输出索引,是固定值,为0xffffffff,转化为十进制值为 4294967295

                         07

变长类型,1-9字节, Coinbase数据长度,07即后面有7个字节是”coinbase”这个值的内容。

                            04 ffff 001d
0102 

“coinbase” ,”04ffff001d0102”,不定字节, Coinbase数据: 在V2版本的区块中,除了需要以区块高度开始外,其它数据可以任意填写,用于extra nonce和挖矿标签。

     ffff ffff 

4字节,顺序号:值全部为1,0xffffffff ,即4294967295

              01

“vout_sz”,1-9字节,输出计数器: 包含的交易数量。

                00 f205 2a01  0000 00

“value” ,”000000012a05f200 ” 8字节。变为十进制, 5000000000亿聪,

                                     43

变长,1-9字节, 锁定脚本大小:用字节表示的后面的锁定脚本长度。0x43, 即67,后面有67字节是锁定脚本的内容。

4104 d46c 4968 bde0 2899 d2aa 0963 367c
7a6c e34e ec33 2b32 e42e 5f34 07e0 52d6
4ac6 25da 6f07 18e7 b302 1404 34bd 7257
0695 7c09 2db5 3805 b821 a85b 23a7 ac61
725b ac

“scriptPubKey” ,锁定脚本: 一个定义了支付输出所需条件的脚本。开头的41含义,表示要将接下来的65个字节压入堆栈,最后一个字节的16进制数值,ac,表示的是OP_CHECKSIG
“04d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725b OP_CHECKSIG”

       00 0000 00

“lock_time”,4字节,锁定时间: 一个区块号或UNIX时间戳。 锁定时间为0,表示立即执行。

3.2 第二笔普通交易

                 01 0000 00

“ver”: 1, 4字节, 版本为1。

                       01 

“vin_sz”: 1 , 1-9字节, 输入计数器:包含的交易输入数量。

3.2.1 交易输入

                              c997 a5e5
6e10 4102 fa20 9c6a 852d d906 60a2 0b2d
9c35 2423 edce 2585 7fcd 3704

“hash”,32字节,交易哈希值:指向被花费的UTXO所在的交易的哈希指针。”0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9”

                              0000 0000

“n”: 0,输出索引: 被花费的UTXO的索引号,索引为0。

48

变长类型,1-9字节 解锁脚本大小:用字节表示的后面的解锁脚本长度。转换成十进制为72,后面72字节是解锁脚本的值。

  47 3044 0220 4e45 e169 32b8 af51 4961
a1d3 a1a2 5fdf 3f4f 7732 e9d6 24c6 c615
48ab 5fb8 cd41 0220 1815 22ec 8eca 07de
4860 a4ac dd12 909d 831c c56c bbac 4622
0822 21a8 768d 1d09 01

“scriptSig”,长度不定,解锁脚本:满足UTXO解锁脚本条件的脚本。解锁脚本开头的0x47表示现将后面的71个字节压入堆栈
“scriptSig”:”304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901”

                      ff ffff ff

4字节,序列号:目前未被使用的交易替换功能。设为0xFFFFFFFF

3.2.2 交易输出

                                02 

1-9字节,输出计数器:包含的交易输出数量,02表示有两个输出。

3.2.2.1 第一个输出
                                   00ca
9a3b 0000 0000

“value”,8字节,总量:用聪表示的比特币值。 0x0000003b9aca00,即1000000000。
“value”: “10.00000000”

               43

1-9字节,变长类型,锁定脚本大小,用字节表示的后面的锁定脚本长度。十进制为67,后面67字节是解锁脚本的值。

                 41 04ae 1a62 fe09 c5f5
1b13 905f 07f0 6b99 a2f7 159b 2225 f374
cd37 8d71 302f a284 14e7 aab3 7397 f554
a7df 5f14 2c21 c1b7 303b 8a06 26f1 bade
d5c7 2a70 4f7e 6cd8 4cac

“scriptPubKey”,不定长度,锁定脚本:一个定义了支付输出所需条件的脚本。开头的0x41表示将接下来的65个字节压入堆栈。而结尾的0xac表示OP_CHECKSIG。
“scriptPubKey”:”04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG”

3.2.2.2 第二个输出
                         0028 6bee 0000
0000

“value”,8字节,总量。0x00000000ee6b2800,即4000000000 。 “value”: “40.00000000”

     43

1-9字节,锁定脚本大小。十进制为67,含义同前。

       41 0411 db93 e1dc db8a 016b 4984
0f8c 53bc 1eb6 8a38 2e97 b148 2eca d7b1
48a6 909a 5cb2 e0ea ddfb 84cc f974 4464
f82e 160b fa9b 8b64 f9d4 c03f 999b 8643
f656 b412 a3ac 

“scriptPubKey”,不定长 ,锁定脚本:一个定义了支付输出所需条件的脚本,开头的0x41表示将接下来的65个字节压入堆栈。而结尾的0xac表示OP_CHECKSIG。
“scriptPubKey”:”0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG”

               0000 0000 

“lock_time”,4字节 ,锁定时间 一个区块号或UNIX时间戳 锁定时间为0,表示立即执行。”lock_time”: 0。

4. 数据长度

最后解释一下json中的各size值的来源。
(1)整个十六进制的数据长度是980,即490字节,对应json中的”size”: 490。
(2)区块头,header 是80字节:

0100000055bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000ff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d51b96a49ffff001d283e9e70

(3)交易数目值是02,占用1字节
(4)交易1, 长度268,134字节,对应json中的 “size”: 134

01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0102ffffffff0100f2052a01000000434104d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725bac00000000

(5)交易2 长度550,275字节 对应json中的”size”: 275

0100000001c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704000000004847304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901ffffffff0200ca9a3b00000000434104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac00286bee0000000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000

所以 490 = 80 + 1 + 134 + 275,正好对应。

5. 小结

以上我们完成了一个完整区块的拆解,是不是觉得理解更踏实了一些呢?是不是感觉这区块链也没有那么神秘?是不是觉得计算机的世界也蛮有趣的?不过对于json中的 “next_in”,”hash” ,”nid”,”mrkl_tree”,”mrkl_root”,” scriptPubKey” 是怎么来的, 怎么计算的? 我们以后再讲了。
对于本文,如果你有什么疑问,欢迎留言。
最后,奉上一枚彩蛋, 一张本区块的结构图,画了很长时间的。


本公众号是活动“每周一新”第11周的践行产物。主要分享成长感悟、旅游经历、通用技术、IT趣闻、编程知识等,欢迎关注。

猜你喜欢

转载自blog.csdn.net/victor0535/article/details/81844908