rust 区块链开发_在Rust和基材中构建区块链:[开发人员分步指南]

rust 区块链开发

关于如何在开放源码框架Substrate中构建核心区块链基础架构的初学者友好教程。

在这个自我指导的教程中,您将从头开始构建无气体,类似比特币的区块链。 您将了解到,区块链比智能合约功能强大得多。

随意将这些内容的任何用途重新用于举办您自己的研讨会!

您将学到什么:

  • 实施UTXO分类帐模型,比特币的会计机制
  • 更改网络事务池逻辑
  • 配置创世块并编写一些测试
  • 使用Web客户端部署链并与活动节点进行交互

要求:

  • 无需区块链和Rust知识
  • 基本的编程经验
  • 预计时间:3小时

有关更详细的演练,在其中我可以彻底解释我们为什么要进行每个步骤,并在哪里花时间讨论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或未用交易输出。

UTXO像旅行支票一样工作:

  1. 身体上有支票的人都可以花钱。 换句话说,支出许可与金钱相关联,而不是帐号。
  2. 您无法撕开支票并花费其部分。 您必须花费整张支票,并在新支票中收到任何更改。

在下面的示例中,Bob有一个价值50美元的UTXO。 他想给Alice $ 0.5,因此销毁了$ 50的UTXO,并创建了两个新的UTXO,其价值分别为$ 0.5(对于Alice)和$ 49.5(作为零钱)。

图片来源:https://freedomnode.com/

密码学是一种仅允许Bob而不允许其他人使用他的UTXO的基本机制。

此信息存储在每个UTXO的3个字段之一中:

  1. UTXO所在位置的参考哈希 ,有点像数据库中的地址指针
  2. UTXO 的货币价值 ,例如$ 50
  3. 其“所有者”的 公钥

公钥与只有所有者才能拥有的秘密“私钥”相对应。 因此,要花费UTXO,所有者必须使用其对应的私钥以密码方式“签署”交易。 以后可以根据“公钥”检查签名以验证其有效性。

例如,当爱丽丝(Alice)花掉她的UTXO时,将如下所示:

她创建了一个新交易(灰色背景),提供了她的UTXO作为要花费的输入,并且在sigscript字段中,Alice提供了她的签名。

注意:爱丽丝正在“签署”整个交易的详细信息。 这样做的好处是可以锁定事务输出详细信息,以防止网络级别的篡改。 稍后,区块链将验证爱丽丝确实确实授权了整个交易的所有细节。

当您保护区块链免受恶意攻击时,我们将在第2部分中更详细地介绍安全隐患。

底物框架快速入门

您正在使用一个名为Substrate的开源区块链框架。 Substrate是基于Rust的 ,可编译为称为WebAssembly(WAsm)的二进制指令格式。

开箱即用,您可以获得核心的区块链组件,例如分布式数据库,对等网络层以及我们可以选择的各种共识机制。

在本教程中,我们将非常熟悉事务队列和运行时模块层

让我们开始编码!

第1部分:构建区块链的逻辑层

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步中,每次成功的消费交易后,您已经发出了此事件。

有关以上内容的逐行说明,请查看 此视频伴奏

第2部分:保护链免受恶意攻击

在第1部分中,我们搭建了UTXO的基本结构。 在本节中,我们将研究链的密码安全性并实现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 Module范围中,执行以下操作:

/// 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()
    }

第3部分:测试您的代码

在这一小部分中,您将学习如何构建区块链测试环境,在每次测试之前构建一些初始状态,以及如何使用可用于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区块链已完成!

卡在这一点上? 查看此存储库 ,了解您当前的实现情况。

第4部分:配置事务池逻辑

在本部分中,您将更改网络优先处理传入事务的方式以及处理比特币遇到的烦人的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部分 。

第5部分:最后的修饰和部署!

在最后一部分中,我们准备配置我们的区块链部署规范,指定要包含在创世块中的内容,然后进行部署!

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

有关以上内容的详细演练和逐行说明,请查看视频伴奏的最后一部分 。

恭喜你! 您现在已经从头开始构建和部署了类似比特币的区块链。

快速UI演示

在本教程中,您学习了如何更改基础分类账模型并在Substrate上构建了比特币链。 实际上,您可以在链上实现任何基本令牌模型。 您可以更改网络确定各种事务优先级的方式,即无需太多代码即可操作网络层。 您甚至可以通过使用剩余值奖励验证者来更改验证者的经济结构。 您可以轻松配置您的创世块。

希望本教程能够说服您尝试构建区块链基础设施并检查Substrate !

有什么问题吗

  • 在防暴聊天中对我们进行ping:https://riot.im/app/#/room/#substrate-technical:matrix.org
  • 在Twitter上向我发送DM:https://twitter.com/nczhu

翻译自: https://hackernoon.com/building-a-blockchain-in-rust-and-substrate-a-step-by-step-guide-for-developers-kc223ybp

rust 区块链开发

你可能感兴趣的:(rust 区块链开发_在Rust和基材中构建区块链:[开发人员分步指南])