Vibrant.kt — быстрое прототипирование и разработка распределенных приложений (DApps) на JVM

    Нихао!


    Введение


    Я долго ничего не писал, потому что ЕГЭ само себя не сдаст, но к Балтийскому конкурсу я не мог не написать чего-нибудь классное. Хороших идей из ниоткуда я выдавить не мог, поэтому решил окунуться в абсолютно незнакомую мне на тот момент(пол месяца назад) тему, в мир блокчейна, криптовалют, смарт контрактов и других умных английских слов. На уроках утыкался в телефон, читая множество текстов про блокчейн, peer2peer сети и все такое, постепенно разбираясь. Все пошло легче, когда я начал писать простые прототипы на Javascript'e: в очередной раз убеждаюсь, что в коде все понятнее, нежели на тексте. В итоге, когда я вроде разобрался, я определился с темой работы, которую видно в заголовке статьи.


    Чего это вообще такое и зачем нужно


    Vibrant — это библиотека, написанная на Kotlin, для быстрого прототипирования распределенных приложений. Идея в том, что можно сосредотачиваться на определенных аспектах будущей системы, заменяя нереализованный код готовыми решениями. Например, Вы задумали написать распределенный чат.


    Сразу возникает несколько пунктов, которые требуют реализации: как обеспечить соединение между пирами, какой протокол общения юзать, а нужно ли мне UDP в локалке, если я просто пробую сделать чат, а может сделать по TCP, а может просто HTTP… В общем, много первостепенных задач, без решения которых ничего не будет работать. И чтобы написать простой чат на распределенной платформе, придется реализовывать некоторое количество функционала. Именно эту задачу решает Vibrant, состоящий из 2-х пакетов, org.vibrant.core — абстракция архитектуры и org.vibrant.base — различные реализации абстракций из core пакета, например, HTTPPeer, HTTPJsonRPCPeer — классы пиров, общение которых происходит по http, первый — более абстрактный, второй использует протокол JSON RPC 2.0 для общения между узлами.
    В общем, вместо написания пира с нуля, берем готовый JSON RPC пир и используем его. Если возникает необходимость сменить протокол или острое желание написать что-то свое — абстракция позволяет, флаг в руки.


    И как этим пользоваться?


    class Peer(port: Int, rpc: BaseJSONRPCProtocol): HTTPJsonRPCPeer(port, rpc){
    
        val miners = arrayListOf<RemoteNode>()
    
        fun broadcastMiners(jsonrpcRequest: JSONRPCRequest): List<JSONRPCResponse<*>> {
            return this.broadcast(jsonrpcRequest, this.miners)
        }
    
        fun addUniqueRemoteNode(remoteNode: RemoteNode, isMiner: Boolean = false) {
            super.addUniqueRemoteNode(remoteNode)
            if (isMiner && this.miners.find { it.address == remoteNode.address && it.port == remoteNode.port } == null) {
                this.miners.add(remoteNode)
            }
        }
    }

    Вот простая реализация HTTPJsonPeer. По ней невозможно сказать, что она умеет, только если не заметить аргумента конструктора rpc: BaseJSONRPCProtocol. Если инициализировать этот класс и запустить, то на выбранном порте будет запущен HTTP сервер, который принимает POST JSON RPC запросы на /rpc endpoint, трансформирует их в Kotlinовские объекты и вызывает соотвествующий метод в переданном BaseJSONRPCProtocol. Так сказать, plug and play.
    Вот пример методов, которые могут быть запущены через JSON RPC запрос:


    @JSONRPCMethod
        fun getLastBlock(request: JSONRPCRequest, remoteNode: RemoteNode): JSONRPCResponse<*>{
            return JSONRPCResponse(
                    result = node.chain.latestBlock().serialize(),
                    error = null,
                    id = request.id
            )
        }
    
        @JSONRPCMethod
        fun newBlock(request: JSONRPCRequest, remoteNode: RemoteNode): JSONRPCResponse<*>{
    
            val blockModel = BaseJSONSerializer.deserialize(request.params[0].toString().toByteArray()) as BaseBlockModel
            node.handleLastBlock(blockModel, remoteNode)
            return JSONRPCResponse(
                    result = node.chain.latestBlock().serialize(),
                    error = null,
                    id = request.id
            )
        }

    Вот так вот быстренько можно сделать пир.
    Но где же блокчейн?! Вот он.


    abstract class InMemoryBlockChain<B: BlockModel, out T: BlockChainModel>: BlockChain<B, T>() {
    
        protected val blocks = arrayListOf(this.createGenesisBlock())
    
        override fun latestBlock(): B = this.blocks.last()
    
        override fun addBlock(block: B): B {
            synchronized(this.blocks, {
                this.blocks.add(block)
                this.notifyNewBlock()
                return this.latestBlock()
            })
        }
    }

    Это класс из пакета org.vibrant.base, наследовав его можно спокойно юзать блокчейн, существующий только в памяти. Такой блокчейн хорошо подойдет, например, для тестирования приложения.


    Стоит отметить, что наличие InMemoryBlockChain не ограничивает разработчика: можно написать свой InMemoryBlockChain, где вместо arraylist'a используется h2 database, но это уже относится к пункту про "не хочу париться над boilerplate, дайте мне готовый boilerplate и возможность писать мой код".


    Предзаключение


    Я активно пыхчу над этим проектом, тут есть еще куча вещей, которые я хотел бы добавить, например, реализовать Tangle по образу и подобию Iota, написать качественный UDPPeer, используя, например, Netty channels. Ах да, сейчас я работаю над смарт контрактами, которые тоже можно будет plug and play. Думаю, получится забавно


    Заключение


    Буду премного рад энтузиазму читателей в виде pull request'ов.
    Ссылки на github:
    Core пакет с абстракцией
    Base пакет с реализациями
    Рабочее приложение-чат

    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое