基于區(qū)塊鏈技術(shù)的底層架構(gòu)Substrate介紹
掃描二維碼
隨時(shí)隨地手機(jī)看文章
前段時(shí)間,Gavin Wood要求我研究基于Substrate實(shí)施UTXO鏈的可能性,Substrate是目前最有前景的區(qū)塊鏈技術(shù)底層架構(gòu),而且目前Polkadot是基于substrate進(jìn)行開發(fā)的。
我們想知道Substrate的靈活性,而UTXO鏈似乎是進(jìn)行測(cè)試的一個(gè)不錯(cuò)的選擇,因?yàn)樗c我們過去在實(shí)施Substrate時(shí)所考慮的完全不同。如果可行,則表明Substrate確實(shí)非常靈活且通用。我們可以更有信心,把Substrate應(yīng)該到不同領(lǐng)域的區(qū)塊鏈項(xiàng)目中。
與以太坊類似,Substrate保留一定數(shù)量的可用資金。從某種意義上講,它類似于普通的銀行系統(tǒng),其中帳戶余額用數(shù)字表示,并存儲(chǔ)在數(shù)據(jù)庫或計(jì)算機(jī)內(nèi)存中的某個(gè)位置。
從歷史上看,第一個(gè)成功的加密貨幣是比特幣,它使用完全不同的方法。在比特幣中,本身沒有賬戶,余額也不是作為一個(gè)數(shù)字存儲(chǔ)的。取而代之的是,可用資金是根據(jù)一組所謂的未用交易輸出來定義的,簡(jiǎn)稱為UTXO,這是一個(gè)非常簡(jiǎn)單的主意。
簡(jiǎn)而言之是UTXO
簡(jiǎn)而言之,UTXO非常類似于現(xiàn)金,或者更確切地說,是旅行支票。
當(dāng)你用現(xiàn)金支付某人時(shí),你通常會(huì)想到要支付的總價(jià)值,但是你用一組獨(dú)特的、不可分割的單位(代幣或鈔票)來表示這個(gè)價(jià)值。例如如果Alice希望付給Bob$250美元,她可以給Bob2張價(jià)值$100美元的鈔票和1張價(jià)值50美元的鈔票,或五張面值$50的鈔票,或總計(jì)為所需值的任何其他組合。
每一張鈔票都是獨(dú)一無二的。盡管有數(shù)百萬張鈔票具有相同的價(jià)值,但是每張鈔票在物理上都是唯一的,并在其表面印有序列號(hào)。通常情況下,我們不太注意它,只是在支付東西的時(shí)候,把兩張100美元的鈔票視為相等,但這個(gè)數(shù)字對(duì)于銀行控制資金流動(dòng)和真?zhèn)螜z查是必不可少的。
因此每張鈔票代表著具有預(yù)定和固定價(jià)值的獨(dú)特且不可分割的資產(chǎn),這些資產(chǎn)只能整體使用,即您不能將100美元的鈔票撕成兩張50美元的鈔票。當(dāng)然你可以要求某人找零,將價(jià)值分成較小的單位,但是您仍然需要花100美元的原始鈔票。同樣,購買咖啡時(shí),您會(huì)花掉10美元的鈔票,作為回報(bào),您會(huì)得到咖啡和一些零錢。
UTXO的工作方式與此類似。要使用比特幣付款,您的錢包中應(yīng)該已經(jīng)有一些未使用的資產(chǎn)。與法定貨幣一樣,您可以結(jié)合使用多個(gè)UTXO以獲得更大的價(jià)值。
與現(xiàn)金不同,每個(gè)UTXO都有自己的所有者。從這個(gè)意義上說,它類似于旅行支票,因?yàn)橹挥兄彼腥瞬趴梢允褂盟?。這是通過所有者簽名增加單位來完成的。不同之處在于,旅行支票由所有者的手簽名,而UTXO使用非對(duì)稱加密,并且包含收件人而非發(fā)件人的公鑰。而是鈔票由政府印刷,UTXO由發(fā)起人創(chuàng)建。
目標(biāo)
在我們的研究中,我們將嘗試建立一個(gè)區(qū)塊鏈模型,使用與比特幣相同的原理將資金從一個(gè)所有者轉(zhuǎn)移到另一個(gè)所有者。
當(dāng)閱讀文章時(shí),請(qǐng)記住我們的主要目標(biāo)是評(píng)估Substrate的靈活性,而不是比特幣移植時(shí)使用端口的詳細(xì)解釋。在某些情況下,其實(shí)現(xiàn)幾乎與Parity比特幣的實(shí)現(xiàn)相同,而在其他情況下則不是。例如當(dāng)前的實(shí)現(xiàn)不支持挖掘和coinbase事務(wù);它只是重新分配在genesis塊中初始化的“預(yù)先定義”UTXO集的值。
另外,請(qǐng)注意,所提供的實(shí)現(xiàn)還不能完全投入生產(chǎn)。它尚未經(jīng)過正式驗(yàn)證,并且可能存在一些安全性或穩(wěn)定性問題,因此,我不建議您在沒有適當(dāng)研究的情況下,將其用于任何關(guān)鍵基礎(chǔ)架構(gòu)。但是如果有人將這個(gè)原型制作成可行的解決方案,我會(huì)非常高興。
話雖如此,讓我們繼續(xù)進(jìn)行代碼。
首先讓我們談?wù)凷ubstrate如何允許您對(duì)其進(jìn)行自定義。作為應(yīng)用程序員,您應(yīng)該提供一個(gè)runtime的運(yùn)行邏輯,這些邏輯告訴Substrate如何處理鏈以及應(yīng)采用的業(yè)務(wù)邏輯。所有這些都圍繞著狀態(tài)轉(zhuǎn)換函數(shù)(簡(jiǎn)稱STF)的概念。但現(xiàn)在我們只需說,每個(gè)區(qū)塊鏈都可以表示為一個(gè)函數(shù),接受當(dāng)前狀態(tài)和一個(gè)掛起的事務(wù),然后生成另一個(gè)狀態(tài),反映在應(yīng)用事務(wù)后所做的更改。
假設(shè)Alice和Bob都有10個(gè)代幣,然后Alice向Bob發(fā)送了5個(gè)代幣。應(yīng)用此交易后,我們預(yù)計(jì)Alice現(xiàn)在將有5個(gè)代幣,而Bob將有15個(gè)代幣。如果Bob隨后嘗試向Claire支付20個(gè)代幣,則該交易必須視為無效,因?yàn)楦鶕?jù)最新的鏈條狀態(tài),Bob只有15個(gè)代幣。
這正是runTIme的意圖-它定義了所有實(shí)體及其關(guān)系,驗(yàn)證了傳入的事務(wù)并相應(yīng)地更改了狀態(tài)。
讓我們從指定將用于定義UTXO鏈的業(yè)務(wù)邏輯的數(shù)據(jù)類型開始。首先是TransacTIon 類型。它表示要調(diào)度的單個(gè)UTXO事務(wù):
/// 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》,
}
這里沒有什么特別的,只是一個(gè)簡(jiǎn)單的定義,即Transaction只是一堆輸入和輸出。如果您好奇,可以將其與Parity Bitcoin的版本進(jìn)行比較,以了解相似之處。上面所有#[。..]怪異都稱為屬性,它告訴Rust編譯器為我們實(shí)現(xiàn)各種操作,例如比較運(yùn)算符,哈希函數(shù)和序列化例程。您現(xiàn)在可以放心地忽略它們。
我留下了所有注釋和屬性,以表明即使將它們包括在內(nèi),代碼仍會(huì)保持緊湊。我認(rèn)為,即使與在成千上萬行中做“同一件事”的Parity Bitcoin相比,這也是Substrate的可觀成就。就像在用JavaScript為網(wǎng)絡(luò)編寫代碼時(shí)一樣,您并沒有考慮過瀏覽器引擎或任何底層操作系統(tǒng)(包括操作系統(tǒng))的復(fù)雜性。相反,您只是以高級(jí)形式制定業(yè)務(wù)邏輯,然后讓系統(tǒng)完成其余工作。
好的,但是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匯總花費(fèi)一個(gè)UTXO所需的所有數(shù)據(jù)。首先我們需要一種方法來引用一些現(xiàn)有的UTXO。最簡(jiǎn)單的方法是使用其哈希作為標(biāo)識(shí)符。這是分布式系統(tǒng)世界中的一種普遍做法,并且只要哈希沖突的可能性可以忽略不計(jì),它就可以很好地工作。為此我們使用256位Blake2。parent_output字段包含此類哈希。
如前所述,要使用UTXO,所有者必須使用與存儲(chǔ)在該特定UTXO中的公鑰匹配的秘密密鑰對(duì)其進(jìn)行簽名。只要知道密鑰的唯一人是所有者,這就是安全的。這種證明存儲(chǔ)在簽名字段中。
我們的實(shí)現(xiàn)與比特幣之間的區(qū)別在于,我們直接通過其哈希值引用parent_output,而比特幣則使用產(chǎn)生了UTXO的交易的哈希值以及一個(gè)索引來從交易輸出列表中選擇特定條目。原因是比特幣是根據(jù)交易和區(qū)塊定義的,而我們是根據(jù)業(yè)務(wù)邏輯和狀態(tài)轉(zhuǎn)換來定義的。在我們的例子中,Substrate事務(wù)只是輔助實(shí)體,它們促進(jìn)了流程,并且大部分都超出了業(yè)務(wù)邏輯的范圍。稍后再談。
接下來是定義UTXO的TransactionOutput結(jié)構(gòu):
/// 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字段的用途應(yīng)該已經(jīng)清楚。唯一值得解釋的是salt。此字段提供了額外的熵,以使每個(gè)UTXO及其哈希真正唯一。想象一下這樣的情況,我們有一個(gè)機(jī)器人每天向同一個(gè)收件人發(fā)送10個(gè)代幣。為了簡(jiǎn)單起見,它可以使用相同的目的地地址,即接收者的公鑰。因?yàn)関alue和pubkey字段都包含相同的數(shù)據(jù),所以bot創(chuàng)建的所有UTXO看起來都完全相同,因此具有相同的散列。
沒有salt,攻擊者將能夠記住所有者所用的第一個(gè)UTXO的簽名,然后在所有者甚至沒有注意到之前就花費(fèi)所有后續(xù)的UTXO來竊取金錢,這稱為重放攻擊。同樣還有另一種在源代碼中尚未解決的重放攻擊的可能性。
請(qǐng)注意,由于比特幣實(shí)現(xiàn)依賴于交易哈希來精確定位UTXO,因此它不會(huì)遭受此問題的困擾,因此不需要salt。然而,這并不意味著比特幣不可能進(jìn)行重放攻擊。這就是為什么為每一筆交易生成一個(gè)新的比特幣地址是至關(guān)重要的。
狀態(tài)
到目前為止,我們已經(jīng)定義了表示內(nèi)存中單個(gè)事務(wù)所需的所有數(shù)據(jù)結(jié)構(gòu)。但是我們還需要告訴Substrate通過在一段時(shí)間內(nèi)保留此信息,在狀態(tài)數(shù)據(jù)庫中存儲(chǔ)什么以支持鏈的業(yè)務(wù)邏輯。
這是通過使用decl_storage定義模塊存儲(chǔ)來完成的!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》;
}
}
上面的代碼實(shí)際上它僅定義了三件事:未使用的輸出列表,當(dāng)前剩余值量以及已鎖定且除非解鎖就無法使用的輸出列表。除此之外,它還定義了在引導(dǎo)過程中如何使用一組初始的UTXO填充鏈。
需要要注意的是,狀態(tài)存儲(chǔ)與區(qū)塊存儲(chǔ)有很大不同。
區(qū)塊存儲(chǔ)是每個(gè)區(qū)塊鏈節(jié)點(diǎn)的重要組成部分,用于存儲(chǔ)該鏈中的區(qū)塊。如今只有專用的存檔節(jié)點(diǎn)將整個(gè)鏈存儲(chǔ)在本地,而普通節(jié)點(diǎn)僅管理最近區(qū)塊的臨時(shí)子集。
另一方面,狀態(tài)存儲(chǔ)與業(yè)務(wù)邏輯有關(guān)。它包含反映業(yè)務(wù)實(shí)體及其關(guān)系的當(dāng)前狀態(tài)所需的所有數(shù)據(jù)。為了驗(yàn)證傳入交易,您唯一需要知道的是所有受影響方的狀態(tài)及其資金額。這就是為什么即使是輕度客戶也能夠驗(yàn)證交易的原因。
設(shè)計(jì)邏輯
當(dāng)我們說Alice從Bob那里得到一些資金時(shí),我們的意思是根據(jù)規(guī)則,Bob用來支付Alice的一組UTXO必須標(biāo)記為已用(以防止Bob以后重復(fù)使用)。然后Bob為Alice創(chuàng)建的一組新UTXO現(xiàn)在必須被記住是有效的,這樣Alice就可以在之后使用它們了。
這些規(guī)則是業(yè)務(wù)邏輯的本質(zhì),在驗(yàn)證和調(diào)度傳入事務(wù)時(shí)需要考慮這些規(guī)則。
讓我們看一下整個(gè)UTXO模塊的入口點(diǎn):
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);
}
}
}
我們定義了兩個(gè)函數(shù):execute和on_finalize
execute函數(shù)是整個(gè)UTXO邏輯的關(guān)鍵。它接受單個(gè)事務(wù),對(duì)其進(jìn)行檢查,如果有效,則通過更新存儲(chǔ)應(yīng)用該事務(wù)。最后它存儲(chǔ)一個(gè)事件,表示一個(gè)事務(wù)剛剛被處理。
當(dāng)剛剛形成一個(gè)充滿交易的單個(gè)塊時(shí),將調(diào)用on_finalize事件處理程序。通過觸發(fā)該事件處理程序,Substrate允許運(yùn)行時(shí)根據(jù)需要采取一些措施。我們使用此處理程序從參與創(chuàng)建此塊的驗(yàn)證程序之間的所有事務(wù)中重新分配合并的剩余價(jià)值,作為對(duì)其工作的獎(jiǎng)勵(lì)。
交易檢查
為了驗(yàn)證傳入事務(wù),我們需要確保以下內(nèi)容:
1. 輸入和輸出不為空。
2. 所有輸入與現(xiàn)有的、未使用的和未鎖定的輸出匹配。
3. 每個(gè)輸入只使用一次。
4. 每個(gè)輸出只定義一次,并且有一個(gè)非零值。
5. 總產(chǎn)值不得超過總產(chǎn)值。
6. 新的輸出不能與現(xiàn)有的沖突。
7. 輸入和輸出值之和不能溢出。
8. 提供的簽名有效。
違反任何一項(xiàng)檢查都可能導(dǎo)致連鎖安全性問題,因此正確實(shí)施它們至關(guān)重要。幸運(yùn)的是,邏輯非常簡(jiǎn)單明了:
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?。?/p>
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?。?/p>
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?。?/p>
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?。╫utput.value != 0, “output value must be nonzero”);
let hash = BlakeTwo256::hash_of(output);
ensure?。ǎ 禪nspentOutputs《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?。╰otal_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))
}
}
您可能注意到,除了事務(wù)檢查之外,此函數(shù)還收集一些信息。讓我們看看它的定義:
/// Result of transaction verification
pub type CheckResult《‘a(chǎn)》 = 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(chǎn) H256》),
}
/// Representation of UTXO value
pub type Value = u128;
稍后將顯示,我們使用總的 inputs和outputs來計(jì)算交易的優(yōu)先級(jí),并將剩余價(jià)值的一部分作為塊式獎(jiǎng)勵(lì)在驗(yàn)證者之間重新分配。
但是如果交易未通過驗(yàn)證,談?wù)撨@些價(jià)值絕對(duì)沒有任何意義。否則攻擊者將能夠通過淹沒交易池并阻止正常交易被派發(fā),從而故意制作具有最高優(yōu)先級(jí)的交易并對(duì)鏈進(jìn)行DoS?;蛘?,它可能會(huì)“憑空產(chǎn)生”大量剩余價(jià)值以利用獎(jiǎng)勵(lì)系統(tǒng)。
通過將數(shù)據(jù)組織為Rust枚舉,可以防止意外誤用,因?yàn)橹挥性诮灰子行r(shí)值才可用。反之亦然,只有在發(fā)現(xiàn)事務(wù)引用狀態(tài)數(shù)據(jù)庫中不存在的某個(gè)UTXO時(shí),才可以使用缺少輸入的列表。這樣一來,就不會(huì)濫用API,這有利于提高可讀性和鏈安全性。
狀態(tài)更新
如果交易經(jīng)過驗(yàn)證并證明是正確的,那么我們要做的就是更改鏈狀態(tài)以反映該交易所做的更改:
/// 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(“l(fā)eftover 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(())
}
基本上,我們刪除所有現(xiàn)在認(rèn)為已用完的輸入,并添加所有新輸出以將其標(biāo)記為可用。我們還將剩余的值累積在臨時(shí)存儲(chǔ)變量LeftoverTotal中,該變量將在區(qū)塊確定期間使用。
阻止獎(jiǎng)勵(lì)
區(qū)塊完成后,就該獎(jiǎng)勵(lì)創(chuàng)作該區(qū)塊的節(jié)點(diǎn)了。這是通過重新分配從此區(qū)塊中包括的所有事務(wù)中收集的剩余價(jià)值來完成的:
/// 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(“l(fā)eftover share sent to”);
runtime_io::print(hash.as_fixed_bytes() as &[u8]);
} else {
runtime_io::print(“l(fā)eftover share wasted due to hash collision”);
}
}
}
邏輯非常簡(jiǎn)單:我們接受一個(gè)權(quán)限列表,然后將剩余的總值除以權(quán)限數(shù)平均得出一個(gè)share_value。然后,我們?yōu)槊總€(gè)作者創(chuàng)建一個(gè)UTXO,并將其插入U(xiǎn)nspentOutputs中。我們將當(dāng)前區(qū)塊號(hào)用作salt值,以防止上述潛在的重放攻擊。
我們還通過將獎(jiǎng)勵(lì)UTXO插入U(xiǎn)nspentOutputs來進(jìn)行檢查,以確保我們不會(huì)意外覆蓋一些恰好具有相同哈希值的現(xiàn)有UTXO。這種情況在實(shí)踐中極為罕見,但是不幸的是,如果有人因?yàn)槌R?guī)獎(jiǎng)勵(lì)UTXO覆蓋了他或她的UTXO而損失了數(shù)百萬美元的UTXO,那將是不幸的。
乍一看,我們似乎是憑空創(chuàng)造價(jià)值,但仔細(xì)想想,人們可能會(huì)意識(shí)到,全局價(jià)值不會(huì)增加,因?yàn)榻灰姿姓呙鞔_放棄了部分資金,以換取優(yōu)先權(quán)。
最后,由于每個(gè)區(qū)塊發(fā)起人都知道所有詳細(xì)信息,例如區(qū)塊編號(hào),該特定時(shí)代使用的會(huì)話密鑰,當(dāng)然還有與該會(huì)話密鑰匹配的秘密密鑰,因此區(qū)塊發(fā)起人將始終能夠重構(gòu)UTXO,計(jì)算其哈希值,即使沒有將UTXO存儲(chǔ)在任何地方也可以要求其獎(jiǎng)勵(lì)。
UTXO鎖定
這就是與比特幣不同的地方。
據(jù)我所知,比特幣規(guī)范并沒有規(guī)定哪些信息需要存儲(chǔ)在磁盤上以及如何存儲(chǔ)。唯一重要的是比特幣協(xié)議本身,它是根據(jù)交易和區(qū)塊來制定的。因此,每個(gè)節(jié)點(diǎn)必須建立自己的理解,在區(qū)塊鏈歷史的任何給定點(diǎn)上,哪些UTXO是有效的。
相反,根據(jù)定義,我們的UTXO實(shí)現(xiàn)具有所有參與節(jié)點(diǎn)都同意的全局狀態(tài)數(shù)據(jù)庫。眾所周知,它用于存儲(chǔ)UTXO狀態(tài)和剩余的臨時(shí)值。由于狀態(tài)數(shù)據(jù)庫是共識(shí)的一部分,因此我們可以在業(yè)務(wù)邏輯中依賴狀態(tài)數(shù)據(jù)庫的內(nèi)容,并確保所有其他節(jié)點(diǎn)都將這樣做。
但沒有什么能阻止我們儲(chǔ)存額外的東西。例如我們可以將現(xiàn)有UTXO的哈希映射映射到定義該UTXO的鎖定狀態(tài)的結(jié)構(gòu)。如果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》》;
}
}
很像鎖在保險(xiǎn)箱里的現(xiàn)金:你可以最終使用它,但不早于你打開保險(xiǎn)箱的時(shí)候。它是可用的,只是鎖上了。
你可能在想,為什么一個(gè)人會(huì)需要這個(gè)?您會(huì)發(fā)現(xiàn),在加密貨幣的世界中,有一種趨勢(shì)是用貪婪程度更低,更有效的方法來代替舊的廢物證明算法(proof-of-waste)。一種可能是將資金本身用作保證同peer行為正常的保證。
基本上,有人會(huì)說:“我發(fā)誓要遵守規(guī)則。這是我的錢。請(qǐng)把它鎖在安全的地方。如果有人證明我的行為不當(dāng),那么我的錢就必須削減或在誠實(shí)的參與者之間分配?!碑?dāng)然,如果這樣的人隨后希望取回他或她的資金,則網(wǎng)絡(luò)將檢查是否沒有惡意行為。在最后期限內(nèi)提取,然后解鎖資金。通常,鎖定的資金越多,您獲得的能力,投票權(quán)重或收入就越多。此類系統(tǒng)通常簡(jiǎn)稱為權(quán)益證明或PoS。
只要網(wǎng)絡(luò)中三分之二以上的節(jié)點(diǎn)沒有惡意,并且按照協(xié)議操作,這就可以正常工作。除了執(zhí)行常規(guī)任務(wù)外,這些節(jié)點(diǎn)還將支持PoS。
在類似以太坊的區(qū)塊鏈中,在調(diào)度交易時(shí),對(duì)可用資金的推論可能非常復(fù)雜:每個(gè)節(jié)點(diǎn)必須確保有足夠的可用資金,尤其是因?yàn)榭赡艽嬖谂c時(shí)間相關(guān)的復(fù)雜合約。
有趣的是,我們的UTXO實(shí)現(xiàn)以幾行代碼來完成。與以太坊式的鏈相反,類比特幣的鏈的資金已經(jīng)以自然的方式分配。我們可以輕松地鎖定單個(gè)UTXO,并在滿足某些解鎖條件之前防止其被花費(fèi)。
由于狀態(tài)數(shù)據(jù)庫不是其原始規(guī)范的一部分,因此在比特幣中很難做到這一點(diǎn)。因此,很難在任何給定的時(shí)間點(diǎn)推斷哪個(gè)UTXO被鎖定,更不用說客戶端兼容性問題了。
交易排序
在談到鏈的業(yè)務(wù)邏輯時(shí),我們提到Substrate為我們完成了所有骯臟的工作,例如處理塊存儲(chǔ),執(zhí)行網(wǎng)絡(luò)交互和進(jìn)行共識(shí)投票。但這并非總是如此。我們已經(jīng)說過,我們的runtime原子性一次調(diào)度一個(gè)事務(wù)。因此如果該交易有效,則狀態(tài)將相應(yīng)更改。
但是如果兩個(gè)從屬事務(wù)在短時(shí)間內(nèi)到達(dá)同一節(jié)點(diǎn)會(huì)發(fā)生什么呢?真實(shí)的網(wǎng)絡(luò)是復(fù)雜且不可預(yù)測(cè)的。連接性問題和突然的拓?fù)涓目赡軙?huì)對(duì)傳輸?shù)臄?shù)據(jù)造成各種影響。值得注意的是,消息可能會(huì)丟失,延遲或重新排序。后一個(gè)事實(shí)對(duì)我們尤為重要。
想象一個(gè)情況,我們有兩個(gè)事務(wù),A和B,B依賴于A。在UTXO的情況下,這意味著B消耗了A創(chuàng)建的UTXO。如果B在A之前到達(dá),我們可能會(huì)遇到這樣的情況節(jié)點(diǎn)運(yùn)行時(shí)將無法檢查事務(wù)的有效性,因?yàn)樗昧丝此撇淮嬖诘腢TXO。當(dāng)然,我們確實(shí)知道它存在,但尚未交付,但是節(jié)點(diǎn)不知道。本質(zhì)上,它有兩個(gè)選擇:
1. 只需將交易B視為無效即可。如果原始發(fā)送人重新廣播該交易,它仍將有機(jī)會(huì)被應(yīng)用,但不會(huì)早于A被調(diào)度。此解決方案可能有效,但它是骯臟且無效的。此外,一些嚴(yán)重的網(wǎng)絡(luò)問題可能導(dǎo)致無法分配B的情況,從而使整個(gè)系統(tǒng)無用。我們可以做得更好。
2. 將事務(wù)B的分派推遲到有意義的時(shí)候。在我們的情況下,我們需要以某種方式等待A的發(fā)送。
第二種選擇似乎更有趣,但是在實(shí)踐中我們?cè)撊绾巫瞿兀客ㄟ^其本身的設(shè)計(jì),Substrate對(duì)運(yùn)行時(shí)內(nèi)部或鏈的業(yè)務(wù)邏輯一無所知。實(shí)際上,從其角度來看,Substrate就像不透明的字節(jié)數(shù)組一樣“看到”我們的交易。
這里的解決方案是“解釋” Substrate如何處理我們的交易以及如何正確排序它們。這是通過使用事務(wù)池向運(yùn)行時(shí)公開的專用TaggedTransactionQueue API完成的。
在Substrate中,每個(gè)事務(wù)都與兩組標(biāo)簽相關(guān)聯(lián):require和Provides。標(biāo)簽只是代表某個(gè)唯一值的任意字節(jié)向量。第一組描述此事務(wù)需要哪些標(biāo)簽,而第二組定義此事務(wù)提供的標(biāo)簽。
在上述情況下,我們需要通過聲明A提供一些標(biāo)簽而B消耗與其要求相同的標(biāo)簽來將事務(wù)A和B鏈接在一起。為了簡(jiǎn)單起見,我們可以使用UTXO哈希作為標(biāo)簽。
通過遍歷事務(wù)并查詢其標(biāo)記,事務(wù)池以一種順序組織它們,以使每個(gè)事務(wù)都可以滿足其要求。那些熟悉計(jì)算機(jī)科學(xué)的人可能會(huì)意識(shí)到這類似于拓?fù)漤樞颉?/p>
有時(shí)兩個(gè)事務(wù)不相互依賴,但又依賴于第三次事務(wù)。例如我們可能有交易A產(chǎn)生兩個(gè)輸出,交易B和C分別花費(fèi)這兩個(gè)輸出。這將導(dǎo)致B和C都依賴于A。拓?fù)渑判驙顟B(tài)規(guī)定必須在B和C之前調(diào)度A,但是未定義分發(fā)B和C的順序。在這種情況下,事務(wù)池使用其他條件來確定事務(wù)的優(yōu)先級(jí)。
經(jīng)典解決方案是將剩余值的數(shù)量用作優(yōu)先級(jí)。交易所有者有意留給當(dāng)局的資金越多,交易優(yōu)先級(jí)就越高,雙贏。