451 Unavailable For Legal Reasons

とあるゲームエンジニアのブログです

C#で最小構成のブロックチェーンを実装してみる

はじめに

この記事は BlockChain Advent Calendar 2017 の1日目の記事です。

qiita.com

ブロックチェーンとは

ブロックはデータと自身のハッシュ値と1つ前のブロックのハッシュ値を持つことでリスト構造となっているのがブロックチェーンというわけで、各スレッドをノードに見立ててランダムなタイミングでブロックを生成して定期的に他のノードに送信するアプリケーションを実装してみました。

github.com

エントリーポイント

6つのノードを生成して適当にP2P接続。並列実行しています。

using System.Threading.Tasks;

namespace SmallChain
{
    class Program
    {
        static void Main(string[] args)
        {
            RunAll().Wait();
        }

        public static async Task RunAll()
        {
            var node1 = new ClientNode("node1");
            var node2 = new ClientNode("node2");
            var node3 = new ClientNode("node3");
            var node4 = new ClientNode("node4");
            var node5 = new ClientNode("node5");
            var node6 = new ClientNode("node6");

            node1.CreateGenesisBlock();

            node1.Connect(node2);
            node2.Connect(node3);
            node3.Connect(node4);
            node4.Connect(node5);
            node5.Connect(node6);

            node1.Connect(node3);
            node2.Connect(node4);
            node3.Connect(node5);
            node4.Connect(node6);

            await Task.WhenAll(
                node1.Run(logging: true),
                node2.Run(),
                node3.Run(),
                node4.Run(),
                node5.Run(),
                node6.Run());
        }
    }
}

ノード

接続している他ノードのOnReceiveChainを毎秒1回呼び出すことでlocalChainを他のノードへ送信しています。また、random.Next(1, 6)の結果が5だった時にブロックを生成してlocalChainへ追加しています。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SmallChain
{
    public class ClientNode
    {
        private BlockChain localChain = new BlockChain();
        private List<ClientNode> connectedNodes = new List<ClientNode>();
        private readonly string name;
        private int counter;

        public ClientNode(string name)
        {
            this.name = name;
        }

        public void CreateGenesisBlock()
        {
            localChain.CreateGenesis();
        }

        public void OnReceiveChain(BlockChain remoteChain)
        {
            if (!remoteChain.Validate())
            {
                Console.WriteLine("invalid chain received");
                return;
            }

            if (localChain.GetLength() > remoteChain.GetLength())
            {
                Console.WriteLine("smaller length chain received");
                return;
            }

            localChain = remoteChain;
        }

        public void Connect(ClientNode node)
        {
            connectedNodes.Add(node);
        }

        public async Task Run(bool logging = false)
        {
            var random = new Random();

            while (true)
            {
                foreach (var node in connectedNodes)
                {
                    node.OnReceiveChain(localChain);
                }

                await Task.Delay(1000);

                var i = random.Next(1, 6);
                if (i == 5)
                {
                    counter++;
                    if (localChain.TryAddBlock(name + "-" + counter))
                    {
                        Console.WriteLine(name + " added block " + localChain.GetLatestBlock());
                    }

                    if (logging)
                    {
                        Console.WriteLine(localChain);
                    }
                }
            }
        }
    }
}

ブロックチェーン

マルチスレッド並列実行のための排他制御とチェーンの検証をしています。

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SmallChain
{
    public class BlockChain
    {
        private readonly ReaderWriterLockSlim chainsLock = new ReaderWriterLockSlim();
        private readonly List<Block> chain = new List<Block>();

        public Block GetLatestBlock()
        {
            chainsLock.EnterReadLock();
            try
            {
                return chain[chain.Count - 1];
            }
            finally
            {
                chainsLock.ExitReadLock();
            }
        }

        public int GetLength()
        {
            chainsLock.EnterReadLock();
            try
            {
                return chain.Count;
            }
            finally
            {
                chainsLock.ExitReadLock();
            }
        }

        public void CreateGenesis()
        {
            chainsLock.EnterWriteLock();
            try
            {
                chain.Add(Block.Genesis);
            }
            finally
            {
                chainsLock.ExitWriteLock();
            }
        }

        public bool TryAddBlock(string data = null)
        {
            chainsLock.EnterUpgradeableReadLock();
            try
            {
                if (chain.Count == 0)
                {
                    return false;
                }

                var previousBlock = GetLatestBlock();
                var nextBlock = new Block
                {
                    Index = previousBlock.Index + 1,
                    PreviousHash = previousBlock.Hash,
                    Timestamp = DateTime.UtcNow.ToUnixTime(),
                    Data = data
                };
                nextBlock.AppendHash();
                if (previousBlock.Validate(nextBlock))
                {
                    chainsLock.EnterWriteLock();
                    try
                    {
                        chain.Add(nextBlock);
                    }
                    finally
                    {
                        chainsLock.ExitWriteLock();
                    }
                    return true;
                }
            }
            finally
            {
                chainsLock.ExitUpgradeableReadLock();
            }
            return false;
        }

        public bool Validate()
        {
            chainsLock.EnterReadLock();
            try
            {
                if (chain.Count == 0)
                {
                    return false;
                }

                if (!Block.Genesis.Equals(chain[0]))
                {
                    return false;
                }

                if (chain.Count == 1)
                {
                    return true;
                }

                for (var i = 1; i < chain.Count - 1; i++)
                {
                    var block = chain[i];
                    if (block == null)
                    {
                        return false;
                    }

                    if (chain[i].PreviousHash != chain[i - 1].Hash)
                    {
                        return false;
                    }
                }
            }
            finally
            {
                chainsLock.ExitReadLock();
            }
            return true;
        }

        public override string ToString()
        {
            var builder = new StringBuilder();

            builder.AppendLine();
            builder.AppendLine("===========================");

            chainsLock.EnterReadLock();
            try
            {
                foreach (var block in chain)
                {
                    builder.AppendLine(block.ToString());
                }
            }
            finally
            {
                chainsLock.ExitReadLock();
            }

            builder.AppendLine("===========================");
            builder.AppendLine();
            return builder.ToString();
        }
    }
}

