Pull to refresh

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

Reading time 3 min
Views 3.4K

Нихао!


Введение


Я долго ничего не писал, потому что ЕГЭ само себя не сдаст, но к Балтийскому конкурсу я не мог не написать чего-нибудь классное. Хороших идей из ниоткуда я выдавить не мог, поэтому решил окунуться в абсолютно незнакомую мне на тот момент(пол месяца назад) тему, в мир блокчейна, криптовалют, смарт контрактов и других умных английских слов. На уроках утыкался в телефон, читая множество текстов про блокчейн, 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 пакет с реализациями
Рабочее приложение-чат

Tags:
Hubs:
+6
Comments 0
Comments Leave a comment

Articles