EOS跨鏈通訊原理與設計
By ·
本文將介紹IBC技術原理,並對BOSCORE團隊開發的IBC合約及外掛進行深入介紹。為了實現兩條EOSIO體系區塊鏈間token的跨鏈轉移,首先需要解決兩個難題,1.輕客戶端如何實現,2.如何確保跨鏈交易的完整性和可靠性, 如何防止雙花和重放攻擊。下文均以EOS主網和BOS主網為例進行說明,但本文件適用於任何兩條EOSIO體系的區塊鏈。一、術語BOSIBC:是指BOSCORE技術團隊開發的IBC合約及外掛。二、關鍵概念及資料結構· Simple Payment Verification (SPV)簡單支付驗證技術最早在中本聰的比特幣白皮書中提出Bitcoin,用於驗證一筆交易存在於區塊鏈中。 SPV client儲存著連續的區塊頭,但沒有區塊體,因此只需佔用很小的儲存空間。當獲得一筆交易和這筆交易的Merkle path後,可以驗證這筆交易是否 存在於區塊鏈上。· Lightwight client (lwc)輕客戶端,即SPV client,即由區塊頭組成的一條輕量的鏈。· Merkle path 默克爾路徑。為驗證一筆交易是否存在於某個區塊中,只需要提供交易原始資料和交易在所在區塊的merkle path,而無需提供整個區塊體,透過計算merkle path並和區塊頭中 記錄的merkle root對比,若相等,則說明此交易存在於此區塊中。merkle path也被稱為merkle branch。· Block Producer ScheduleBP Schedule是基於DPOS機制的EOSIO體系公鏈用於決定生產區塊權利的技術,新的BP Schedule是由上一批BP Schedule包含的Block Producers認證透過後生效 以此確保嚴格的BP權利交接,在輕客戶端中跟隨對應主網的BP Schedule是IBC系統邏輯的一項核心技術。· forkdbEOSIO節點在執行時,有兩個底層的db用於儲存區塊資訊,一個是blog即block log,用於儲存不可逆區塊,一個是forkdb,用於儲存可逆區塊。 forkdb儲存的是當前區塊鏈最頂端的一部分割槽塊資訊,一個區塊首先要被forkdb接受才能最終進入不可逆區塊,IBC系統在合約實現的輕客戶端主要參考了forkdb的邏輯。三、輕客戶端為了解決跨鏈問題,首先要解決的是輕客戶端如何實現的問題。1. 輕客戶端執行在哪裡合理,合約中還是合約外,例如外掛中;2. 若執行在合約中,是實時同步對方鏈的全部區塊頭資料,還是根據需要同步一部分割槽塊資料來驗證交易,因為如果同步全部區塊資料會消耗兩條鏈大量cpu資源。3. 若執行在合約中,如何確保輕客戶端的可信性,如何防止惡意攻擊,如何做到完全去中心化,不依賴對任何中繼節點的信任;3.1 輕客戶端是否執行在合約中比特幣的輕客戶端最早是執行在單個節點上(如個人電腦或去中心化比特幣手機錢包),用於驗證交易是否存,並檢視交易所在區塊深度。IBC和去中心化錢包對輕客戶端的需求是不同的,去中心化錢包一般執行在個人的手機App上,為使用者個人提供交易驗證服務,而IBC系統需要的輕客戶端 要對所有人公開可查可信,從這個角度看,一個能夠獲得大眾信任的輕客戶端只能執行在合約中,因為只有合約的資料是全域性一致不可篡改的, 執行在合約外則無法實現一個可信的輕客戶端,因此BOSIBC將輕客戶端執行在合約中。3.2 是否同步全量區塊頭資訊在比特幣和以太坊的輕客戶端中,會尋找一個起點和後續的一些驗證點,輕客戶端會同步起點後的全量的區塊頭資訊。比特幣每年產生的所有區塊的區塊頭體積僅有4Mb, 按現在移動裝置的儲存能力,是完全可以容納的,並且同步這些區塊頭也不會消耗移動裝置大量計算資源。然而EOSIO的情況卻很不同, EOSIO每0.5秒一個區塊,實際測試可知,每新增一個區塊頭到合約中需要消耗0.5毫秒cpu,每刪除一個區塊頭需要0.2毫秒,因此每處理一個區塊頭需要0.7ms的cpu。 假設要同步對方EOSIO公鏈的全量區塊頭資訊,按現在每個區塊總的cpu時間200ms計算,也就是需要一條鏈全部計算的0.7ms / 200ms = 0.35%才能實時全量同步 另一條鏈的所有區塊頭, 按實際全網抵押總量為4億token計算,如果再cpu繁忙時保證IBC系統正常工作,需要為push區塊資訊的賬戶抵押 4億 * 0.35% = 140萬token,這是個很大的數目。又因為EOSIO倡導多側鏈的生態,假設未來有多條側鏈和EOSIO主網實現跨鏈,並且側鏈與側鏈間 也實現了一對多的跨鏈,按1對10計算,每條鏈需要維護10個輕客戶端,則只為了維護這些輕客戶端就需要消耗3.5%的單條鏈全網cpu,這個比例實在是太高了, 因此需要尋找更合理的方案。設計跨鏈通訊的過程是一種尋找可信證據的過程,有沒有一種方案即不需要同步全量區塊資訊,又可以保證輕客戶端的可信性,EOSIO底層已經為實現這一目的有所準備。 我們先假設,如果BP schedule自始至終不會變化,那麼任何時候,當ibc.chain合約中獲得一連串的簽名驗證透過的區塊頭,比如第n ~ n+336個, 並且有2/3以上的活躍bp在出塊,就可以確信第n個區塊已經是不可逆的,可以用於驗證跨鏈交易。 然後,就是需要考慮有BP schedule更換的情況了,當出現BP schedule更換時,不在接受交易驗證,直到更換完成,處理BP更換時相對複雜的過程,後續會更詳細介紹, 因此使用這個方案就可以大大降低需要同步的區塊頭數量,只有在BP列表更換或有跨鏈交易時才需要同步區塊。為了實現這一目的,在ibc.chain中引入了概念section,一個section記錄的是一段連續的區塊頭資訊,section結構不儲存具體的區塊頭資訊,而是記錄這一段 區塊頭的第一個區塊編號(first)和最後一個區塊編號(last),具體區塊頭資訊在chaindb中儲存,每個section都有一個valid值,在沒有bp schedule更替的時候, 只要有2/3的活躍BP在出塊,並且last - first > lib_depth則認為first ~ last - lib_depth的區塊是不可逆的,可以用於驗證跨鏈交易, 當遇到BP schedule 更替,section的valid變為false,不再接受交易驗證,直到schedule更替完成,valid重新變為true之後,繼續驗證跨鏈交易。3.3 如何確保輕客戶端的可信性3.3.1 forkdb1.一個新的區塊是如何追加到forkdb的一個執行的nodeos節點維護著兩個底層資料結構blog 和forkdb, blog用於儲存不可逆的區塊資訊,其儲存的資料是序列化的signed_block,forkdb用於儲存可逆區塊資訊,其儲存的資料是block_state, block_state比signed_block包含更多區塊相關資訊。一個區塊首先要被追加到forkdb,才可能最終變為不可逆區塊而移除forkdb進入blog, 一個區塊的block_state資訊是如何獲得的呢,並非生產區塊的BP將所生產區塊的block_state透過p2p網路傳遞給其他bp和全節點,p2p網路 只傳遞signed_block, 當一個節點透過p2p網路接收到一個signed_block後,它會使用此signed_block構建block_state並驗證簽名 相關函式, 其中需要說明的幾個關鍵點是,1.blockroot_merkle,2.get_scheduled_producer(),3.verify_signee()。blockroot_merkleEOSIO在block_state::block_header_state結構中維護了一個blockroot_merkle的incremental_merkle資料,incremental_merkle實際是 一個完整的merkle樹的活躍節點,使用incremental_merkle只需維護極少的活躍節點資訊即可不斷累加並獲得merkle_root,是block_state 中使用的一個關鍵技術。BOSIBC的ibc.chain合約同樣使用了此資料結構。blockroot_merkle從創世區塊id不斷累加,但是signed_block和blog中並沒有這個資料,只有forkdb的每個block_state中記錄著當前block的 blockroot_merkle,並且此值被用於計算區塊簽名。get_scheduled_producer()此函式根據一個區塊的header.timestamp計算出應該生成此區塊的producer_key(見block_header_state::next()),為後續驗證簽名做準備。驗證簽名相關函式如下digest_type block_header_state::sig_digest()const { auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) ); return digest_type::hash( std::make_pair(header_bmroot, pending_schedule_hash) ); } public_key_type block_header_state::signee()const { return fc::crypto::public_key( header.producer_signature, sig_digest(), true ); } void block_header_state::verify_signee( const public_key_type& signee )const { EOS_ASSERT( block_signing_key == signee, wrong_signing_key, "block not signed by expected key", ("block_signing_key", block_signing_key)( "signee", signee ) ); }驗證簽名的第一步是獲得區塊摘要,即sig_digest(),此函式中用到了header.digest(),blockroot_merkle.get_root()和pending_schedule_hash; 第二步是獲得簽名公鑰,即signee(),透過區塊的producer_signature和sig_digest()計算BP公鑰; 第三步是驗證公鑰是否正確,即verify_signee(),此函式在block_header_state::next()被呼叫;驗證透過後,一個區塊被追加的forkdb中的分支中。所以在forkdb中每新增一個區塊都經過了非常嚴格全面的效驗,核心是包括blockroot_merkle,get_scheduled_producer()和verify_signee(), 在ibc.chain合約完全繼承了forkdb嚴格的效驗。2.forkdb如何處理分叉當新增一個新的區塊導致fordb的head.id和controller_impl的head.id不同時,則重新選擇分支。 原始碼參考eosio::chain::controller_impl的push_block()和maybe_switch_forks();3.LIB如何確定EOSIO目前使用的共識方式是dpos,當構造一個區塊的block_header_state時會設定required_confs,此值為當前活躍BP數量的2/3+1, 在21個BP的情況下,required_confs為15。每個區塊頭中都有header.confirmed,用於對前面的區塊進行確認,每個區塊得到一個確認, 其required_confs會減1,當某個區塊的required_confs減少到零時,此區塊會被最新區塊(即forkdb的head)提名為dpos_proposed_irreversible_blocknum, 當某個區塊獲得了2/3的BP提名後,其變為不可逆區塊,即進入LIB。由於確認的資訊是在header中傳遞的,因此一個區塊從產生到進入LIB總共需要 兩個2/3輪,也就是 12 *( 14 * 2 ) = 336才會進入LIB,考錄到BP每次都是連續出12個區塊,只有第一個區塊的header.confirmed為非零, 因此當一個BP開始出塊時,只有第一個區塊會提升LIB,因此實際head和LIB的差距在325至336之間,但是在有BP丟塊的情況下,head和LIB的差距可能出現小於 325至336。4.BP列表是如何更換的在pow的區塊鏈中,比如比特幣和以太坊,是選擇算力累加最大的分叉作為主鏈,一個區塊只有包含一定的算力才有可能被認可,最終變為不可逆。 而在以dpos為共識演算法的EOS中,區塊被認可的標記是BP簽名,因此BP列表在EOSIO中具有至關重要的地位。IBC的輕客戶端同樣需要維護BP列表和BP列表的更換, 因此需要透徹分析BP列表的更換邏輯。第一步,在系統合約eosio.system的onblock()函式中,系統會每分鐘一次嘗試更新bp列表update_elected_producers( timestamp ),此函式最終呼叫 wasm介面set_proposed_producers(),透過一系列檢查後,會將新的schedule和當前區塊編號存到global_property_object物件中。第二部,當此區塊變為不可逆之後,會在當前的pending區塊中設定新的名單header.new_producers,並重置global_property_object物件,當前 pending區塊編號會被記錄到pending_schedule_lib_num,此時在nodeos日誌中可以看到新的名單;具體邏輯參考controller::start_block() // Promote proposed schedule to pending schedule.。 也就是說新的名單從proposed schedule變為pending schedule大約需要經歷325至336個區塊。從這裡開始, 後面區塊block_header_state的pending_schedule.version會比active_schedule.version大1.第三步,當pending_schedule_lib_num變為不可逆後,active_schedule會被pending schedule替換,整個的BP更換過程完成。 從pending schedule出現到其變為active_schedule同樣需要經歷約325至336區塊。IBC系統的輕客戶端同樣需要繼承forkdb的這些邏輯,才能實現可信的輕客戶端。然而,輕客戶端是在合約中實現,需要充分考慮合約的特性和限制, 因此在實現細節上,需要做諸多調整。3.3.2 eosio::table("chaindb"), ibc.chain合約中的forkdb1.輕客戶端(lwc)的LIB如何確定有兩種方案,一種是完全按forkdb的邏輯,維護一整套confirm_count和confirmations等block_header_state相關資訊,每新增一個區塊 計算一次LIB,這樣做的優點是可以準確獲得實時LIB值,然而對於輕客戶端來說,其關心的是區塊已經不可逆,而並非實時的精確LIB值。有沒有更簡單的方案呢, 根據上述的邏輯,如果有活躍的2/3以上的BP在出塊,並且某個區塊的深度超過336,則此區塊一定是不可逆的,可以用於驗證跨鏈交易;使用這種方案, 可以簡化合約中forkdb的複雜度。ibc.chain合約中表global的lib_depth是一個深度值,當在一段連續的區塊頭中,某個區塊頭的深度超過此值時,則認為不可逆,可以用於驗證交易了。 此值應該設定多少合適呢,可以設定成336,當輕客戶端檢查到有2/3以上的BP在出塊,則認為深度超過了336的區塊是不可逆的。然而在合約中新增和刪除區塊頭 是非常耗cpu的,實際測試可知,每新增一個區塊頭需要消耗0.5毫秒cpu,每刪除一個區塊頭需要0.2毫秒,因此每個區塊頭需要0.7ms的cpu。 是否會出現主網巨大波動,導致沒有進lib的區塊全部回滾呢,這是有可能的,實際也發生過,因此要必須確保一筆跨鏈交易所在區塊進入lib,再開始處理。 外掛在此時可以起到一定的作用,在BOSCORE技術團隊研發的ibc_plugin中,設定了引數,只處理一定深度內的跨鏈交易,這樣,外掛中的深度和合約中的深度相加 超過336即可。這只是BOSIBC初期的做法,目的是避免消耗大量cpu,後續可能會考慮將合約中的深度設定為336,從而完全不依賴中繼的深度,然而無論是外掛 還是合約中增加深度值,都會直接延長跨鏈交易到賬時間,從而影響使用者體驗,因此需要根據實際情況確定一個合理的值,從而即保證足夠安全又不失良好的使用者體驗。2.表chaindb表chaindb是ibc.chain合約中的forkdb,ibc.chain合約中沒有blog結構,和eosio中forkdb使用的block_header_state不同,chaindb進行了大量精簡,只保留了 block_num、block_id、header、active_schedule、pending_schedule、blockroot_merkle、block_signing_key7個資料,又因為 bp schedule需要佔用大量空間,因此在另外的表prodsches中儲存實際schedule資訊,在chaindb中用id引用,以節省記憶體佔用和wasm cpu消耗。3.輕客戶端的創世區塊輕客戶端需要一個可信的起點,此起點是輕客戶端的創世區塊頭,後面所有區塊頭的驗證都是基於創世區塊頭的資訊。 創世區塊頭需要block_header_state中的blockroot_merkle,active_schedule和header資訊,才能驗證區塊簽名。 原始碼為chain::chaininit(),最重要一個限制是,創世區塊頭的pending_schedule必須和active_schedule相同,因為其不同意味著 此區塊是bp列表更替過程中的一個區塊,如果使用更替過程中的區塊,需要同步後續的區塊,直到active_schedule被pending_schedule替換, 增加了複雜度,因此這樣的區塊不適合作為創世區塊。4.輕客戶端是如何新增新區塊的輕客戶端新增header的方式和和eosio的forkdb非常類似。原始碼見ibc.chain的chain::pushheader()。 第一步,透過區塊編號驗證是否能夠連線到最新的section 第二部,是否需要處理分叉,刪除舊資料。在ibc.chainz中不會同時儲存多個分支,而是以後者替代前者的方式實現對分叉的處理。 第三步,透過區塊id驗證是否能夠連線到最新的section 第四部,構造block_header_state,並驗證BP簽名其中最核心的是構造block_header_state,在這個過程中處理的BP schedule的更換,確保有2/3以上活躍BP(見section_type::add())。5. Section Section是chaindb的核心概念和創新,意思是一段連續的區塊頭,Section的管理也是最ibc.chain的核心的邏輯。 使用section的目的是降低cpu消耗,只有在BP schedule有變化或有跨鏈交易時才需要同步一部分割槽塊頭。 任何section的起始區塊不能是 bp schedule 更換過程中的區塊,也就是說,任何一個section的起始區塊的pending_schedule.version必須等於 active_schedule.version,並且一個section的起始區塊頭的active_schedule必須和前一個section最後區塊的active_schedule相同, 這樣就保證了在任意兩個section之間一定不存在BP schedule的更換,每一此BP schedule更換的完整過程必須在某個section中完成,從而確保 section資料的可信性。四、跨鏈交易1. 跨鏈交易三部曲一筆跨鏈交易分為三個過程,下面以將EOS從EOS主網跨鏈轉賬到BOS主網為例說明,首先使用者在EOS主網發起一筆跨鏈交易orig_trx,在EOS側ibc.token合約的 origtrx表中會記錄此交易資訊,當這筆交易所在區塊進入lib後,EOS側IBC中繼(relay_eos)將此交易和交易相關資訊(區塊資訊及Merkle路徑) 傳遞到BOS側中繼(relay_bos);relay_bos構造cash交易並呼叫BOS側ibc.token合約的cash介面,如果呼叫成功, cash函式中會給目標使用者發行對應的token;等cash交易所在的區塊進入lib後,relay_bos會將cash_trx和此交易相關資訊(區塊資訊及Merkle路徑) 傳遞到relay_eos,relay_eos構造cashconfirm交易並呼叫EOS側ibc.token合約的cashconfirm介面,cashconfirm會刪除EOS側ibc.token合約 中對orig_trx的記錄,至此一筆完整的跨鏈交易完成。2. 跨鏈失敗的交易跨鏈交易是可能失敗的,比如指定的賬戶在對方鏈上不存在,或者由於網路環境惡劣,導致呼叫cash介面失敗,未能成功跨鏈的交易會被回滾,即原路退還使用者的資產, 然而現在的IBC系統是交易驅動的,失敗的IBC交易需要等到有成功的IBC交易完成後才會被回滾。(注:後續版本升級會讓失敗的交易儘快回滾)3. 如何防止replay攻擊,即雙花攻擊防止雙花攻擊分為兩個階段:1,一筆成功的跨鏈交易只能執行一次cash,否則會造成重複cash。2,對於每一個cash交易,必須將其相關資訊傳回原鏈執行cashconfirm,以消除合約中記錄的原始交易資訊,否則會出現即在目的鏈上給使用者發行了token, 又將原鏈的token退還給了使用者。cash函式是ibc.token的核心邏輯,ibc.token合約中記錄著最近執行cash的原始交易id,即orig_trx_id,並且新的cash的orig_trx的區塊編號必須 大於或等於所有orig_trx所在的區塊編號,也就是說必須按原始交易在原鏈按區塊的順序進行cash,(執行cash時,原鏈某個區塊內的跨鏈交易順序是無關緊要的) ,再結合trx_id檢查,可以確保一筆跨鏈交易只能執行一次cash。同樣,cashconfirm介面會檢查cash交易的編號seq_num,此編號必須逐一遞增,以確保所有在目的鏈上的cash交易都會刪除在原鏈上的原始交易記錄, 從而確保不會出現雙花的情況。五、外掛外掛的作用分為兩部分:1,輕客戶端同步;2,跨鏈交易傳遞。核心邏輯請參考ibc_plugin_impl::ibc_core_checker()ibc_plugin主要參考了net_plugin的框架。六、問答問:IBC合約的多個action中用到了relay的許可權,那麼,本IBC系統是否依賴對中繼的信任。答:目前出於安全以及快速功能迭代的考量,特意新增了中繼許可權,隨著功能逐漸完善 BOS IBC 方案會支援多中繼機制,以避免單點風險。驗證relay許可權處於兩種考慮:1,ibc.chain合約使用了section的機制,現在的邏輯不允許為舊的section新增區塊,也不允許在一個section前面 新增區塊頭,如果任何人都可以呼叫pushsection介面,假設應該push的區塊範圍是1000-1300,故意搗亂的人可能會搶先push 1100-1300, 從而導致1000-1100無法被push,進而導致一些跨鏈交易無法成功,(注,此問題會在後續版本中考慮最佳化);2,考慮到IBC系統承載著 大量使用者資產,並且本系統還未經過長期市場考驗,因此增加了relay許可權,以降低安全風險。七、升級計劃1. 相容pbft2. 以更優雅的方式支援多條側鏈&EOS主鏈互相跨鏈3. 支援token以外其他型別資料的跨鏈更多數字貨幣資訊:www.qukuaiwang.com.cn/news#EOS#跨鏈通訊#跨鏈交易
免責聲明:
- 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
- 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
- 鏈報僅提供相關項目信息,不構成任何投資建議。
推荐阅读
;