ブロック

データの入れ物の役割と、後続の新ブロックの検証をしています。

using System;
using System.Security.Cryptography;
using System.Text;

namespace SmallChain
{
    public class Block : IEquatable<Block>
    {
        public static readonly Block Genesis = new Block
        {
            Index = 0,
            PreviousHash = "0",
            Timestamp = 1512035841,
            Data = "genesis",
            Hash = "c5cd6055557f80eaf2ab3809d1e544d0158198acdd16b80bfa259bfaf521399f"
        };

        public int Index { get; set; }
        public string PreviousHash { get; set; }
        public long Timestamp { get; set; }
        public string Data { get; set; }
        public string Hash { get; set; }

        public void AppendHash()
        {
            Hash = CalculateHash();
        }

        public string CalculateHash()
        {
            using (var sha256 = SHA256.Create())
            {
                return sha256.ComputeHash(Encoding.UTF8.GetBytes(Index + PreviousHash + Timestamp + Data)).ToHexString();
            }
        }

        public bool Validate(Block newBlock)
        {
            if (newBlock == null)
            {
                Console.WriteLine("block is null");
                return false;
            }

            if (Index + 1 != newBlock.Index)
            {
                Console.WriteLine("invalid index");
                return false;
            }

            if (Hash != newBlock.PreviousHash)
            {
                Console.WriteLine("invalid previous hash");
                return false;
            }

            var hash = newBlock.CalculateHash();
            if (hash != newBlock.Hash)
            {
                Console.WriteLine("invalid hash: " + hash + " " + newBlock.Hash);
                return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 17;

                hash = hash * 23 + Index;
                hash = hash * 23 + PreviousHash?.GetHashCode() ?? 0;
                hash = hash * 23 + Timestamp.GetHashCode();
                hash = hash * 23 + Data?.GetHashCode() ?? 0;
                hash = hash * 23 + Hash?.GetHashCode() ?? 0;

                return hash;
            }
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Block);
        }

        public bool Equals(Block other)
        {
            if (other == null)
            {
                return false;
            }

            return Index == other.Index
                && PreviousHash == other.PreviousHash
                && Timestamp == other.Timestamp
                && Data == other.Data
                && Hash == other.Hash;
        }

        public override string ToString()
        {
            return $"Index: {Index}, Hash: {Hash}, Data: {Data}";
        }
    }
}

便利メソッド

UnixTime変換やHexString変換です。

using System;
using System.Collections.Generic;
using System.Text;

namespace SmallChain
{
    public static class Extensions
    {
        private static readonly DateTime UnixEpochDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        internal static long ToUnixTime(this DateTime datetime)
        {
            return (long)datetime.ToUniversalTime().Subtract(UnixEpochDateTime).TotalSeconds;
        }

        internal static string ToHexString(this IReadOnlyCollection<byte> bytes)
        {
            var sb = new StringBuilder(bytes.Count * 2);
            foreach (var b in bytes)
            {
                sb.Append(b.ToString("x2"));
            }
            return sb.ToString();
        }
    }
}

おわりに

ネットワーク機能やPoXを実装していないので、まだ分散型台帳技術であるブロックチェーンに期待される目的が達成できていない気はします。気が向いた時にそれらの機能を追加して行こうかなと考えてます。

ISUCON7予選に参加してパフォーマンスチューニングの代わりにC#実装のために全時間を使ってきた

とリプライ送ってみたがダメだったので予選中にC#実装を作成してみた。

github.com

パフォーマンスチューニングはせずに他言語のと同等なパフォーマンスになるように ASP.NET Core 2.0 + Dapper(MySql.Data) で実装しています。

