摘要:据报道,以太坊联合创始人 伍德在柏林举行的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-ui
folder 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
andsubstrate-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.
转载请注明:15分钟开发一条可用区块链-Substrate区块链开发实战指南/教程-Gavin Wood’s block chain demo | 新星资讯-全球新兴数字资产(区块链资产)资讯平台
评论已关闭!