15分钟开发一条可用区块链-Substrate区块链开发实战指南/教程-Gavin Wood’s block chain demo

2018-11-10 19:02 评论 0 条

摘要:据报道,以太坊联合创始人 伍德在柏林举行的Web3峰会上进行了一次演示,结果令在场开发人员惊叹不已,因为他展示了利用Substrate协议在15分钟构建可行的区块链。如下是海外开发者整理的Gavin Wood 在WEB3世界大会上的区块链Liveshow项目实现过程,暂时未翻译,请将就看,原文链接:https://hackmd.io/s/SkP1lLZhX

Gavin Wood’s Substrate Demo at Web3 Summit

Note: Substrate is a rapidly evolving project, which means that breaking changes may cause you problems when trying to follow the instructions below. Feel free to contact us with any problems you encounter.

This document will walk you through the steps required to duplicate the demo that Gavin Wood presented at the 2018 Web3 Summit, showing off how you can build a Runtime Library for a Substrate Blockchain in less than 30 min.

This tutorial will be written for a Mac OS X machine, and may require some finessing to get working on other operating systems.

Prerequisites

To start, we need to make sure you are able to run Substrate, which means installing Rust and other dependencies.

This can be done with this simple one-liner (it may take a little while, so grab some tea):

curl getsubstrate.io -sSf | sh

You will also need to set up a few more repositories into your working folder which were used in the demo:

You can do that with some script aliases that were loaded on your machine:

substrate-node-new substrate-node-template <author-name>
substrate-ui-new substrate

This will create a folder called substrate-node-template and substrate-ui with the corresponding repositories cloned in them. You can of course rename your projects in these commands, but for the sake of the clarity, we will continue with these folder names.

Step 1: Launch a Blockchain

If you have set up everything correctly, you can now start a substrate dev chain! In substrate-node-template run:

./target/release/substrate-node-template --dev

Note: If you run into an error like Error: UnknownBlock: Unknown block Hash(...), you will need to purge the chain files on your computer.

You can do that with:

rm -rf ~/Library/Application\ Support/Substrate/chains/development/

If everything is working it should start producing blocks!

To interact with the blockchain, you need to start the Substrate UI. Navigate to the substrate-uifolder and run:

npm run dev

Finally, if open your browser to http://localhost:8000, you should be able to interact with your new chain!

Step 2: Add Alice to your network

Alice is a hard-coded account in the substrate system, which is pre-funded to make your life easier. Open a new terminal, and using the installed substrate/subkey package, you can retrieve the seed for this pre-funded account:

subkey restore Alice

> Seed
> 0x416c696365202020202020202020202020202020202020202020202020202020 is account:
>    SS58: 5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ
    Hex: 0xd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f

Then in the Substrate UI, you can go into the Wallet section and add Alice using her seed and name.

If all is working correctly, you can now go into the Send Funds section and send funds from Alice to Default. You will see that Alice has a bunch of units pre-funded in her account, so send some and wait for the green checkmark and an updated balance for Default to show that the transfer has been successfully recorded on the blockchain.

Step 3: Create a new runtime module

Now it’s time to create our own runtime.

Open up the substrate-node-template folder and create a new file:

./runtime/src/demo.rs

This is where our new runtime module will live. Inline comments will hopefully give you insight to what the code is doing.

First, we will need to import a few libraries at the top of our file:

// Encoding library
use parity_codec::Encode;

// Enables access to the runtime storage
use srml_support::{StorageValue, dispatch::Result};

// Enables us to do hashing
use runtime_primitives::traits::Hash;

// Enables access to account balances
use {balances, system::{self, ensure_signed}};

All modules have a configuration trait, and we will specify that it requires the balances module (TODO: Add more details here)

pub trait Trait: balances::Trait {}

In this example, we will create a simple coin flip game. Users will pay an entry fee to play the game and then “flip a coin”. If they win they will get the contents of the pot. If they don’t win, they will get nothing. No matter the outcome, their fee will be placed into the pot after the game resolves for the next user to try and win.

To build this game, we will need to create the module declaration. These are the entry points that we handle, and the macro below takes care of the marshalling of arguments and dispatch. You can learn more about decl_module! [here](TODO: Add link of new wiki page once committed).

