Uniqys Easy Framework
バックエンドの説明の前にUniqys Easy Framework (以下 Easy Framework) の説明をします。
https://uniqys.net では次のように説明されています。
Easy FrameworkではHTTPのリクエストメッセージをブロックチェーンのトランザクションとして扱います。
クライアントで署名されたリクエストは、トランザクションとしてブロックチェーンに記録され、認証されたリクエストとしてサーバーアプリケーションによって処理されます。 これによりクライアントからもサーバーからもブロックチェーンを意識せず、HTTPサーバー・クライアントアプリケーションとしてDAppsを実装することができます。
Easy FrameworkではMerkle Trieによってブロックチェーンに記録されるアプリケーションの状態を、Memcachedプロトコルから扱えます。 また、ユーザーアカウントに関する部分はREST APIが提供されます。 これらによりアプリケーションは、既存のMemcachedクライアントおよびHTTPクライアントを用いて、ブロックチェーンに記録される状態を操作する事ができます。
開発者がバックエンドに実装するのに必要なことを要約すると次の2点です。
- 開発者はこれまで通りHTTPサーバーを実装する
- データベースの操作は既存のMemcachedクライアントを利用できる
前回の記事で説明があった通り、CryptOsushiのバックエンドの実装にはPython 3.7を使用しました。HTTPサーバにはBottleを、Memcachedクライアントにはpymemcachedを利用しました。これら2つのライブラリは一般的なPythonライブラリです。
バックエンドの概要
第1回の記事で紹介した通りCryptOsushiでは、代替不可能なトークン (Non Fungible Token; NFT) である「sushi」とゲーム内専用通貨である「gari」を扱う機能があります。Easy Frameworkを通じて、この2つの機能をPythonでどのように実装したかを説明します。
sushi
sushiに関するコードは sushi.py に書かれています。
sushiはNFTであるため、固有のDNAを持った設計になっています。DNAに関する情報はEasy Frameworkからは提供されていません。そのため各sushiに一意に割り振られているidから計算されたハッシュ値がDNAに設定しています。
@dataclasses.dataclass
class Sushi:
id: int = 1
dna: str = None
owner: str = None
price: int = None
sushiの生成部分のコードです。
@app.route("/generate", method="POST")
@require_via_chain
def generate():
sender = get_sender()
print('sender', sender)
try:
transfer_gari(sender, OPERATOR_ADDRESS, 100)
except Exception as e:
return make_response({'error': 'error.exception', 'body': str(e)}, ok=False)
id = dao.incr_osushi_count()
keccak_hash = sha3.keccak_256()
keccak_hash.update(str(id).encode("utf-8"))
dna = keccak_hash.hexdigest()
osushi = dataclasses.asdict(Sushi(id, dna, sender))
dao.set_osushi(id, osushi)
return make_response({'sushi': osushi}, ok=True)
require_via_chain
はdecorator.pyに独自に定義しているデコレーターです。このデコレーターではリクエストに署名されているかを確認し、されていなければエラーを返します。これは、署名されていないリクエストはブロックチェーンの状態を変更できないようにEasy Frameworkで実装されているためです。
ブロックチェーンの状態の操作は、 dao.py に定義されているData Access Object (Dao) を通じて行われます。Daoはpymemcachedを利用してmemcacheプロトコルでブロックチェーンの状態を操作するクラスです。
これは Dao
のコンストラクタです。
class Dao:
def __init__(self, host, port):
self.db = Client(
(host, port),
default_noreply=False,
serializer=self.__json_serializer,
deserializer=self.__json_deserializer
)
default_noreply=False
を指定しているのが重要です。指定していないとpymemcachedはmemcachedサーバの応答を待つことなく動作します。この場合、HTTPレスポンスを返してトランザクションの処理が完了したあとに、ブロックチェーンの状態を更新しようとしてエラーとなります。 実際CryptOsushiを実装している時にこの現象を踏んでしまい、解決に時間を要しました。
sushiの売買に関するコードも説明した生成のコードと同じくHTTPサーバーの実装として自然に読むことができます。
gari
gariに関するコードは gari.py に書かれています。
ユーザーアカウントに関する部分はEasy FrameworkがREST APIとして提供しています。そのためいたって普通のHTTPリクエストを行うことで、gariに関する処理を実行できます。
例としてgariの送金部分の処理を見てみます。これはsenderから指定したアドレスへ送金を行うメソッドです。
送金するにはEasy FrameworkのInner APIのエンドポイントに向けて、だれに(to)、どのくらい(value)送金するかをbodyに与えてpostします。 API_BASE
はInner APIのエンドポイントです。 requests
はPythonのHTTPクライアントライブラリです。
def transfer_gari(sender, to, value):
sender_gari = refer_gari(sender)
if int(sender_gari) < int(value):
raise Exception('not enough gari.')
api_url = f"{API_BASE}/accounts/{sender}/transfer"
res = requests.post(
api_url,
data=json.dumps(dict({"to": str(to), "value": int(value)})),
headers={"Content-Type": "application/json"}
)
if res.status_code != requests.codes.ok:
raise Exception(res.text)
ちなみに、APIの中身をみると、fromとtoの各アカウントの残高を増減させていることがわかります。
.post('/accounts/:address/transfer', BodyParser(), async (ctx, _next) => {
const { to, value } = ctx.request.body as { to: string, value: number }
const fromAddr = maybeAddress(ctx.params.address)
const toAddr = maybeAddress(to)
ctx.assert(fromAddr, 400)
ctx.assert(toAddr, 400)
ctx.assert(value && typeof (value as any) === 'number', 400)
await this.mutex.use(async () => {
const fromAccount = await this.state.getAccount(fromAddr!)
const toAccount = await this.state.getAccount(toAddr!)
await this.state.setAccount(fromAddr!, fromAccount.decreaseBalance(value))
await this.state.setAccount(toAddr!, toAccount.increaseBalance(value))
})
ctx.status = 200
})
この他にも、残高やnonceを参照できるAPIなどがあります。APIはこれからさらに追加されていく予定です。
おわりに
この記事ではCryptOsushiのバックエンドの実装について詳細に紹介しました。
次の記事では、「CryptOsushiのフロントエンド」について紹介します。技術的な話だけでなく、UniqysKit 製のDAppsならではのUI/UXについて説明します。
ご期待ください!