ベンチマークツール等が公開されたらC#erもISUCON7予選問題やってみましょう。

もしかしたらJava実装も作るかも。

【Unite 2017 Tokyo】2017年の注目アセット100連発の中で使ったことある or 使ってみたい33連発

www.slideshare.net

この中で個人的に使ったことある or 使ってみたいアセットを厳選してみました。

Unity公式提供系

TextMesh Pro

https://www.assetstore.unity3d.com/jp/#!/content/84126

Unity Anima2D

https://www.assetstore.unity3d.com/jp/#!/content/79840

Cinemachine BaseRig

https://www.assetstore.unity3d.com/jp/#!/content/79898

Post Processing Stack

https://www.assetstore.unity3d.com/jp/#!/content/83912


スクリプト系

ICE Creature Control

https://www.assetstore.unity3d.com/jp/#!/content/35364

PuppetMaster

https://www.assetstore.unity3d.com/jp/#!/content/48977

Final IK

https://www.assetstore.unity3d.com/jp/#!/content/14290

UniRx - Reactive Extensions for Unity

https://www.assetstore.unity3d.com/jp/#!/content/17276

OpenCV for Unity

https://www.assetstore.unity3d.com/jp/#!/content/21088

Dlib FaceLandmark Detector

https://www.assetstore.unity3d.com/jp/#!/content/64314

Ink Painter

https://www.assetstore.unity3d.com/jp/#!/content/86210

Obi Cloth

https://www.assetstore.unity3d.com/jp/#!/content/81333

Uni Bullet Hell

https://www.assetstore.unity3d.com/jp/#!/content/19088


3D モデル系

“Unity-chan!” Model

https://www.assetstore.unity3d.com/jp/#!/content/18705

Amane Kisora-chan(FREE ver)

https://www.assetstore.unity3d.com/jp/#!/content/70581

Office and Police Station Pack (Modular)

https://www.assetstore.unity3d.com/jp/#!/content/74311

Japanese Otaku City

https://www.assetstore.unity3d.com/jp/#!/content/20359

Bird Flock Bundle

https://www.assetstore.unity3d.com/jp/#!/content/25576

Simple Dungeons - Cartoon Assets

https://www.assetstore.unity3d.com/jp/#!/content/75980


シェーダー系

DeepSky Haze

https://www.assetstore.unity3d.com/jp/#!/content/60409

Volumetric Fog & Mist

https://www.assetstore.unity3d.com/jp/#!/content/49858

UBER - Standard Shader Ultra

https://www.assetstore.unity3d.com/jp/#!/content/39959

Camera Filter Pack

https://www.assetstore.unity3d.com/jp/#!/content/18433


パーティクルシステム系

Realistic Effects Pack 4

https://www.assetstore.unity3d.com/jp/#!/content/85675

Mesh Effects

https://www.assetstore.unity3d.com/jp/#!/content/67803

Sci-Fi Arsenal

https://www.assetstore.unity3d.com/jp/#!/content/60519

KTK Fireworks Effects Volume1

https://www.assetstore.unity3d.com/jp/#!/content/26568


テクスチャ&マテリアル系

Sky5X One

https://www.assetstore.unity3d.com/jp/#!/content/6332

SUIMONO Water System

https://www.assetstore.unity3d.com/jp/#!/content/4387


アニメーション系

HQ Fighting Animation vol.1

https://www.assetstore.unity3d.com/jp/#!/content/17089

エディタ拡張系

Shader Forge

https://www.assetstore.unity3d.com/jp/#!/content/14147


完成プロジェクト系

MarkerLess AR Example

https://www.assetstore.unity3d.com/jp/#!/content/77560

FaceMask Example

https://www.assetstore.unity3d.com/jp/#!/content/79999

まとめ

Nintendo Switchがインディーズにも開放されたらこの中のいくつかのアセットを使って個人でゲーム作ってみたいと思っていたりします。

C#縛りでLT大会 & Meetup#2 でLTしてきた

イベントページ

forkwell.connpass.com

BTC FX 自動トレードのススメ

参照戻り値とUnsafeクラス C#7.0とパフォーマンス

doc.co

bitFlyer萬藤氏の資料は見つかり次第

総括

LT後の質問がたくさん出てきたのでソフトウェアトレードに興味は持って頂けたかなと(興味持ってもらっても特に得はしない)。

Visual Studio 2017 リリース記念勉強会に行ってきた

csugjp.connpass.com

C# 7

docs.com


ASP‌.NET Core 概要

docs.com


C# でブロックチェーン実装

www.slideshare.net


AWS Loves .NET: または私は如何に心配するのをやめて Mac を愛するようになったか

docs.com


Developing and Deploying .NET Core on Linux

docs.com


Visual Studio 2017 の新機能

docs.com

感想

ライブデモが多い、濃い内容でした。