前段時間,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 transaction
pub inputs: Vec《TransactionInput》,
/// UTXOs to be created as a result of current transaction dispatch
pub 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 spent
pub parent_output: H256,
/// Proof that transaction owner is authorized to spend referred UTXO
pub 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 output
pub 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 locked
LockedOutputs: 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 accordingly
pub 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 finalization
fn on_finalise() {
let authorities: Vec《_》 = Consensus::authorities().iter().map(|&a| a.into()).collect();
Self::spend_leftover(&authorities);
}
}
}
我們定義了兩個函數:execute和on_finalize
execute函數是整個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 storage
if 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 UTXO
ensure!(
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 total
total_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 verification
pub type CheckResult《‘a》 = rstd::result::Result《CheckInfo《’a》, &‘static str》;
/// Information collected during transaction verification
pub enum CheckInfo《’a》 {
/// Combined value of all inputs and outputs
Totals { input: Value, output: Value },
/// Some referred UTXOs were missing
MissingInputs(Vec《&‘a H256》),
}
/// Representation of UTXO value
pub type Value = u128;
稍后將顯示,我們使用總的 inputs和outputs來計算交易的優先級,并將剩余價值的一部分作為塊式獎勵在驗證者之間重新分配。
但是如果交易未通過驗證,談論這些價值絕對沒有任何意義。否則攻擊者將能夠通過淹沒交易池并阻止正常交易被派發,從而故意制作具有最高優先級的交易并對鏈進行DoS。或者,它可能會“憑空產生”大量剩余價值以利用獎勵系統。
通過將數據組織為Rust枚舉,可以防止意外誤用,因為只有在交易有效時值才可用。反之亦然,只有在發現事務引用狀態數據庫中不存在的某個UTXO時,才可以使用缺少輸入的列表。這樣一來,就不會濫用API,這有利于提高可讀性和鏈安全性。
狀態更新
如果交易經過驗證并證明是正確的,那么我們要做的就是更改鏈狀態以反映該交易所做的更改:
/// Update storage to reflect changes made by transaction
fn update_storage(transaction: &Transaction, leftover: Value) -》 Result {
// Calculate new leftover total
let 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 spent
for input in &transaction.inputs {
《UnspentOutputs《T》》::remove(input.parent_output);
}
// Add new UTXO to be used by future transactions
for output in &transaction.outputs {
let hash = BlakeTwo256::hash_of(output);
《UnspentOutputs《T》》::insert(hash, output);
}
Ok(())
}
基本上,我們刪除所有現在認為已用完的輸入,并添加所有新輸出以將其標記為可用。我們還將剩余的值累積在臨時存儲變量LeftoverTotal中,該變量將在區塊確定期間使用。
阻止獎勵
區塊完成后,就該獎勵創作該區塊的節點了。這是通過重新分配從此區塊中包括的所有事務中收集的剩余價值來完成的:
/// Redistribute combined leftover value evenly among authorities
fn 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 locked
Locked,
// 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的順序。在這種情況下,事務池使用其他條件來確定事務的優先級。
經典解決方案是將剩余值的數量用作優先級。交易所有者有意留給當局的資金越多,交易優先級就越高,雙贏。
責任編輯;zl
評論
查看更多