PostgreSQL 区块链条管理举例
標簽
PostgreSQL , blockchain , 鏈條管理 , 遞歸查詢
背景
區塊鏈的概念請參考
https://github.com/yeasy/blockchain_guide/blob/master/intro/what.md
截取部分如下
什么是區塊鏈
區塊鏈的基本原理理解起來并不難。基本概念包括:
-
交易(Transaction):一次操作,導致賬本狀態的一次改變,如添加一條記錄;
-
區塊(Block):記錄一段時間內發生的交易和狀態結果,是對當前賬本狀態的一次共識;
-
鏈(Chain):由一個個區塊按照發生順序串聯而成,是整個狀態變化的日志記錄。
-
如果把區塊鏈作為一個狀態機,則每次交易就是試圖改變一次狀態,而每次共識生成的區塊,就是參與者對于區塊中所有交易內容導致狀態改變的結果進行確認。
在實現上,首先假設存在一個分布式的數據記錄本(這方面的技術相對成熟),這個記錄本只允許添加、不允許刪除。其結構是一個線性的鏈表,由一個個“區塊”串聯組成,這也是其名字“區塊鏈”的來源。新的數據要加入,必須放到一個新的區塊中。而這個塊(以及塊里的交易)是否合法,可以通過一些手段快速檢驗出來。維護節點都可以提議一個新的區塊,然而必須經過一定的共識機制來對最終選擇的區塊達成一致。
具體以比特幣為例來看如何使用了區塊鏈技術?客戶端發起一項交易后,會廣播到網絡中并等待確認。網絡中的節點會將一些等待確認的交易記錄打包在一起(此外還要包括此前區塊的哈希值等信息),組成一個候選區塊。然后,試圖找到一個 nonce 串放到區塊里,使得候選區塊的 hash 結果滿足一定條件(比如小于某個值)。一旦算出來這個區塊在格式上就合法了,就可以進行全網廣播。大家拿到提案區塊,進行驗證,發現確實符合約定條件了,就承認這個區塊是一個合法的新區塊,被添加到鏈上。當然,在實現上還會有很多的細節。
比特幣的這種基于算力的共識機制被稱為 Proof of Work(PoW)。目前,要讓 hash 結果滿足一定條件并無已知的啟發式算法,只能進行暴力嘗試。嘗試的次數越多,算出來的概率越大。通過調節對 hash 結果的限制,比特幣網絡控制約 10 分鐘平均算出來一個合法區塊。算出來的節點將得到區塊中所有交易的管理費和協議固定發放的獎勵費(目前是 12.5 比特幣,每四年減半)。也即俗稱的挖礦。
很自然會有人問,能否進行惡意操作來破壞整個區塊鏈系統或者獲取非法利益。比如不承認別人的結果,拒絕別人的交易等。實際上,因為系統中存在大量的用戶,而且用戶默認都只承認他看到的最長的鏈。只要不超過一半(概率意義上越少肯定越難)的用戶協商,最終最長的鏈將很大概率上是合法的鏈,而且隨著時間增加,這個概率會越大。例如,經過 6 個塊后,即便有一半的節點聯合起來想顛覆被確認的結果,其概率將為 $$\frac{1}{2}^6 = 1.6\%$$,即低于 $$\frac{1}{60}$$ 的可能性。
分類
根據參與者的不同,可以分為公開(Public)鏈、聯盟(Consortium)鏈和私有(Private)鏈。
公開鏈,顧名思義,任何人都可以參與使用和維護,典型的如比特幣區塊鏈,信息是完全公開的。
如果引入許可機制,包括私有鏈和聯盟鏈兩種。
私有鏈,則是集中管理者進行限制,只能得到內部少數人可以使用,信息不公開。
聯盟鏈則介于兩者之間,由若干組織一起合作維護一條區塊鏈,該區塊鏈的使用必須是有權限的管理,相關信息會得到保護,典型如銀聯組織。
目前來看,公開鏈將會更多的吸引社區和媒體的眼球,但更多的商業價值應該在聯盟鏈和私有鏈上。
根據使用目的和場景的不同,又可以分為以數字貨幣為目的的貨幣鏈,以記錄產權為目的的產權鏈,以眾籌為目的的眾籌鏈等。
誤區
目前,對區塊鏈的認識還存在不少誤區。
首先,區塊鏈不是數據庫。雖然區塊鏈也可以用來存儲數據,但它要解決的問題是多方的互信問題。單純從存儲數據角度,它的效率可能不高,筆者也不推薦把大量的原始數據放到區塊鏈上。
其次,區塊鏈不是要顛覆現有技術。作為基于多項已有技術而出現的新事物,區塊鏈跟現有技術的關系是一脈相承的,在解決多方合作和可信處理上多走了一步,但并不意味著它將徹底顛覆已有的商業模式。很長一段時間里,區塊鏈的適用場景仍需摸索,跟已有系統必然是合作共存的關系。
區塊鏈條
區塊鏈由BLOCK組成,每個BLOCK有一個HASH VALUE,同時有一個指向上一個BLOCK的HASH value,從而形成了BLOCK的鏈條。
A blockchain – originally block chain – is a distributed database that maintains a continuously growing list of ordered records called blocks.
Each block contains a timestamp and a link to a previous block.[6] By design, blockchains are inherently resistant to modification of the data — once recorded, the data in a block cannot be altered retroactively.
Blockchains are "an open, distributed ledger that can record transactions between two parties efficiently and in a verifiable and permanent way. The ledger itself can also be programmed to trigger transactions automatically."
一個區塊鏈的圖例如下,主鏈為最長的鏈(黑色),綠色該區塊鏈的起始BLOCK,而紫色為被推翻的BLOCK,在主鏈的外圍。為什么會有紫色的塊呢?很顯然是被推翻的塊,比如超過半數的維護節點(維護區塊鏈副本的所有節點)推翻它。
Blockchain formation. The main chain (black) consists of the longest series of blocks from the genesis block (green) to the current block. Orphan blocks (purple) exist outside of the main chain.
鏈條數據的維護
區塊鏈中,有一條主鏈,即最長的鏈,不在主鏈上的塊,都是報廢的塊。
每個塊都有一個height值,表示它的前面有多少個數據塊。
如果在數據庫中為每個BLOCK存儲一條記錄,那么如何從一個區塊,找到它的上層區塊呢?
生成區塊鏈的測試數據
1. 測試表
create table blockchain( block_hash text primary key, -- block 哈希值 prev_block text, -- 前一個block的哈希值 height int -- 當前block的高度,表示前面有多少個BLOCK,起始BLOCK的高度為0 );2. 插入100萬測試數據,100萬個區塊。
do language plpgsql $$ declare v_id1 text; v_id2 text; begin v_id1 := ( md5(random()::text||clock_timestamp()::text) || md5(random()::text||clock_timestamp()::text) ); v_id2 := ( md5(random()::text||clock_timestamp()::text) || md5(random()::text||clock_timestamp()::text) ); for i in 1..1000000 loop if i <> 1 then insert into blockchain(block_hash, prev_block, height) values (v_id2, v_id1, i-1); else insert into blockchain(block_hash, prev_block, height) values (v_id2, '0', 0); end if; v_id1 := v_id2; v_id2 := ( md5(random()::text||clock_timestamp()::text) || md5(random()::text||clock_timestamp()::text) ); end loop; end; $$;測試數據如下
postgres=# select * from blockchain limit 10; block_hash | prev_block | height ------------------------------------------------------------------+------------------------------------------------------------------+-------- 487eb30b363cedb9abd171d17ac4b1bacea760824080a09beaed48e63f8f3bfe | 0 | 0 ad1585e0b348f6dcd630801b815a8ff541a2a57183408e4a7b85ba8dcc19768b | 487eb30b363cedb9abd171d17ac4b1bacea760824080a09beaed48e63f8f3bfe | 1 1868e5a410091d003ca3313f4e5cfda2a4941ff747cf77e26b2c6e94e3f04fde | ad1585e0b348f6dcd630801b815a8ff541a2a57183408e4a7b85ba8dcc19768b | 2 ad6e8994d1b54ce3a7ebf4d25c306ea587c0a7145237bd1e4d064da85581a371 | 1868e5a410091d003ca3313f4e5cfda2a4941ff747cf77e26b2c6e94e3f04fde | 3 f8257ed95ec8c42c3377e362ef34a02b0051b7ac92067ab51852bb86de88eff2 | ad6e8994d1b54ce3a7ebf4d25c306ea587c0a7145237bd1e4d064da85581a371 | 4 bcb5c2e001fff982b78e9f413a076359ab546a146bc2bbab585ab3c69e47d43c | f8257ed95ec8c42c3377e362ef34a02b0051b7ac92067ab51852bb86de88eff2 | 5 ecfd744a073aa655faa5c8bee32d2686fc63dbf334b34a2caf2c04b5c360fc24 | bcb5c2e001fff982b78e9f413a076359ab546a146bc2bbab585ab3c69e47d43c | 6 317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89 | ecfd744a073aa655faa5c8bee32d2686fc63dbf334b34a2caf2c04b5c360fc24 | 7 a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9 | 317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89 | 8 14967b84c7297e68611b4e294eed993f7c8e19894375e2fa55307b71cd7d3e80 | a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9 | 9 (10 rows)創建索引
create index idx_blockchain_1 on blockchain(prev_block);根據某個區塊找到它的前面的所有區塊
由于區塊鏈是鏈表結構,從一個塊找前面所有的塊,就類似順藤摸瓜,在PostgreSQL中,使用遞歸查詢即可達到這個目的。
WITH RECURSIVE tmp(block_hash, prev_block, height) as ( select * from blockchain where block_hash='a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9' UNION ALL SELECT t1.block_hash, t1.prev_block, t1.height FROM tmp JOIN blockchain t1 ON (t1.block_hash = tmp.prev_block) ) select * from tmp; block_hash | prev_block | height ------------------------------------------------------------------+------------------------------------------------------------------+-------- a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9 | 317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89 | 8 317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89 | ecfd744a073aa655faa5c8bee32d2686fc63dbf334b34a2caf2c04b5c360fc24 | 7 ecfd744a073aa655faa5c8bee32d2686fc63dbf334b34a2caf2c04b5c360fc24 | bcb5c2e001fff982b78e9f413a076359ab546a146bc2bbab585ab3c69e47d43c | 6 bcb5c2e001fff982b78e9f413a076359ab546a146bc2bbab585ab3c69e47d43c | f8257ed95ec8c42c3377e362ef34a02b0051b7ac92067ab51852bb86de88eff2 | 5 f8257ed95ec8c42c3377e362ef34a02b0051b7ac92067ab51852bb86de88eff2 | ad6e8994d1b54ce3a7ebf4d25c306ea587c0a7145237bd1e4d064da85581a371 | 4 ad6e8994d1b54ce3a7ebf4d25c306ea587c0a7145237bd1e4d064da85581a371 | 1868e5a410091d003ca3313f4e5cfda2a4941ff747cf77e26b2c6e94e3f04fde | 3 1868e5a410091d003ca3313f4e5cfda2a4941ff747cf77e26b2c6e94e3f04fde | ad1585e0b348f6dcd630801b815a8ff541a2a57183408e4a7b85ba8dcc19768b | 2 ad1585e0b348f6dcd630801b815a8ff541a2a57183408e4a7b85ba8dcc19768b | 487eb30b363cedb9abd171d17ac4b1bacea760824080a09beaed48e63f8f3bfe | 1 487eb30b363cedb9abd171d17ac4b1bacea760824080a09beaed48e63f8f3bfe | 0 | 0 (9 rows)根據某個區塊找到它的后面的所有區塊
WITH RECURSIVE tmp(block_hash, prev_block, height) as ( select * from blockchain where block_hash='a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9' UNION ALL SELECT t1.block_hash, t1.prev_block, t1.height FROM tmp JOIN blockchain t1 ON (t1.prev_block = tmp.block_hash) ) select * from tmp limit 10; block_hash | prev_block | height ------------------------------------------------------------------+------------------------------------------------------------------+-------- a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9 | 317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89 | 8 14967b84c7297e68611b4e294eed993f7c8e19894375e2fa55307b71cd7d3e80 | a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9 | 9 efd41c98ea6e6ba557b35ce982b87b3c62245534f625c508608d9c58db3555ee | 14967b84c7297e68611b4e294eed993f7c8e19894375e2fa55307b71cd7d3e80 | 10 96c3d5160ee43783bebf70a86361dd6cfb61245d4f3acb177aed14b74c1b177a | efd41c98ea6e6ba557b35ce982b87b3c62245534f625c508608d9c58db3555ee | 11 2dd3ac50b6ef67dcbca5e54bd8aca7bc5327e71a9f29773e27264a0aa0507a20 | 96c3d5160ee43783bebf70a86361dd6cfb61245d4f3acb177aed14b74c1b177a | 12 57ffc9ae3d87c3ec22080e0aed0722605d6eb640bff51c38620196d253c73017 | 2dd3ac50b6ef67dcbca5e54bd8aca7bc5327e71a9f29773e27264a0aa0507a20 | 13 0ae675c06aa5d04a6f8e625babdafce75a97bb7e0ba0d8aa639e4a3c3dfc50cc | 57ffc9ae3d87c3ec22080e0aed0722605d6eb640bff51c38620196d253c73017 | 14 027de6b68ef168d34384ea8f9bb8fc9abdf9baa80616cf7d1f784737c6b0f964 | 0ae675c06aa5d04a6f8e625babdafce75a97bb7e0ba0d8aa639e4a3c3dfc50cc | 15 0c8d6fc0d1c09578f0b95ae7cfbcb0ab8553ee108983f194048a886915dbe4d5 | 027de6b68ef168d34384ea8f9bb8fc9abdf9baa80616cf7d1f784737c6b0f964 | 16 f5c61da7c03769372d0ea404de6e7a4cc19707c43c3818df6176d30c01ddd68b | 0c8d6fc0d1c09578f0b95ae7cfbcb0ab8553ee108983f194048a886915dbe4d5 | 17 (10 rows)遞歸查詢的啟動子句除了SELECT,也支持VALUES子句。
WITH RECURSIVE tmp(block_hash, prev_block, height) as ( select * from (values ('a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9', '317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89', 8)) as t(block_hash,prev_block,height)UNION ALL SELECT t1.block_hash, t1.prev_block, t1.height FROM tmp JOIN blockchain t1 ON (t1.prev_block = tmp.block_hash) ) select * from tmp limit 10;效率是一樣的。
postgres=# explain (analyze,verbose,timing,costs,buffers) WITH RECURSIVE tmp(block_hash, prev_block, height) as ( select * from blockchain where block_hash='a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9' UNION ALL SELECT t1.block_hash, t1.prev_block, t1.height FROM tmp JOIN blockchain t1 ON (t1.prev_block = tmp.block_hash) ) select * from tmp ; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------CTE Scan on tmp (cost=324.94..326.96 rows=101 width=68) (actual time=0.025..5817.479 rows=999992 loops=1)Output: tmp.block_hash, tmp.prev_block, tmp.heightBuffers: shared hit=5029960, temp written=17822CTE tmp-> Recursive Union (cost=0.55..324.94 rows=101 width=134) (actual time=0.024..5189.381 rows=999992 loops=1)Buffers: shared hit=5029960-> Index Scan using blockchain_pkey on public.blockchain (cost=0.55..3.17 rows=1 width=134) (actual time=0.022..0.022 rows=1 loops=1)Output: blockchain.block_hash, blockchain.prev_block, blockchain.heightIndex Cond: (blockchain.block_hash = 'a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9'::text)Buffers: shared hit=5-> Nested Loop (cost=0.55..31.97 rows=10 width=134) (actual time=0.004..0.005 rows=1 loops=999992)Output: t1.block_hash, t1.prev_block, t1.heightBuffers: shared hit=5029955-> WorkTable Scan on tmp tmp_1 (cost=0.00..0.20 rows=10 width=32) (actual time=0.000..0.000 rows=1 loops=999992)Output: tmp_1.block_hash, tmp_1.prev_block, tmp_1.height-> Index Scan using idx_blockchain_1 on public.blockchain t1 (cost=0.55..3.17 rows=1 width=134) (actual time=0.004..0.004 rows=1 loops=999992)Output: t1.block_hash, t1.prev_block, t1.heightIndex Cond: (t1.prev_block = tmp_1.block_hash)Buffers: shared hit=5029955Planning time: 0.189 msExecution time: 5922.225 ms (21 rows)postgres=# explain (analyze,verbose,timing,costs,buffers) WITH RECURSIVE tmp(block_hash, prev_block, height) as ( select * from (values ('a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9', '317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89', 8)) as t(block_hash,prev_block,height)UNION ALL SELECT t1.block_hash, t1.prev_block, t1.height FROM tmp JOIN blockchain t1 ON (t1.prev_block = tmp.block_hash) ) select * from tmp ; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------CTE Scan on tmp (cost=321.78..323.80 rows=101 width=68) (actual time=0.006..5815.594 rows=999992 loops=1)Output: tmp.block_hash, tmp.prev_block, tmp.heightBuffers: shared hit=5029955, temp written=17822CTE tmp-> Recursive Union (cost=0.00..321.78 rows=101 width=134) (actual time=0.003..5186.289 rows=999992 loops=1)Buffers: shared hit=5029955-> Result (cost=0.00..0.01 rows=1 width=68) (actual time=0.001..0.001 rows=1 loops=1)Output: 'a1896dffd46a9c5695c2340a42ac8957d3141e67d047765c4cf3ac270e8dd6e9'::text, '317e0727c91758af37f0d21d1adda8d80f3f0f86b914aac4275b4c7e358fbe89'::text, 8-> Nested Loop (cost=0.55..31.97 rows=10 width=134) (actual time=0.004..0.005 rows=1 loops=999992)Output: t1.block_hash, t1.prev_block, t1.heightBuffers: shared hit=5029955-> WorkTable Scan on tmp tmp_1 (cost=0.00..0.20 rows=10 width=32) (actual time=0.000..0.000 rows=1 loops=999992)Output: tmp_1.block_hash, tmp_1.prev_block, tmp_1.height-> Index Scan using idx_blockchain_1 on public.blockchain t1 (cost=0.55..3.17 rows=1 width=134) (actual time=0.004..0.004 rows=1 loops=999992)Output: t1.block_hash, t1.prev_block, t1.heightIndex Cond: (t1.prev_block = tmp_1.block_hash)Buffers: shared hit=5029955Planning time: 0.196 msExecution time: 5923.727 ms (19 rows)更新BLOCK高度
遞歸調用中同樣可以使用DML語句,比如
delete from ... returning *; update xxx set xxx from xxx where xxx returning *;遞歸查詢可以由多個SUB組成。
至于BLOCK的內容,你可以存儲在PostgreSQL的bytea或者large object中。
圖數據應用
遞歸查詢同樣可以用于圖數據場景,比如人脈分析。
《金融風控、公安刑偵、社會關系、人脈分析等需求分析與數據庫實現 - PostgreSQL圖數據庫場景應用》
參考
https://github.com/yeasy/blockchain_guide/blob/master/intro/what.md
https://en.wikipedia.org/wiki/Blockchain
【區塊鏈之技術實戰】
《金融風控、公安刑偵、社會關系、人脈分析等需求分析與數據庫實現 - PostgreSQL圖數據庫場景應用》
《PostgreSQL Oracle 兼容性之 - connect by》
《PostgreSQL Oracle 兼容性之 - WITH 遞歸 ( connect by )》
總結
以上是生活随笔為你收集整理的PostgreSQL 区块链条管理举例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PostgreSQL高可用(二)使用pg
- 下一篇: postgreSQL pgpool-I