This game will have two entry points: one that lets us play the game, and one that lets us set the payment.

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn play(origin) -> Result {
            // Logic for playing the game
        }
        fn set_payment(_origin, value: T::Balance) -> Result {
            // Logic for setting the game payment
        }
    }
}

Now that we have established our module structure, we can add the logic which powers these functions. First, we will write the logic for playing our game:

fn play(origin) -> Result {
    // Ensure we have a signed message, and derive the sender's account id from the signature
    let sender = ensure_signed(origin)?;
    
    // Here we grab the payment, and put it into a local variable.
    // We are able to use Self::payment() because we defined it in our decl_storage! macro above
    // If there is no payment, exit with an error message
    let payment = Self::payment().ok_or("Must have payment amount set")?;

    // First, we decrease the balance of the sender by the payment amount using the balances module
    <balances::Module<T>>::decrease_free_balance(&sender, payment)?;
    
    // Then we flip a coin by generating a random seed
    // We pass the seed with our sender's account id into a hash algorithm
    // Then we check if the first byte of the hash is less than 128
    if (<system::Module<T>>::random_seed(), &sender)
    .using_encoded(<T as system::Trait>::Hashing::hash)
    .using_encoded(|e| e[0] < 128)
    {
        // If the sender wins the coin flip, we increase the sender's balance by the pot amount
        // `::take()` will also remove the pot amount from storage, which by default will give it a value of 0
        <balances::Module<T>>::increase_free_balance_creating(&sender, <Pot<T>>::take());
    }

    // No matter the outcome, we will add the original sender's payment back into the pot
    <Pot<T>>::mutate(|pot| *pot += payment);

    Ok(())
}

Next we will set up logic for initializing the game with the initial payment:

fn set_payment(_origin, value: T::Balance) -> Result {
    //If the payment has not been set...
    if Self::payment().is_none() {
        // ... we will set it to the value we passed in.
        <Payment<T>>::put(value);
        
        // We will also put that initial value into the pot for someone to win
        <Pot<T>>::put(value);
    }
    
    Ok(())
}

Then we will create the storage declaration. Using the decl_storage! macro, we can define the module specific data entries to be stored on-chain. Learn more about this macro here.

decl_storage! {
    trait Store for Module<T: Trait> as Demo {
        Payment get(payment) config(): Option<T::Balance>;
        Pot get(pot): T::Balance;
    }
}

And that’s it! This is how easy it can be to build new runtime modules. You can find a complete version of this file here to check your work.

Step 4: Integrate our new module into our runtime

To actually use our module, we need to tell our runtime that it exists. To do this we will be modifying the ./runtime/src/lib.rs file:

First, we need to declare that we are using the new demo module:

...
extern crate srml_upgrade_key as upgrade_key;

mod demo;     // Add this line

To keep track of this version of the blockchain, we can update our spec_name and impl_name:

...
pub const VERSION: RuntimeVersion = RuntimeVersion {
    spec_name: ver_str!("demo"),        // Update name to "demo"
    impl_name: ver_str!("demo-node"),   // Update name to "demo-node"

Next, we need to implement our configuration trait, which we can do at the end of all the other impl statements:

...
impl upgrade_key::Trait for Runtime {
    type Event = Event;
}


impl demo::Trait for Runtime {}      // Add this line

Finally, we put our new module into the runtime construction macro, construct_runtime!:

construct_runtime!(
    pub enum Runtime with Log(InternalLog: DigestItem<Hash, AuthorityId>) where
        Block = Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        System: system::{default, Log(ChangesTrieRoot)},
        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
        Balances: balances,
        UpgradeKey: upgrade_key,
        Demo: demo::{Module, Call, Storage, Config<T>},  // Add this line
    }
);

Again, you can find a complete version of this file here.

Step 5: Upgrade our chain

Now that we have created a new runtime module, it’s time for us to upgrade our blockchain.

To do this, first we will need to build our new runtime. Go into substrate-node-template and run:

./build.sh

If this completes successfully, it will update the following file:

./runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

You can go back to the Substrate UI, and in the Runtime Upgrade section, you can select this file and press upgrade.

If all went well, you can see at the top of the Substrate UI that the Runtime will have our updated name!

Step 6: Interacting with our new module

Finally, we can try and play the game we created. We will begin our interaction through the browser console.

On the page with the Substrate UI, press F12 to open your developer console. We will take advantage of some of the JavaScript libraries loaded on this page.

