rust 区块链开发
关于如何在开放源码框架Substrate中构建核心区块链基础架构的初学者友好教程。
在这个自我指导的教程中,您将从头开始构建无气体,类似比特币的区块链。 您将了解到,区块链比智能合约功能强大得多。
随意将这些内容的任何用途重新用于举办您自己的研讨会!
您将学到什么:
要求:
有关更详细的演练,在其中我可以彻底解释我们为什么要进行每个步骤,并在哪里花时间讨论Rust的特征,请查看此视频教程的伴奏:
让我们开始吧!
1.获取Rust&WebAssembly工具的最新稳定版本。 在您的终端中,运行以下命令:
# On Windows, download and run rustup-init.exe from https://rustup.rs instead
# On Macs:
curl https://sh.rustup.rs -sSf | sh
rustup update nightly
rustup target add wasm32-unknown-unknown —toolchain nightly
rustup update stable
cargo install —git https://github.com/alexcrichton/wasm-gc
如果您遇到任何问题或使用其他操作系统,请查看此详细的安装指南 。
2.在新的终端中,还克隆您本教程的样板代码的副本:
gitclone https://github.com/substrate-developer-hub/utxo-workshop.git
git fetch origin workshop:workshop
git checkout workshop
# [Optional] Once step 1 installations are completed
# Run the following commands to shorten future build time
cd /project_base_directory
cargo test -p utxo-runtime
此仓库还在master
分支中包含一个更新且完整的比特币实现(作弊),因此请确保您从头开始检查workshop
分支!
根据您的CPU,第一次安装Rust最多可能需要10-20分钟。
现在让我们使用这段时间来学习速成课程,了解比特币的工作原理,并探索我们正在使用的该开发人员SDK!
如果您拥有银行帐户,则已经熟悉“基于帐户”的分类帐模型。 在这里,您的银行帐户的总余额会在每次交易中计入或扣除。
比特币提供了一种根本不同的分类帐模型,称为UTXO或未用交易输出。
UTXO像旅行支票一样工作:
在下面的示例中,Bob有一个价值50美元的UTXO。 他想给Alice $ 0.5,因此销毁了$ 50的UTXO,并创建了两个新的UTXO,其价值分别为$ 0.5(对于Alice)和$ 49.5(作为零钱)。
图片来源:https://freedomnode.com/
密码学是一种仅允许Bob而不允许其他人使用他的UTXO的基本机制。
此信息存储在每个UTXO的3个字段之一中:
公钥与只有所有者才能拥有的秘密“私钥”相对应。 因此,要花费UTXO,所有者必须使用其对应的私钥以密码方式“签署”交易。 以后可以根据“公钥”检查签名以验证其有效性。
例如,当爱丽丝(Alice)花掉她的UTXO时,将如下所示:
她创建了一个新交易(灰色背景),提供了她的UTXO作为要花费的输入,并且在sigscript
字段中,Alice提供了她的签名。
注意:爱丽丝正在“签署”整个交易的详细信息。 这样做的好处是可以锁定事务输出详细信息,以防止网络级别的篡改。 稍后,区块链将验证爱丽丝确实确实授权了整个交易的所有细节。
当您保护区块链免受恶意攻击时,我们将在第2部分中更详细地介绍安全隐患。
您正在使用一个名为Substrate的开源区块链框架。 Substrate是基于Rust的 ,可编译为称为WebAssembly(WAsm)的二进制指令格式。
开箱即用,您可以获得核心的区块链组件,例如分布式数据库,对等网络层以及我们可以选择的各种共识机制。
在本教程中,我们将非常熟悉事务队列和运行时模块层
让我们开始编码!
1.在终端中,快速进行Rust编译器检查,以确保所有内容均正确下载:
cargo check -p utxo-runtime# Don’t worry if this takes a while, just let it run!
# You should see a few warnings but no breaking errors
2.在您最喜欢的Rust兼容IDE中打开项目。 我推荐IntelliJ或VSCode来突出显示Rust语法。
3.打开Runtime
子目录,该目录包含区块链运行时。 然后,打开utxo.rs
文件,您将在其中构建大多数比特币UTXO逻辑。
您将看到一个典型的Substrate入门模板,其中包含解释如何使用SDK的内联注释。 再往下看,您还应该看到可以在哪里编写单元测试。
4.在依赖项导入行之后,立即创建代表UTXO和UTXO事务所需的数据结构。
/// Single transaction to be dispatched
#[cfg_attr(feature = "std" , derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)]
pub struct Transaction {
/// UTXOs to be used as inputs for current transaction
pub inputs: Vec ,
/// UTXOs to be created as a result of current transaction dispatch
pub outputs: Vec ,
}
/// Single transaction input that refers to one UTXO
#[cfg_attr(feature = "std" , derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)]
pub struct TransactionInput {
/// Reference to an UTXO to be spent
pub outpoint: H256,
/// Proof that transaction owner is authorized to spend referred UTXO &
/// that the entire transaction is untampered
pub sigscript: H512,
}
/// Single transaction output to create upon transaction dispatch
#[cfg_attr(feature = "std" , derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)]
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 the whole `Transaction` and
/// signing it with a corresponding private key.
pub pubkey: H256,
}
有关以上内容的详细演练和逐行说明,请查看 此视频伴奏 。
5.指定要存储在区块链链状态上的内容。 这是在名为decl_storage
的Rust宏内完成的。 您将存储一个指向UTXO的256位指针的哈希图作为键,而UTXO结构本身作为值。 执行以下操作:
decl_storage! {trait Store for Module as Utxo {
/// All valid unspent transaction outputs are stored in this map.
/// Initial set of UTXO is populated from the list stored in genesis.
UtxoStore build(|config: &GenesisConfig| {
config.genesis_utxos
.iter()
.cloned()
.map(|u| (BlakeTwo256::hash_of(&u), u))
.collect::< Vec <_>>()
}): map H256 => Option ;
/// Total reward value to be redistributed among authorities.
/// It is accumulated from transactions during block execution
/// and then dispersed to validators on block finalization.
pub RewardTotal get(reward_total): Value;
}
add_extra_genesis {
config(genesis_utxos): Vec ;
}
}
注意,除了配置存储,我们还配置了如何*在创世块中填充此存储。 在第0区块,您将能够使用现有的UTXO向量为您的区块链播种以供消费!
6.创建交易签名,使您的比特币区块链用户可以使用UTXO。 让我们实现spend
功能:
// External functions: callable by the end user
decl_module! {
pub struct Module for enum Call where origin: T::Origin {
fn deposit_event () = default ;
/// Dispatch a single transaction and update UTXO set accordingly
pub fn spend (_origin, transaction: Transaction) -> DispatchResult {
// TransactionValidity{}
let transaction_validity = Self::validate_transaction(&transaction)?;
Self::update_storage(&transaction, transaction_validity.priority as u128 )?;
Self::deposit_event(Event::TransactionSuccess(transaction));
Ok (())
}
}
请注意,我们不久将为此花事务逻辑实现一些帮助器功能。
7.每当有链上交易时,区块链也可以发出事件。 设置您的区块链以识别TransactionSuccess
事件类型。
decl_event!(pub enum Event {
/// Transaction was executed successfully
TransactionSuccess(Transaction),
}
);
请注意,在第2步中,每次成功的消费交易后,您已经发出了此事件。
有关以上内容的逐行说明,请查看 此视频伴奏 。
在第1部分中,我们搭建了UTXO的基本结构。 在本节中,我们将研究链的密码安全性并实现UTXO事务逻辑。
实际上,比特币区块链可以防御许多漏洞。
1.现在,我们实现validate_transaction
函数以确保进行这些安全检查。
// "Internal" functions, callable by code.
impl Module {
pub fn validate_transaction (transaction: &Transaction) -> Result 'static str > {
// Check basic requirements
ensure!(!transaction.inputs.is_empty(), "no inputs" );
ensure!(!transaction.outputs.is_empty(), "no outputs" );
{
let input_set: BTreeMap<_, ()> =transaction.inputs.iter().map(|input| (input, ())).collect();
ensure!(input_set.len() == transaction.inputs.len(), "each input must only be used 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 total_output: Value = 0 ;
let mut output_index: u64 = 0 ;
let simple_transaction = Self::get_simple_transaction(transaction);
// Variables sent to transaction pool
let mut missing_utxos = Vec ::new();
let mut new_utxos = Vec ::new();
let mut reward = 0 ;
// Check that inputs are valid
for input in transaction.inputs.iter() {
if let Some (input_utxo) = ::get(&input.outpoint) {
ensure!(sp_io::crypto::sr25519_verify(
&Signature::from_raw(*input.sigscript.as_fixed_bytes()),
&simple_transaction,
&Public::from_h256(input_utxo.pubkey)
), "signature must be valid" );
total_input = total_input.checked_add(input_utxo.value).ok_or( "input value overflow" )?;
} else {
missing_utxos.push(input.outpoint.clone().as_fixed_bytes().to_vec());
}
}
// Check that outputs are valid
for output in transaction.outputs.iter() {
ensure!(output.value > 0 , "output value must be nonzero" );
let hash = BlakeTwo256::hash_of(&(&transaction.encode(), output_index));
output_index = output_index.checked_add( 1 ).ok_or( "output index overflow" )?;
ensure!(!::exists(hash), "output already exists" );
total_output = total_output.checked_add(output.value).ok_or( "output value overflow" )?;
new_utxos.push(hash.as_fixed_bytes().to_vec());
}
// If no race condition, check the math
if missing_utxos.is_empty() {
ensure!( total_input >= total_output, "output value must not exceed input value" );
reward = total_input.checked_sub(total_output).ok_or( "reward underflow" )?;
}
// Returns transaction details
Ok (ValidTransaction {
requires: missing_utxos,
provides: new_utxos,
priority: reward as u64 ,
longevity: TransactionLongevity::max_value(),
propagate: true ,
})
}
}
哎呀,很多。 要逐行解释发生的情况,请查看 视频伴奏的第2部分。
2.在第1部分中,我们假设使用了一些内部帮助器函数。 即验证交易后我们实际更新区块链存储的步骤。 在同一个impl
范围中,执行以下操作:
/// Update storage to reflect changes made by transaction
/// Where each utxo key is a hash of the entire transaction and its order in the TransactionOutputs vector
fn update_storage (transaction: &Transaction, reward: Value) -> DispatchResult {
// Calculate new reward total
let new_total = ::get()
.checked_add(reward)
.ok_or( "Reward overflow" )?;
::put(new_total);
// Removing spent UTXOs
for input in &transaction.inputs {
::remove(input.outpoint);
}
let mut index: u64 = 0 ;
for output in &transaction.outputs {
let hash = BlakeTwo256::hash_of(&(&transaction.encode(), index));
index = index.checked_add( 1 ).ok_or( "output index overflow" )?;
::insert(hash, output);
}
Ok (())
}
以及get_simple_transaction
:
// Strips a transaction of its Signature fields by replacing value with ZERO-initialized fixed hash.
pub fn get_simple_transaction (transaction: &Transaction) -> Vec < u8 > { //&'a [u8] {
let mut trx = transaction.clone();
for input in trx.inputs.iter_mut() {
input.sigscript = H512::zero();
}
trx.encode()
}
在这一小部分中,您将学习如何构建区块链测试环境,在每次测试之前构建一些初始状态,以及如何使用可用于Substrate / Rust中的测试的便捷助手功能。
1.构建测试环境:
// This function basically just builds a genesis storage key/value store according to our desired mockup.
// We start each test by giving Alice 100 utxo to start with.
fn new_test_ext () -> sp_io::TestExternalities {
let keystore = KeyStore::new(); // a key storage to store new key pairs during testing
let alice_pub_key = keystore.write().sr25519_generate_new(SR25519, Some (ALICE_PHRASE)).unwrap();
let mut t = system::GenesisConfig:: default ()
.build_storage::()
.unwrap();
t.top.extend(
GenesisConfig {
genesis_utxos: vec! [
TransactionOutput {
value: 100 ,
pubkey: H256::from(alice_pub_key),
}
],
.. Default :: default ()
}
.build_storage()
.unwrap()
.top,
);
// Print the values to get GENESIS_UTXO
let mut ext = sp_io::TestExternalities::from(t);
ext.register_extension(KeystoreExt(keystore));
ext
}
此函数根据我们在decl_storage
步骤1中写回的代码来构建创世纪存储键/值存储。 我们只需给Alice提供价值100的UTXO来开始消费即可开始测试。
2.编写一个简单的单元测试,测试一个简单的事务
#[test]
fn test_simple_transaction () {
new_test_ext().execute_with(|| {
let alice_pub_key = sp_io::crypto::sr25519_public_keys(SR25519)[ 0 ];
// Alice wants to send herself a new utxo of value 50.
let mut transaction = Transaction {
inputs: vec! [TransactionInput {
outpoint: H256::from(GENESIS_UTXO),
sigscript: H512::zero(),
}],
outputs: vec! [TransactionOutput {
value: 50 ,
pubkey: H256::from(alice_pub_key),
}],
};
let alice_signature = sp_io::crypto::sr25519_sign(SR25519, &alice_pub_key, &transaction.encode()).unwrap();
transaction.inputs[ 0 ].sigscript = H512::from(alice_signature);
let new_utxo_hash = BlakeTwo256::hash_of(&(&transaction.encode(), 0 as u64 ));
assert_ok!(Utxo::spend(Origin::signed( 0 ), transaction));
assert! (!UtxoStore::exists(H256::from(GENESIS_UTXO)));
assert! (UtxoStore::exists(new_utxo_hash));
assert_eq! ( 50 , UtxoStore::get(new_utxo_hash).unwrap().value);
});
}
3.不要忘记方便的常量!
// need to manually import this crate since its no include by default
use hex_literal::hex;
const ALICE_PHRASE: & str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten" ;
const GENESIS_UTXO: [ u8 ; 32 ] = hex!( "79eabcbd5ef6e958c6a7851b36da07691c19bda1835a08f875aa286911800999" );
4.使用cargo test -p utxo-runtime
在控制台中运行测试
并且您的测试应该通过,这意味着您的基本UTXO区块链已完成!
卡在这一点上? 查看此存储库 ,了解您当前的实现情况。
在本部分中,您将更改网络优先处理传入事务的方式以及处理比特币遇到的烦人的UTXO竞争条件。 具体来说,您将学习如何在无需太多代码的情况下更改区块链交易排队逻辑 。
考虑以下竞争条件,爱丽丝向鲍勃发送鲍勃的UTXO A,从而创建了一个属于鲍勃的新UTXOB。
幕后发生的事情是Alice的事务开始在网络中的节点之间传播:
立即,Bob决定使用此UTXO B,创建一个UTXOC。
因此,他的交易开始通过网络传播到尚未收到Alice消息的节点! 由于规则的网络延迟和其他现实世界的限制,这种情况很常见。
从Bob听到但尚未从Alice听到的节点将拒绝其交易,因为UTXO B尚不处于其区块链状态。 但是Bob的交易是有效的,因此由于这种竞争状况而导致的错误并不理想。
理想情况下,我们可以将有效事务排队到网络池中,然后等待满足先决条件。
1.幸运地,Substrate允许您通过一个API调用来更改事务排序逻辑。 如下配置runtime_api::TaggedTransactionQueue
特征:
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime {
fn validate_transaction (tx: as BlockT>::Extrinsic) -> TransactionValidity {
// Extrinsics representing UTXO transaction need some special handling
if let Some (&utxo::Call::spend( ref transaction)) = IsSubType::, Runtime>::is_sub_type(&tx.function) {
match >::validate_transaction(&transaction) {
// Transaction verification failed
Err (e) => {
sp_runtime::print(e);
return Err (TransactionValidityError::Invalid(InvalidTransaction::Custom( 1 )));
}
// Race condition, or Transaction is good to go
Ok (tv) => { return Ok (tv); }
}
}
// Fall back to default logic for non UTXO::execute extrinsics
Executive::validate_transaction(tx)
}
}
实际上,我们要告诉事务队列在处理使用该UTXO的新事务之前,先等待所需UTXO的哈希存在。 事务池将一直保留race-UTXO,直到在一定时间内满足此条件。
有关发生的情况的详细说明,请查看视频伴奏的第4部分 。
在最后一部分中,我们准备配置我们的区块链部署规范,指定要包含在创世块中的内容,然后进行部署!
1.立即进入src
子目录,然后找到chain_spec.rs
文件。
2.在testnet_genesis
函数中,追加以下配置以使用种子数据/ UTXO设置您的测试网。
// Dev mode genesis setup
fn testnet_genesis (
initial_authorities: Vec <(AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec ,
endowed_utxos: Vec ,
_enable_println: bool ) -> GenesisConfig
{
GenesisConfig {
system: Some (SystemConfig {
code: WASM_BINARY.to_vec(),
changes_trie_config: Default :: default (),
}),
...
utxo: Some (utxo::GenesisConfig {
genesis_utxos: endowed_utxos
.iter()
.map(|x|
utxo::TransactionOutput {
value: 100 as utxo::Value,
pubkey: H256::from_slice(x.as_slice()),
})
.collect()
}),
}
}
3.在fn load
,确保还包括应该拥有这些UTXO的公共密钥的生成集。
// Genesis set of pubkeys that own UTXOs
vec! [
get_from_seed::( "Alice" ),
get_from_seed::( "Bob" ),
],
4.在终端中,以开发人员模式编译并构建您的区块链版本:
# Initialize your Wasm Build environment:
./scripts/init.sh
# Build Wasm and native code:
cargo build --release
5.启动您的节点,您的区块链将开始产生区块:
./target/release/utxo-workshop --dev
# If you already modified state, run this to purge the chain
./target/release/utxo-workshop purge-chain --dev
有关以上内容的详细演练和逐行说明,请查看视频伴奏的最后一部分 。
在本教程中,您学习了如何更改基础分类账模型并在Substrate上构建了比特币链。 实际上,您可以在链上实现任何基本令牌模型。 您可以更改网络确定各种事务优先级的方式,即无需太多代码即可操作网络层。 您甚至可以通过使用剩余值奖励验证者来更改验证者的经济结构。 您可以轻松配置您的创世块。
希望本教程能够说服您尝试构建区块链基础设施并检查Substrate !
翻译自: https://hackernoon.com/building-a-blockchain-in-rust-and-substrate-a-step-by-step-guide-for-developers-kc223ybp
rust 区块链开发