Vibrant.kt - rapid prototyping and development of distributed applications (DApps) on the JVM
Nihao!
Introduction
I didn’t write anything for a long time, because the USE will not surrender itself, but I couldn’t help writing the class for the Baltic Competition . I could not squeeze out good ideas from nowhere, so I decided to plunge into a topic completely unfamiliar to me at that time (half a month ago), into the world of blockchain, cryptocurrencies, smart contracts and other clever English words. In the lessons I ran into the phone, reading a lot of texts about blockchain, peer2peer network and all that, gradually sorting it out. Everything went easier when I started writing simple prototypes in Javascript'e: once again I am convinced that everything is more understandable in the code than in the text. As a result, when I sort of figured it out, I decided on the topic of the work, which can be seen in the title of the article.
What is it all about and why is it necessary
Vibrant is a library written in Kotlin for rapid prototyping of distributed applications. The idea is that you can focus on certain aspects of the future system, replacing unrealized code with ready-made solutions. For example, you are planning to write a distributed chat.
Immediately there are several points that require implementation: how to provide a connection between peers, which communication protocol to use, and do I need UDP in LAN if I just try to chat, or can do it over TCP, or maybe just HTTP ... In general, a lot primary tasks, without which nothing will work. And to write a simple chat on a distributed platform, you will have to implement a certain amount of functionality. Vibrant, which consists of 2 packages, solves this very problem org.vibrant.core
- architecture abstraction and org.vibrant.base
- various implementations of abstractions from the core package, for example HTTPPeer
, HTTPJsonRPCPeer
- peer classes that communicate via http, the first is more abstract, the second uses the JSON RPC 2.0 protocol for communication between nodes.
In general, instead of writing a feast from scratch, we take a ready-made JSON RPC feast and use it. If there is a need to change the protocol or an acute desire to write something of their own - an abstraction allows the flag in hand.
And how to use it?
class Peer(port: Int, rpc: BaseJSONRPCProtocol): HTTPJsonRPCPeer(port, rpc){
val miners = arrayListOf()
fun broadcastMiners(jsonrpcRequest: JSONRPCRequest): List> {
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)
}
}
}
Here is a simple implementation HTTPJsonPeer
. It is impossible to say on it that it can only if you do not notice the argument of the constructor rpc: BaseJSONRPCProtocol
. If you initialize this class and run, then the HTTP server will be launched on the selected port, which will accept POST
JSON RPC requests for /rpc
endpoint, transform them into Kotlin objects and call the appropriate method in the passed one BaseJSONRPCProtocol
. So to say, plug and play.
Here is an example of methods that can be run through a JSON RPC request:
@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
)
}
So here you can quickly make a feast.
But where is the blockchain ?! There he is.
abstract class InMemoryBlockChain: BlockChain() {
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()
})
}
}
This is a class from a package org.vibrant.base
, inheriting it you can safely use a blockchain that exists only in memory. Such a blockchain is well suited, for example, for testing an application.
It is worth noting that the availability InMemoryBlockChain
does not limit the developer: you can write your own InMemoryBlockChain
, where h2 database is used instead of arraylist, but this already applies to the item “I do not want to steam over the boilerplate, give me a ready boilerplate and the ability to write my code”.
Preclusion
I am actively puffing about this project, there are still a lot of things that I would like to add, for example, implement Tangle in the image of Iota , write high-quality UDPPeer using, for example, Netty channels. Oh yes, now I'm working on smart contracts, which will also be possible plug and play
. I think it’s going to be funny
Conclusion
I will be very glad for the enthusiasm of readers in the form of pull requests.
Links to github:
Core package with abstraction
Base package with implementations
Working chat application