Before we can play the game, we need to initialize the set_payment from an account. We will call the function on behalf of Alice, who will generously initialize the pot with a signed message.

post({sender: runtime.balances.ss58Decode('F7Gh'), call: calls.demo.setPayment(1000)}).tie(console.log)

When this call completed, you should see {finalized: "..."}, showing that it has been added to the chain. We can check this by reading the balance in the pot:

runtime.demo.pot.then(console.log)

Which should return Number {1000}

Step 7: Updating our Substrate UI

Now that we see things are working in the background, it’s time to give our UI some new legs. Let’s add an interface so that someone can play our game. To do this we will need to modify the substrate-ui repository.

Open the ./src/app.jsx file, and in the readyRender() function, you will see the code which generates all the different UX components.

For example, this code snippet controls the Runtime Upgrade UX that we most recently interacted with:

<Divider hidden />
<Segment style={{margin: '1em'}} padded>
    <Header as='h2'>
        <Icon name='search' />
        <Header.Content>
            Runtime Upgrade
            <Header.Subheader>Upgrade the runtime using the UpgradeKey module</Header.Subheader>
        </Header.Content>
    </Header>
    <div style={{paddingBottom: '1em'}}></div>
    <FileUploadBond bond={this.runtime} content='Select Runtime' />
    <TransactButton
        content="Upgrade"
        icon='warning'
        tx={{
            sender: runtime.upgrade_key.key,
            call: calls.upgrade_key.upgrade(this.runtime)
        }}
    />
</Segment>

We can use this as a template for how we should add our game’s UX. After the last </Segment>, create a new one with the following code:

...
</Segment>
<Divider hidden />
<Segment style={{margin: '1em'}} padded>
    <Header as='h2'>
        <Icon name='game' />
        <Header.Content>
            Play the game
            <Header.Subheader>Play the game here!</Header.Subheader>
        </Header.Content>
    </Header>
    <div style={{paddingBottom: '1em'}}>
        <div style={{fontSize: 'small'}}>player</div>
        <SignerBond bond={this.player}/>
        <If condition={this.player.ready()} then={<span>
            <Label>Balance
                <Label.Detail>
                    <Pretty value={runtime.balances.balance(this.player)}/>
                </Label.Detail>
            </Label>
        </span>}/>
    </div>
    <TransactButton
        content="Play"
        icon='game'
        tx={{
            sender: this.player,
            call: calls.demo.play()
        }}
    />
    <Label>Pot Balance
        <Label.Detail>
            <Pretty value={runtime.demo.pot}/>
        </Label.Detail>
    </Label>
</Segment>

Beyond the updated text, you can see we are accessing a new this.player bond, which represents the user context playing the game.

Using this, we can get details like the user’s balance:

runtime.balances.balance(this.player)

And submit transactions on behalf of this user:

tx={{
    sender: this.player,
    call: calls.demo.play()
}}

Also notice that we are able to dynamically show content like the current balance of the pot in a similar way to how we retrieved it in the developer console:

<Label>Pot Balance
    <Label.Detail>
        <Pretty value={runtime.demo.pot}/>
    </Label.Detail>
</Label>

The only thing left for us to do, is to create the new player bond in our constructor() function at the top of the same file:

...
this.runtime = new Bond;
this.player = new Bond;         // Add this line

If you save your changes and reload the page, you should see your new UX! You can now try playing the game with the Default user:

Here you can see the player lost the game, which means that their 1000 units got added to the pot, and an additional 1 unit transaction fee was taken from their balance.

If we try a few more times, eventually the player will win the game, and the pot will be reset back to its starting amount for the next player:

Final Notes

That’s all folks! While you can’t actually make a profit playing this game, hopefully you see just how simple Substrate can make it to develop your next blockchain.

In summary, we showed you how to:

  • Download and install substrate to your machine in a single command
  • Set up a fresh substrate-node-template and substrate-ui so that you can start hacking right away
  • Program a new runtime for your blockchain
  • Upgrade your runtime, in real time and without forking, via the substrate-ui
  • Update the substrate-ui to reflect your new runtime features and functionality

Substrate is a rapidly developing technology, and we would love to get your feedback, answer questions, and learn more about what you want to build! Feel free to contact us using the details provided here.

分类:技术园区 标签:

评论已关闭!