日付: 2021-03-06
ビットコインとイーサリアムは両方とも仮想通貨である。しかし、それぞれを支えるブロックチェーンインフラが圧倒的に違う。具体的に言えば、2008年に誕生したビットコインのブロックチェーンは取引情報を保存する為に使われるディジタル台帳に過ぎないが、これに対して、イーサリアムの基盤となるテクノロジーは用途が多岐にわたり、取引情報を書き込むことに加えてスマートコントラクト機能も実現する。この概念を簡単に理解できると思わないので今回は実際にイーサリアムブロックチェーンを使ってスマートコントラクトを実装し、スマートコントラクトをどうやって開発するのか、一体何に使うのか、といった質問に答えを共有したいと思う。
Dapp(日本語で自律分散型アプリケーション)を初めて開発したのは私の人生の無視できない一歩だった。以前、dappについて沢山の記事を読んだが、正直dapp開発の運用の甘みをよく捉えることが出来なかった。
数年前にビットコインの採掘に使われるハッシュ関数に基づいた計算手法を理解しようとした時に、そっかハッシュ関数を繰り返して計算するかとやっと分かったけど、好奇心をそそるような発見ではなかった。ビットコインのマイニングをよく見てみると、その計算手法が全く面白くなく少し硬い言い方にすれば相当馬鹿なアルゴリズムだと言っても過言ではない。ビットコインをどのようにマイニングするかというと、膨大な計算量を経って送金情報を検証するわけだ。イーサリアムの取引情報の検証にもほぼ同じような計算手法が用いられる。
仮想通貨マイニングの計算手法を別にして、イーサリアムの基盤となるブロックチェーンに注目しよう。イーサリアムブロックチェーンは仮想通貨取引の情報を保存するだけでなく、いわゆるスマートコントラクトを実現する為にも使われる。スマートコントラクトは実行可能なプログラムと想像してよく、銀行、不動産屋、生命保険会社の相談窓口で目の前に置かれて押印する契約書みたいに署名者たちの関係を表すものである。従来の契約書を署名すると法的関係が生まれるが、ブロックチェーン上に保存されたスマートコントラクトの場合、第三者による裁定がなくスマートコントラクトで定まった条件が満たされるとその結果がすぐに反映されることになる。中央集権型の仕組みに頼らず利用者たちの間に裁定を行えることが分散型金融における自律分散型アプリケーションの魅力だ。
よく考えると、あらゆる仮想通貨がブロックチェーン上に実装されたスマートコントラクトに過ぎないと分かる。なぜかと言えば、例えば仮想通貨を送金する際、送金元の残高から送金した金額を差し引いて、送金先の残高にその金額を足す必要がある。この作業を実現する為にスマートコントラクトが好適ではないか?
イーサリアムのブロックチェーン上で構築されたスマートコントラクトの数は2020年に200万件を越えた。明らかにスマートコントラクト開発がブームになっており、世界中の多くの国で自律分散型アプリケーションの開発を先行させている開発チームがイーサリアムのブロックチェーンをアプリケーション開発の基盤と採用する。
実は、新しい仮想通貨を発行したいならイーサリアムのブロックチェーンでスマートコントラクトを展開することで簡単にできる。その為にイーサリアムコミュニティの新しいプログラミング言語「Solidity、ソリジティー」で実装したソースコードを使う:
pragma solidity ^0.5.0;
contract Coin {
// The keyword "public" makes those variables
// easily readable from outside.
address public minter;
mapping (address => uint) public balances;
// Events allow light clients to react to
// changes efficiently.
event Sent(address from, address to, uint amount);
// This is the constructor whose code is
// run only when the contract is created.
constructor() public {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
require(amount < 1e60);
balances[receiver] += amount;
}
function send(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
このスマートコントラクトのソースコードはmint(発行)、send(送金)といった基本的な機能を実装する。次にイーサリアムの開発プラットフォーム(Remix)を使ってこのスマートコントラクトをコンパイルしてブロックチェーンに展開させる。
誰でも使っている稼働中のイーサリアムネットワークに展開しようとすると高額な手数料がかかってくるので、自分のパソコンで実行できる試用ネットワークを活用することを勧める。その為に$ sudo npm install -g ganache-cliを実行し、ganacheをインストールしよう。
ganacheを立ち上がるとRemixのDeployタブでWeb3 Providerを選択し、スマートコントラクトを実行中のネットワークに展開できる。
無事に展開できた後にNodeJsを使ってウェブアプリケーションを開発する必要がある。例えばこのようなソースコードを使って簡単にウェブサーバーを実装しよう:
const http = require('http');
var express = require('express');
var fs = require('fs');
const Web3 = require('web3');
let web3 = new Web3('ws://localhost:8545');
const hostname = '127.0.0.1';
const port = 3000;
web3.eth.getAccounts().then(console.log);
var app = express();
app.use(express.static('public'));
const MyContract = new web3.eth.Contract([{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}],"0xEb1cE03a26617328d2D558f14C2b0b8CE7FCf621")
const server = http.createServer((req, res) => {
if (req.url === '/send-money') {
MyContract.methods.send("0x69402829B26DDC039B462AfB4bF7b42a4E0ed7DA",2).send({from: '0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3'})
.then(function(result){
console.log(result);
});
res.writeHead(200, {'Content-Type': 'text/html'});
res.end();
}
if (req.url === '/mint-money1') {
MyContract.methods.mint("0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3",2).send({from: '0x674A0964f6516e1ed2d048DfaE685dD834eD201B'})
.then(function(result){
console.log(result);
res.writeHead(200, {'Content-Type': 'text/html'});
res.end();
});
MyContract.methods.balances("0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3").call({from: '0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3'})
.then(function(result){
console.log(result);
});
}
if (req.url === '/return-balance-1') {
MyContract.methods.balances("0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3").call({from: '0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3'})
.then(function(result){
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(result);
res.end();
});
}
if (req.url === '/return-balance-2') {
MyContract.methods.balances("0x69402829B26DDC039B462AfB4bF7b42a4E0ed7DA").call({from: '0xe4DcFDEDFe2b631d6BC586B9e18737E7e6C868e3'})
.then(function(result){
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(result);
res.end();
});
}
if (req.url === '/mint-money2') {
MyContract.methods.mint("0x69402829B26DDC039B462AfB4bF7b42a4E0ed7DA",2).send({from: '0x674A0964f6516e1ed2d048DfaE685dD834eD201B'})
.then(function(result){
console.log(result);
res.writeHead(200, {'Content-Type': 'text/html'});
res.end();
});
MyContract.methods.balances("0x69402829B26DDC039B462AfB4bF7b42a4E0ed7DA").call({from: '0x674A0964f6516e1ed2d048DfaE685dD834eD201B'})
.then(function(result){
console.log(result);
});
}
if (req.url === '/index') {
fs.readFile('index.html',function (err, data){
res.writeHead(200, {'Content-Type': 'text/html','Content-Length':data.length});
res.write(data);
res.end();
});
}
if(req.url.indexOf('bootstrap.min.css') != -1){ //req.url has the pathname, check if it conatins '.css'
fs.readFile(__dirname + '/public/bootstrap.min.css', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('signin.css') != -1){ //req.url has the pathname, check if it conatins '.css'
fs.readFile(__dirname + '/public/signin.css', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end();
});
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
ここで一番分かり難いところが、web3.eth.Contractの引数だった。スマートコントラクトのハッシュが展開時にブロックチェーンの出力で表示されるが、いわゆるABIデータの抽出が難事中の難事だった。ABIというのはApplication Binary Interface(アプリケーションバイナリインタフェース)の略であり、solcjsコンパイルを使うとスマートコントラクトのソースコードから抽出できる。
まずはsolcjsコンパイルを$ sudo npm install -g solc-js使ってインストールしよう。
そして次に、スマートコントラクトのファイルからABIデータを抽出しよう:
$ solcjs 「スマートコントラクト名称」.sol --abi
このコマンドの出力としてABIデータが保存される。
このデータをweb3.eth.Contractに渡してNodeJsウェブサーバーでブロックチェーンに書き込まれたスマートコントラクトをアクセスできるようになる。
そうすると、口座間の送金をMyContract.methods.send()、新しいコインの発行をMyContract.methods.mint()を引き起こすことで行うことができる。
NodeJsに精通した開発者ならウェブアプリケーションと試用ネットワークの接続の為の一部をすぐに実装できるし、一般利用者からの依頼に応答するソースコードの実装もそれほど難しいと思わない。
以下によりHTML、CSSとJavascriptを使った拙著を沢山の開発者に参考にして頂けることは幸せの極みだ。