基於Substrate實現比特幣UTXO支付模型

買賣虛擬貨幣
前段時間,Gavin Wood要求我研究基於Substrate實施UTXO鏈的可能性,Substrate是目前最有前景的區塊鏈技術底層架構,而且目前Polkadot是基於substrate進行開發的。我們想知道Substrate的靈活性,而UTXO鏈似乎是進行測試的一個不錯的選擇,因為它與我們過去在實施Substrate時所考慮的完全不同。如果可行,則表明Substrate確實非常靈活且通用。我們可以更有信心,把Substrate應該到不同領域的區塊鏈專案中。與以太坊類似,Substrate保留一定數量的可用資金。從某種意義上講,它類似於普通的銀行系統,其中帳戶餘額用數字表示,並儲存在資料庫或計算機記憶體中的某個位置。從歷史上看,第一個成功的加密貨幣是比特幣,它使用完全不同的方法。在比特幣中,本身沒有賬戶,餘額也不是作為一個數字儲存的。取而代之的是,可用資金是根據一組所謂的未用交易輸出來定義的,簡稱為UTXO,這是一個非常簡單的主意。簡而言之是UTXO簡而言之,UTXO非常類似於現金,或者更確切地說,是旅行支票。
當你用現金支付某人時,你通常會想到要支付的總價值,但是你用一組獨特的、不可分割的單位(代幣或鈔票)來表示這個價值。例如如果Alice希望付給Bob$250美元,她可以給Bob2張價值$100美元的鈔票和1張價值50美元的鈔票,或五張面值$50的鈔票,或總計為所需值的任何其他組合。每一張鈔票都是獨一無二的。儘管有數百萬張鈔票具有相同的價值,但是每張鈔票在物理上都是唯一的,並在其表面印有序列號。通常情況下,我們不太注意它,只是在支付東西的時候,把兩張100美元的鈔票視為相等,但這個數字對於銀行控制資金流動和真偽檢查是必不可少的。因此每張鈔票代表著具有預定和固定價值的獨特且不可分割的資產,這些資產只能整體使用,即您不能將100美元的鈔票撕成兩張50美元的鈔票。當然你可以要求某人找零,將價值分成較小的單位,但是您仍然需要花100美元的原始鈔票。同樣,購買咖啡時,您會花掉10美元的鈔票,作為回報,您會得到咖啡和一些零錢。UTXO的工作方式與此類似。要使用比特幣付款,您的錢包中應該已經有一些未使用的資產。與法定貨幣一樣,您可以結合使用多個UTXO以獲得更大的價值。與現金不同,每個UTXO都有自己的所有者。從這個意義上說,它類似於旅行支票,因為只有支票所有人才可以使用它。這是透過所有者簽名增加單位來完成的。不同之處在於,旅行支票由所有者的手簽名,而UTXO使用非對稱加密,並且包含收件人而非發件人的公鑰。而是鈔票由政府印刷,UTXO由發起人建立。目標
在我們的研究中,我們將嘗試建立一個區塊鏈模型,使用與比特幣相同的原理將資金從一個所有者轉移到另一個所有者。當閱讀文章時,請記住我們的主要目標是評估Substrate的靈活性,而不是比特幣移植時使用埠的詳細解釋。在某些情況下,其實現幾乎與Parity比特幣的實現相同,而在其他情況下則不是。例如當前的實現不支援挖掘和coinbase事務;它只是重新分配在genesis塊中初始化的“預先定義”UTXO集的值。另外,請注意,所提供的實現還不能完全投入生產。它尚未經過正式驗證,並且可能存在一些安全性或穩定性問題,因此,我不建議您在沒有適當研究的情況下,將其用於任何關鍵基礎架構。但是如果有人將這個原型製作成可行的解決方案,我會非常高興。話雖如此,讓我們繼續進行程式碼。首先讓我們談談Substrate如何允許您對其進行自定義。作為應用程式設計師,您應該提供一個runtime的執行邏輯,這些邏輯告訴Substrate如何處理鏈以及應採用的業務邏輯。所有這些都圍繞著狀態轉換函式(簡稱STF)的概念。但現在我們只需說,每個區塊鏈都可以表示為一個函式,接受當前狀態和一個掛起的事務,然後生成另一個狀態,反映在應用事務後所做的更改。假設Alice和Bob都有10個代幣,然後Alice向Bob傳送了5個代幣。應用此交易後,我們預計Alice現在將有5個代幣,而Bob將有15個代幣。如果Bob隨後嘗試向Claire支付20個代幣,則該交易必須視為無效,因為根據最新的鏈條狀態,Bob只有15個代幣。
這正是runtime的意圖-它定義了所有實體及其關係,驗證了傳入的事務並相應地更改了狀態。讓我們從指定將用於定義UTXO鏈的業務邏輯的資料型別開始。首先是Transaction 型別。它表示要排程的單個UTXO事務:/// Single transaction to be dispatched#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash)]pub struct Transaction {
/// UTXOs to be used as inputs for current transactionpub inputs: Vec<TransactionInput>,/// UTXOs to be created as a result of current transaction dispatchpub outputs: Vec<TransactionOutput>,}這裡沒有什麼特別的,只是一個簡單的定義,即Transaction只是一堆輸入和輸出。如果您好奇,可以將其與Parity Bitcoin的版本進行比較,以瞭解相似之處。上面所有#[...]怪異都稱為屬性,它告訴Rust編譯器為我們實現各種操作,例如比較運算子,雜湊函式和序列化例程。您現在可以放心地忽略它們。
我留下了所有註釋和屬性,以表明即使將它們包括在內,程式碼仍會保持緊湊。我認為,即使與在成千上萬行中做“同一件事”的Parity Bitcoin相比,這也是Substrate的可觀成就。就像在用JavaScript為網路編寫程式碼時一樣,您並沒有考慮過瀏覽器引擎或任何底層作業系統(包括作業系統)的複雜性。相反,您只是以高階形式制定業務邏輯,然後讓系統完成其餘工作。好的,但是TransactionInput呢?/// Single transaction input that refers to one UTXO#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash)]pub struct TransactionInput {
/// Reference to an UTXO to be spentpub parent_output: H256,/// Proof that transaction owner is authorized to spend referred UTXOpub signature: Signature,}TransactionInput彙總花費一個UTXO所需的所有資料。首先我們需要一種方法來引用一些現有的UTXO。最簡單的方法是使用其雜湊作為識別符號。這是分散式系統世界中的一種普遍做法,並且只要雜湊衝突的可能性可以忽略不計,它就可以很好地工作。為此我們使用256位Blake2。parent_output欄位包含此類雜湊。
如前所述,要使用UTXO,所有者必須使用與儲存在該特定UTXO中的公鑰匹配的秘密金鑰對其進行簽名。只要知道金鑰的唯一人是所有者,這就是安全的。這種證明儲存在簽名欄位中。我們的實現與比特幣之間的區別在於,我們直接透過其雜湊值引用parent_output,而比特幣則使用產生了UTXO的交易的雜湊值以及一個索引來從交易輸出列表中選擇特定條目。原因是比特幣是根據交易和區塊定義的,而我們是根據業務邏輯和狀態轉換來定義的。在我們的例子中,Substrate事務只是輔助實體,它們促進了流程,並且大部分都超出了業務邏輯的範圍。稍後再談。接下來是定義UTXO的TransactionOutput結構:/// Single transaction output to create upon transaction dispatch#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Hash)]
pub struct TransactionOutput {/// Value associated with this outputpub value: Value,/// Public key associated with this output. In order to spend this output/// owner must provide a proof by hashing whole `TransactionOutput` and/// signing it with a corresponding private key.
pub pubkey: H256,/// Unique (potentially random) value used to distinguish this/// particular output from others addressed to the same public/// key with the same value. Prevents potential replay attacks.pub salt: u32,}
value和pubkey欄位的用途應該已經清楚。唯一值得解釋的是salt。此欄位提供了額外的熵,以使每個UTXO及其雜湊真正唯一。想象一下這樣的情況,我們有一個機器人每天向同一個收件人傳送10個代幣。為了簡單起見,它可以使用相同的目的地地址,即接收者的公鑰。因為value和pubkey欄位都包含相同的資料,所以bot建立的所有UTXO看起來都完全相同,因此具有相同的雜湊。沒有salt,攻擊者將能夠記住所有者所用的第一個UTXO的簽名,然後在所有者甚至沒有注意到之前就花費所有後續的UTXO來竊取金錢,這稱為重放攻擊。同樣還有另一種在原始碼中尚未解決的重放攻擊的可能性。請注意,由於比特幣實現依賴於交易雜湊來精確定位UTXO,因此它不會遭受此問題的困擾,因此不需要salt。然而,這並不意味著比特幣不可能進行重放攻擊。這就是為什麼為每一筆交易生成一個新的比特幣地址是至關重要的。狀態到目前為止,我們已經定義了表示記憶體中單個事務所需的所有資料結構。但是我們還需要告訴Substrate透過在一段時間內保留此資訊,在狀態資料庫中儲存什麼以支援鏈的業務邏輯。這是透過使用decl_storage定義模組儲存來完成的!marco:
decl_storage! {trait Store for Module<T: Trait> as Utxo {/// All valid unspent transaction outputs are stored in this map./// Initial set of UTXO is populated from the list stored in genesis.UnspentOutputs build(|config: &GenesisConfig<T>| {config.initial_utxo
.iter().cloned().map(|u| (BlakeTwo256::hash_of(&u), u)).collect::<Vec<_>>()}): map H256 => Option<TransactionOutput>;/// Total leftover value to be redistributed among authorities.
/// It is accumulated during block execution and then drained/// on block finalization.LeftoverTotal: Value;/// Outputs that are lockedLockedOutputs: map H256 => Option<LockStatus<T>>;}
add_extra_genesis {config(initial_utxo): Vec<TransactionOutput>;}}上面的程式碼實際上它僅定義了三件事:未使用的輸出列表,當前剩餘值量以及已鎖定且除非解鎖就無法使用的輸出列表。除此之外,它還定義了在引導過程中如何使用一組初始的UTXO填充鏈。需要要注意的是,狀態儲存與區塊儲存有很大不同。
區塊儲存是每個區塊鏈節點的重要組成部分,用於儲存該鏈中的區塊。如今只有專用的存檔節點將整個鏈儲存在本地,而普通節點僅管理最近區塊的臨時子集。另一方面,狀態儲存與業務邏輯有關。它包含反映業務實體及其關係的當前狀態所需的所有資料。為了驗證傳入交易,您唯一需要知道的是所有受影響方的狀態及其資金額。這就是為什麼即使是輕度客戶也能夠驗證交易的原因。設計邏輯當我們說Alice從Bob那裡得到一些資金時,我們的意思是根據規則,Bob用來支付Alice的一組UTXO必須標記為已用(以防止Bob以後重複使用)。然後Bob為Alice建立的一組新UTXO現在必須被記住是有效的,這樣Alice就可以在之後使用它們了。這些規則是業務邏輯的本質,在驗證和排程傳入事務時需要考慮這些規則。讓我們看一下整個UTXO模組的入口點:
decl_module! {pub struct Module<T: Trait> for enum Call where origin: T::Origin {/// Dispatch a single transaction and update UTXO set accordinglypub fn execute(origin, transaction: Transaction) -> Result {ensure_inherent(origin)?;let leftover = match Self::check_transaction(&transaction)? {
CheckInfo::MissingInputs(_) => return Err("all parent outputs must exist and be unspent"),CheckInfo::Totals { input, output } => input - output};Self::update_storage(&transaction, leftover)?;Self::deposit_event(Event::TransactionExecuted(transaction));Ok(())
}/// Handler called by the system on block finalizationfn on_finalise() {let authorities: Vec<_> = Consensus::authorities().iter().map(|&a| a.into()).collect();Self::spend_leftover(&authorities);}
}}我們定義了兩個函式:execute和on_finalizeexecute函式是整個UTXO邏輯的關鍵。它接受單個事務,對其進行檢查,如果有效,則透過更新儲存應用該事務。最後它儲存一個事件,表示一個事務剛剛被處理。當剛剛形成一個充滿交易的單個塊時,將呼叫on_finalize事件處理程式。透過觸發該事件處理程式,Substrate允許執行時根據需要採取一些措施。我們使用此處理程式從參與建立此塊的驗證程式之間的所有事務中重新分配合並的剩餘價值,作為對其工作的獎勵。交易檢查
為了驗證傳入事務,我們需要確保以下內容:1. 輸入和輸出不為空。2. 所有輸入與現有的、未使用的和未鎖定的輸出匹配。3. 每個輸入只使用一次。4. 每個輸出只定義一次,並且有一個非零值。5. 總產值不得超過總產值。
6. 新的輸出不能與現有的衝突。7. 輸入和輸出值之和不能溢位。8. 提供的簽名有效。違反任何一項檢查都可能導致連鎖安全性問題,因此正確實施它們至關重要。幸運的是,邏輯非常簡單明瞭:pub fn check_transaction(transaction: &Transaction) -> CheckResult<'_> {ensure!(!transaction.inputs.is_empty(), "no inputs");
ensure!(!transaction.outputs.is_empty(), "no outputs");{// Collecting inputs into a set where every element is unique.// If two equal elements are inserted, only one will remain.let input_set: BTreeMap<_, ()> = transaction.inputs
.iter().map(|input| (input, ())).collect();// Ensuring that the size of original collection and the set are equal.// If they are not, then due to pigeonhole principle, some entries must// have been maliciously mentioned several times.
ensure!(input_set.len() == transaction.inputs.len(),"each input must be used only once");}{
let output_set: BTreeMap<_, ()> = transaction.outputs.iter().map(|output| (output, ())).collect();ensure!(
output_set.len() == transaction.outputs.len(),"each output must be defined only once");}let mut total_input: Value = 0;let mut missing_utxo = Vec::new();
for input in transaction.inputs.iter() {// Fetch UTXO from the storageif let Some(output) = <UnspentOutputs<T>>::get(&input.parent_output) {ensure!(!<LockedOutputs<T>>::exists(&input.parent_output), "utxo is locked");// Check that we're authorized to spend this UTXOensure!(
ed25519_verify(input.signature.as_fixed_bytes(),input.parent_output.as_fixed_bytes(),&output.pubkey),"signature must be valid"
);// Add the value to the input totaltotal_input = total_input.checked_add(output.value).ok_or("input value overflow")?;} else {missing_utxo.push(&input.parent_output);}
}let mut total_output: Value = 0;for output in transaction.outputs.iter() {ensure!(output.value != 0, "output value must be nonzero");let hash = BlakeTwo256::hash_of(output);ensure!(!<UnspentOutputs<T>>::exists(hash), "output already exists");
total_output = total_output.checked_add(output.value).ok_or("output value overflow")?;}if missing_utxo.is_empty() {ensure!(total_input >= total_output, "output value must not exceed input value");Ok(CheckInfo::Totals { input: total_input, output: total_output })} else {
Ok(CheckInfo::MissingInputs(missing_utxo))}}您可能注意到,除了事務檢查之外,此函式還收集一些資訊。讓我們看看它的定義:/// Result of transaction verificationpub type CheckResult<'a> = rstd::result::Result<CheckInfo<'a>, &'static str>;
/// Information collected during transaction verificationpub enum CheckInfo<'a> {/// Combined value of all inputs and outputsTotals { input: Value, output: Value },/// Some referred UTXOs were missingMissingInputs(Vec<&'a H256>),
}/// Representation of UTXO valuepub type Value = u128;稍後將顯示,我們使用總的 inputs和outputs來計算交易的優先順序,並將剩餘價值的一部分作為塊式獎勵在驗證者之間重新分配。但是如果交易未透過驗證,談論這些價值絕對沒有任何意義。否則攻擊者將能夠透過淹沒交易池並阻止正常交易被派發,從而故意製作具有最高優先順序的交易並對鏈進行DoS。或者,它可能會“憑空產生”大量剩餘價值以利用獎勵系統。透過將資料組織為Rust列舉,可以防止意外誤用,因為只有在交易有效時值才可用。反之亦然,只有在發現事務引用狀態資料庫中不存在的某個UTXO時,才可以使用缺少輸入的列表。這樣一來,就不會濫用API,這有利於提高可讀性和鏈安全性。
狀態更新如果交易經過驗證並證明是正確的,那麼我們要做的就是更改鏈狀態以反映該交易所做的更改:/// Update storage to reflect changes made by transactionfn update_storage(transaction: &Transaction, leftover: Value) -> Result {// Calculate new leftover totallet new_total = <LeftoverTotal<T>>::get()
.checked_add(leftover).ok_or("leftover overflow")?;// Storing updated leftover value<LeftoverTotal<T>>::put(new_total);// Remove all used UTXO since they are now spentfor input in &transaction.inputs {
<UnspentOutputs<T>>::remove(input.parent_output);}// Add new UTXO to be used by future transactionsfor output in &transaction.outputs {let hash = BlakeTwo256::hash_of(output);<UnspentOutputs<T>>::insert(hash, output);
}Ok(())}基本上,我們刪除所有現在認為已用完的輸入,並新增所有新輸出以將其標記為可用。我們還將剩餘的值累積在臨時儲存變數LeftoverTotal中,該變數將在區塊確定期間使用。阻止獎勵區塊完成後,就該獎勵創作該區塊的節點了。這是透過重新分配從此區塊中包括的所有事務中收集的剩餘價值來完成的:
/// Redistribute combined leftover value evenly among authoritiesfn spend_leftover(authorities: &[H256]) {let leftover = <LeftoverTotal<T>>::take();let share_value = leftover / authorities.len() as Value;if share_value == 0 { return }for authority in authorities {
let utxo = TransactionOutput {pubkey: *authority,value: share_value,salt: System::block_number() as u32,};let hash = BlakeTwo256::hash_of(&utxo);
if !<UnspentOutputs<T>>::exists(hash) {<UnspentOutputs<T>>::insert(hash, utxo);runtime_io::print("leftover share sent to");runtime_io::print(hash.as_fixed_bytes() as &[u8]);} else {runtime_io::print("leftover share wasted due to hash collision");
}}}邏輯非常簡單:我們接受一個許可權列表,然後將剩餘的總值除以許可權數平均得出一個share_value。然後,我們為每個作者建立一個UTXO,並將其插入UnspentOutputs中。我們將當前區塊號用作salt值,以防止上述潛在的重放攻擊。我們還透過將獎勵UTXO插入UnspentOutputs來進行檢查,以確保我們不會意外覆蓋一些恰好具有相同雜湊值的現有UTXO。這種情況在實踐中極為罕見,但是不幸的是,如果有人因為常規獎勵UTXO覆蓋了他或她的UTXO而損失了數百萬美元的UTXO,那將是不幸的。乍一看,我們似乎是憑空創造價值,但仔細想想,人們可能會意識到,全域性價值不會增加,因為交易所有者明確放棄了部分資金,以換取優先權。
最後,由於每個區塊發起人都知道所有詳細資訊,例如區塊編號,該特定時代使用的會話金鑰,當然還有與該會話金鑰匹配的秘密金鑰,因此區塊發起人將始終能夠重構UTXO,計算其雜湊值,即使沒有將UTXO儲存在任何地方也可以要求其獎勵。UTXO鎖定這就是與比特幣不同的地方。據我所知,比特幣規範並沒有規定哪些資訊需要儲存在磁碟上以及如何儲存。唯一重要的是比特幣協議本身,它是根據交易和區塊來制定的。因此,每個節點必須建立自己的理解,在區塊鏈歷史的任何給定點上,哪些UTXO是有效的。相反,根據定義,我們的UTXO實現具有所有參與節點都同意的全域性狀態資料庫。眾所周知,它用於儲存UTXO狀態和剩餘的臨時值。由於狀態資料庫是共識的一部分,因此我們可以在業務邏輯中依賴狀態資料庫的內容,並確保所有其他節點都將這樣做。但沒有什麼能阻止我們儲存額外的東西。例如我們可以將現有UTXO的雜湊對映對映到定義該UTXO的鎖定狀態的結構。如果UTXO被鎖定,則不允許以通常的方式使用它:
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]#[derive(Clone, Encode, Decode, Hash)]pub enum LockStatus<T: Trait> {// Referred UTXO is lockedLocked,// Referred UTXO is locked until specified block
LockedUntil(T::BlockNumber),}decl_storage! {trait Store for Module<T: Trait> as Utxo {.../// Outputs that are locked
LockedOutputs: map H256 => Option<LockStatus<T>>;}}很像鎖在保險箱裡的現金:你可以最終使用它,但不早於你開啟保險箱的時候。它是可用的,只是鎖上了。你可能在想,為什麼一個人會需要這個?您會發現,在加密貨幣的世界中,有一種趨勢是用貪婪程度更低,更有效的方法來代替舊的廢物證明演算法(proof-of-waste)。一種可能是將資金本身用作保證同peer行為正常的保證。基本上,有人會說:“我發誓要遵守規則。這是我的錢。請把它鎖在安全的地方。如果有人證明我的行為不當,那麼我的錢就必須削減或在誠實的參與者之間分配。”當然,如果這樣的人隨後希望取回他或她的資金,則網路將檢查是否沒有惡意行為。在最後期限內提取,然後解鎖資金。通常,鎖定的資金越多,您獲得的能力,投票權重或收入就越多。此類系統通常簡稱為權益證明或PoS。
只要網路中三分之二以上的節點沒有惡意,並且按照協議操作,這就可以正常工作。除了執行常規任務外,這些節點還將支援PoS。在類似以太坊的區塊鏈中,在排程交易時,對可用資金的推論可能非常複雜:每個節點必須確保有足夠的可用資金,尤其是因為可能存在與時間相關的複雜合約。有趣的是,我們的UTXO實現以幾行程式碼來完成。與以太坊式的鏈相反,類比特幣的鏈的資金已經以自然的方式分配。我們可以輕鬆地鎖定單個UTXO,並在滿足某些解鎖條件之前防止其被花費。由於狀態資料庫不是其原始規範的一部分,因此在比特幣中很難做到這一點。因此,很難在任何給定的時間點推斷哪個UTXO被鎖定,更不用說客戶端相容性問題了。交易排序在談到鏈的業務邏輯時,我們提到Substrate為我們完成了所有骯髒的工作,例如處理塊儲存,執行網路互動和進行共識投票。但這並非總是如此。我們已經說過,我們的runtime原子性一次排程一個事務。因此如果該交易有效,則狀態將相應更改。
但是如果兩個從屬事務在短時間內到達同一節點會發生什麼呢?真實的網路是複雜且不可預測的。連線性問題和突然的拓撲更改可能會對傳輸的資料造成各種影響。值得注意的是,訊息可能會丟失,延遲或重新排序。後一個事實對我們尤為重要。想象一個情況,我們有兩個事務,A和B,B依賴於A。在UTXO的情況下,這意味著B消耗了A建立的UTXO。如果B在A之前到達,我們可能會遇到這樣的情況節點執行時將無法檢查事務的有效性,因為它引用了看似不存在的UTXO。當然,我們確實知道它存在,但尚未交付,但是節點不知道。本質上,它有兩個選擇:1. 只需將交易B視為無效即可。如果原始傳送人重新廣播該交易,它仍將有機會被應用,但不會早於A被排程。此解決方案可能有效,但它是骯髒且無效的。此外,一些嚴重的網路問題可能導致無法分配B的情況,從而使整個系統無用。我們可以做得更好。2. 將事務B的分派推遲到有意義的時候。在我們的情況下,我們需要以某種方式等待A的傳送。第二種選擇似乎更有趣,但是在實踐中我們該如何做呢?透過其本身的設計,Substrate對執行時內部或鏈的業務邏輯一無所知。實際上,從其角度來看,Substrate就像不透明的位元組陣列一樣“看到”我們的交易。這裡的解決方案是“解釋” Substrate如何處理我們的交易以及如何正確排序它們。這是透過使用事務池向執行時公開的專用TaggedTransactionQueue API完成的。
在Substrate中,每個事務都與兩組標籤相關聯:require和Provides。標籤只是代表某個唯一值的任意位元組向量。第一組描述此事務需要哪些標籤,而第二組定義此事務提供的標籤。在上述情況下,我們需要透過宣告A提供一些標籤而B消耗與其要求相同的標籤來將事務A和B連結在一起。為了簡單起見,我們可以使用UTXO雜湊作為標籤。透過遍歷事務並查詢其標記,事務池以一種順序組織它們,以使每個事務都可以滿足其要求。那些熟悉電腦科學的人可能會意識到這類似於拓撲順序。有時兩個事務不相互依賴,但又依賴於第三次事務。例如我們可能有交易A產生兩個輸出,交易B和C分別花費這兩個輸出。這將導致B和C都依賴於A。拓撲排序狀態規定必須在B和C之前排程A,但是未定義分發B和C的順序。在這種情況下,事務池使用其他條件來確定事務的優先順序。經典解決方案是將剩餘值的數量用作優先順序。交易所有者有意留給當局的資金越多,交易優先順序就越高,雙贏。讓我們看看它如何在我們的鏈中實現:
impl runtime_api::TaggedTransactionQueue<Block> for Runtime {    fn validate_transaction(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity {        // A bunch of imports        use srml_support::IsSubType;        use runtime_primitives::{            traits::Hash,
            transaction_validity::{TransactionLongevity, TransactionPriority, TransactionValidity},        };        // Extrinsics representing UTXO transaction need some special handling        if let Some(&utxo::Call::execute(ref transaction)) = IsSubType::<utxo::Module<Runtime>>::is_aux_sub_type(&tx.function) {            // List of tags to require            let requires;
            // Transaction priority to assign            let priority;            match <utxo::Module<Runtime>>::check_transaction(&transaction) {                // Verification failed                Err(e) => {                    runtime_io::print(e);
                    return TransactionValidity::Invalid(1);                }                // Transaction was fully verified and is valid                Ok(utxo::CheckInfo::Totals { input, output }) => {

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;