stratus: initial dump

fun with eth-p2p and QML
This commit is contained in:
Jacek Sieka 2018-12-29 12:54:28 -06:00
parent ac75f0ed00
commit a2c1730486
No known key found for this signature in database
GPG Key ID: 6299FEB3EB6FA465
16 changed files with 523 additions and 0 deletions

View File

@ -168,3 +168,5 @@ type
MessageTimeout,
SubprotocolReason = 0x10
proc `$`*(v: Capability): string =
v.name & '/' & $v.version

View File

@ -1338,6 +1338,9 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
await postHelloSteps(result, response)
ok = true
info "Peer connected",
enode=result.remote.node,
capabilities=result.network.capabilities
except PeerDisconnected as e:
if e.reason != TooManyPeers:
debug "Unexpected disconnect during rlpxConnect", reason = e.reason
@ -1415,6 +1418,9 @@ proc rlpxAccept*(node: EthereumNode,
result.remote = newNode(initEnode(handshake.remoteHPubkey, address))
await postHelloSteps(result, response)
info "Peer accepted",
enode=result.remote.node,
capabilities=result.network.capabilities
except PeerDisconnected as e:
if e.reason == AlreadyConnected:
debug "Disconnect during rlpxAccept", reason = e.reason

5
examples/stratus/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
DOtherSide/
docker
*.AppImage
*.qmlc
tmp/

View File

@ -0,0 +1,9 @@
#!/bin/sh
rm -rf DOtherSide
git clone https://github.com/filcuc/DOtherSide.git --depth=1
cd DOtherSide
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=${1:-.} ..
make -j4 DOtherSideStatic

View File

@ -0,0 +1,23 @@
#!/bin/sh
[ -f docker/nim/bin/nim ] || {
mkdir -p docker
cd docker
git clone https://github.com/status-im/nim.git --depth 1
cd nim
sh build_all.sh
bin/nim c -d:release -o:bin/nimble dist/nimble/src/nimble
cd ../..
}
sudo apt-get install -y cmake
export PATH=$PATH:$PWD/docker/nim/bin
export PKG_CONFIG_PATH=/opt/qt/5.12.0/gcc_64/lib/pkgconfig
export LD_LIBRARY_PATH=/opt/qt/5.12.0/gcc_64/lib/
cd ../..
nimble develop
cd examples/stratus
make clean appimage

View File

@ -0,0 +1,35 @@
const
# bootnodes taken from go-ethereum
MainBootnodes* = [
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
"enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303",
"enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303",
"enode://158f8aab45f6d19c6cbf4a089c2670541a8da11978a2f90dbf6a502a4a3bab80d288afdbeb7ec0ef6d92de563767f3b1ea9e8e334ca711e9f8e2df5a0385e8e6@13.75.154.138:30303",
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",
"enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303",
]
# Whisper nodes taken from:
# https://github.com/status-im/status-react/blob/80aa0e92864c638777a45c3f2aeb66c3ae7c0b2e/resources/config/fleets.json
# These are probably not on the main network?
WhisperNodes* = [
"enode://66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7@206.189.243.176:30305",
"enode://0440117a5bc67c2908fad94ba29c7b7f2c1536e96a9df950f3265a9566bf3a7306ea8ab5a1f9794a0a641dcb1e4951ce7c093c61c0d255f4ed5d2ed02c8fce23@35.224.15.65:30305",
"enode://a80eb084f6bf3f98bf6a492fd6ba3db636986b17643695f67f543115d93d69920fb72e349e0c617a01544764f09375bb85f452b9c750a892d01d0e627d9c251e@47.89.16.125:30305",
"enode://4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b@206.189.243.178:30305",
"enode://552942cc4858073102a6bcd0df9fe4de6d9fc52ddf7363e8e0746eba21b0f98fb37e8270bc629f72cfe29e0b3522afaf51e309a05998736e2c0dad5288991148@130.211.215.133:30305",
"enode://aa97756bc147d74be6d07adfc465266e17756339d3d18591f4be9d1b2e80b86baf314aed79adbe8142bcb42bc7bc40e83ee3bbd0b82548e595bf855d548906a1@47.52.188.241:30305",
"enode://ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e@206.189.243.179:30305",
"enode://b33dc678589931713a085d29f9dc0efee1783dacce1d13696eb5d3a546293198470d97822c40b187336062b39fd3464e9807858109752767d486ea699a6ab3de@35.193.151.184:30305",
"enode://f34451823b173dc5f2ac0eec1668fdb13dba9452b174249a7e0272d6dce16fb811a01e623300d1b7a67c240ae052a462bff3f60e4a05e4c4bd23cc27dea57051@47.52.173.66:30305",
"enode://4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c@206.189.243.171:30305",
"enode://eb4cc33c1948b1f4b9cb8157757645d78acd731cc8f9468ad91cef8a7023e9c9c62b91ddab107043aabc483742ac15cb4372107b23962d3bfa617b05583f2260@146.148.66.209:30305",
"enode://7c80e37f324bbc767d890e6381854ef9985d33940285413311e8b5927bf47702afa40cd5d34be9aa6183ac467009b9545e24b0d0bc54ef2b773547bb8c274192@47.91.155.62:30305",
"enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@206.189.243.172:30305",
"enode://c7e00e5a333527c009a9b8f75659d9e40af8d8d896ebaa5dbdd46f2c58fc010e4583813bc7fc6da98fcf4f9ca7687d37ced8390330ef570d30b5793692875083@35.192.123.253:30305",
"enode://4b2530d045b1d9e0e45afa7c008292744fe77675462090b4001f85faf03b87aa79259c8a3d6d64f815520ac76944e795cbf32ff9e2ce9ba38f57af00d1cc0568@47.90.29.122:30305",
"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@206.189.243.177:30305",
"enode://2af8f4f7a0b5aabaf49eb72b9b59474b1b4a576f99a869e00f8455928fa242725864c86bdff95638a8b17657040b21771a7588d18b0f351377875f5b46426594@35.232.187.4:30305",
"enode://76ee16566fb45ca7644c8dec7ac74cadba3bfa0b92c566ad07bcb73298b0ffe1315fd787e1f829e90dba5cd3f4e0916e069f14e50e9cbec148bead397ac8122d@47.91.226.75:30305",
"enode://2b01955d7e11e29dce07343b456e4e96c081760022d1652b1c4b641eaf320e3747871870fa682e9e9cfb85b819ce94ed2fee1ac458904d54fd0b97d33ba2c4a4@206.189.240.70:30305",
"enode://19872f94b1e776da3a13e25afa71b47dfa99e658afd6427ea8d6e03c22a99f13590205a8826443e95a37eee1d815fc433af7a8ca9a8d0df7943d1f55684045b7@35.238.60.236:30305"
]

142
examples/stratus/main.qml Normal file
View File

@ -0,0 +1,142 @@
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
ApplicationWindow {
width: 1600
height: 1050
title: "Stratus"
visible: true
function joinChannel() {
root.join(channelTextField.text)
joinedChannels.append({"name": channelTextField.text})
channelTextField.text = ""
}
function sendMessage() {
messageTextField.text = "haha. you thought this actually worked? go fix it!"
}
Timer {
interval: 50; running: true; repeat: true
onTriggered: root.onTimer()
}
ListModel {
id: joinedChannels
// ListElement { name: "test" }
}
SplitView {
anchors.fill: parent
orientation: Qt.Horizontal
Item {
width: parent.width * 0.25
ColumnLayout {
anchors.margins: 5
anchors.fill: parent
spacing: 5
ListView {
model: joinedChannels
Layout.fillHeight: true
delegate: Row {
spacing: 0
Text { text: name[0].toUpperCase(); font.pointSize: 24; font.bold: true; width: height ; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; anchors.verticalCenter: parent.verticalCenter }
Text { text: "#"; font.pointSize: 16; verticalAlignment: Text.AlignVCenter; anchors.verticalCenter: parent.verticalCenter }
Text { text: name; font.pointSize: 16; verticalAlignment: Text.AlignVCenter; anchors.verticalCenter: parent.verticalCenter }
}
}
RowLayout {
Text { text: "#"; font.pointSize: 16 }
TextField { id: channelTextField; Layout.fillWidth: true; onAccepted: joinChannel() }
Button {
id: joinButton
text: "Join channel"
onClicked: joinChannel()
enabled: channelTextField.text != ""
}
}
}
}
Item {
Layout.fillWidth: true
ColumnLayout {
anchors.margins: 5
anchors.fill: parent
ListModel {
id: sampleMessages
ListElement {
channel: "test"
source: "xxx"
time: "1212"
message: "message that is really really long xxxxXXXXxxsacdsca asdcasd cas cdad casd cascdsa ad sadcasd csad csad csadmessage that is really really long xxxxXXXXxxsacdsca asdcasd cas cdad casd cascdsa ad sadcasd csad csad csad"
}
ListElement {
channel: "test"
source: "xxx"
time: "1212"
message: "message that is really really long"
}
ListElement {
channel: "test"
source: "xxx"
time: "1212"
message: "message that is really really long xxxxXXXXxxsacdsca asdcasd cas cdad casd cascdsa ad sadcasd csad csad csadmessage that is really really long xxxxXXXXxxsacdsca asdcasd cas cdad casd cascdsa ad sadcasd csad csad csad"
}
}
ListView {
id: messagesView
model: root.messageList
// model: sampleMessages
Layout.fillWidth: true
Layout.fillHeight: true
delegate: RowLayout {
width: messagesView.width
Text { text: "👾"; font.pointSize: 28 }
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Text { text: channel; font.bold: true }
Text { text: " ("; }
Text { text: source; }
Text { text: ")"; }
Item { Layout.fillWidth: true }
Text { text: time; }
}
Text { Layout.fillWidth: true; text: message; wrapMode: Text.WordWrap }
}
}
}
RowLayout {
TextField {
id: messageTextField;
Layout.fillWidth: true;
onAccepted: sendMessage()
}
Button {
id: sendButton
text: "Send"
onClicked: sendMessage()
enabled: messageTextField.text != ""
}
}
}
}
}
}

View File

@ -0,0 +1,66 @@
import NimQml, statusmessage, Tables
type
StatusMessageRoles {.pure.} = enum
Channel = UserRole + 1
Source = UserRole + 2
Message = UserRole + 3
Time = UserRole + 4
QtObject:
type
MessageList* = ref object of QAbstractListModel
messages*: seq[StatusMessage]
proc delete(self: MessageList) =
self.QAbstractListModel.delete
for message in self.messages:
message.delete
self.messages = @[]
proc setup(self: MessageList) =
self.QAbstractListModel.setup
proc newMessageList*(): MessageList =
new(result, delete)
result.messages = @[]
result.setup
method rowCount(self: MessageList, index: QModelIndex = nil): int =
return self.messages.len
method data(self: MessageList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.messages.len:
return
let message = self.messages[index.row]
let messageRole = role.StatusMessageRoles
case messageRole:
of StatusMessageRoles.Channel: result = newQVariant(message.channel)
of StatusMessageRoles.Source: result = newQVariant(message.source)
of StatusMessageRoles.Message: result = newQVariant(message.message)
of StatusMessageRoles.Time: result = newQVariant(message.time)
else: return
method roleNames(self: MessageList): Table[int, string] =
{ StatusMessageRoles.Channel.int:"channel",
StatusMessageRoles.Source.int:"source",
StatusMessageRoles.Message.int:"message",
StatusMessageRoles.Time.int:"time"}.toTable
proc add*(
self: MessageList, channel: string, source: string, message: string,
time: string) {.slot.} =
let sm = newStatusMessage(channel, source, message, time)
self.beginInsertRows(newQModelIndex(), self.messages.len, self.messages.len)
self.messages.add(sm)
self.endInsertRows()
proc del*(self: MessageList, pos: int) {.slot.} =
if pos < 0 or pos >= self.messages.len:
return
self.beginRemoveRows(newQModelIndex(), pos, pos)
self.messages.del(pos)
self.endRemoveRows

View File

@ -0,0 +1,43 @@
import NimQml, messagelist
QtObject:
type Root* = ref object of QObject
messageList*: MessageList
app: QApplication
cb: proc()
doJoin: proc(channel: string)
proc delete*(self: Root) =
self.QObject.delete
self.messageList.delete
proc setup(self: Root) =
self.QObject.setup
proc newRoot*(
app: QApplication, cb: proc(),
doJoin: proc(channel: string)): Root =
new(result)
result.messageList = newMessageList()
result.app = app
result.cb = cb
result.doJoin = doJoin
result.setup()
proc getMessageList(self: Root): QVariant {.slot.} =
return newQVariant(self.messageList)
proc onExitTriggered(self: Root) {.slot.} =
self.app.quit
proc onTimer(self: Root) {.slot.} =
self.cb()
QtProperty[QVariant] messageList:
read = getMessageList
proc add*(self: var Root, a, b, c, d: string) =
self.messageList.add(a, b, c, d)
proc join*(self: Root, channel: string) {.slot.} =
self.doJoin(channel)

View File

@ -0,0 +1,47 @@
import NimQml
QtObject:
type StatusMessage* = ref object of QObject
channel: string
source: string
message: string
time: string
proc delete*(self: StatusMessage) =
self.QObject.delete
proc setup(self: StatusMessage) =
self.QObject.setup
proc newStatusMessage*(channel, source, message, time: string): StatusMessage =
result = StatusMessage(
channel: channel,
source: source,
message: message,
time: time,
)
result.setup
proc channel*(self: StatusMessage): string {.slot.} =
self.channel
QtProperty[string] channel:
read = channel
proc source*(self: StatusMessage): string {.slot.} =
self.source
QtProperty[string] source:
read = source
proc message*(self: StatusMessage): string {.slot.} =
self.message
QtProperty[string] message:
read = message
proc time*(self: StatusMessage): string {.slot.} =
self.time
QtProperty[string] time:
read = time

View File

@ -0,0 +1,6 @@
docker run -it --rm --device /dev/fuse \
-v /home/arnetheduck/status/nim-eth-p2p:/nim-eth-p2p:Z \
-w /nim-eth-p2p/examples/stratus \
--cap-add SYS_ADMIN \
a12e/docker-qt:5.12-gcc_64 \
sh build-in-docker.sh

View File

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Stratus
Exec=stratus %F
Icon=stratus
Comment=Fun with nimbus
Terminal=true

View File

@ -0,0 +1,114 @@
#
# Ethereum P2P
# (c) Copyright 2018
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import
cligen, sequtils, options, strutils, parseopt, asyncdispatch2, json, times,
nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak, hash],
eth_keys, rlp, eth_p2p, eth_p2p/rlpx_protocols/[whisper_protocol],
eth_p2p/[discovery, enode, peer_pool], chronicles
import NimQml, model/root, fleets
{.passl: "-lDOtherSideStatic".}
# Normally this part is done with CMake/qmake
{.passl: gorge("pkg-config --libs --static Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets").}
{.passl: "-Wl,-as-needed".}
proc `$`*(digest: SymKey): string =
for c in digest: result &= hexChar(c.byte)
# Don't do this at home, you'll never get rid of ugly globals like this!
var
node: EthereumNode
rootItem: Root
proc subscribeChannel(
channel: string, handler: proc (msg: ReceivedMessage)) =
var ctx: HMAC[sha256]
var symKey: SymKey
discard ctx.pbkdf2(channel, "", 65356, symKey)
let channelHash = digest(keccak256, channel)
var topic: array[4, byte]
for i in 0..<4:
topic[i] = channelHash.data[i]
info "Subscribing to channel", channel, topic, symKey
discard node.subscribeFilter(newFilter(symKey = some(symKey),
topics = @[topic]),
handler)
proc handler(msg: ReceivedMessage) =
try:
# ["~#c4",["dcasdc","text/plain","~:public-group-user-message",
# 154604971756901,1546049717568,[
# "^ ","~:chat-id","nimbus-test","~:text","dcasdc"]]]
let
src =
if msg.decoded.src.isSome(): $msg.decoded.src.get()
else: ""
payload = cast[string](msg.decoded.payload)
data = parseJson(cast[string](msg.decoded.payload))
channel = data.elems[1].elems[5].elems[2].str
time = $fromUnix(data.elems[1].elems[4].num div 1000)
message = data.elems[1].elems[0].str
info "adding", full=(cast[string](msg.decoded.payload))
rootItem.add(channel, src[0..<8] & "..." & src[^8..^1], message, time)
except:
notice "no luck parsing", message=getCurrentExceptionMsg()
proc run(port: uint16 = 30303) =
let address = Address(
udpPort: port.Port, tcpPort: port.Port, ip: parseIpAddress("0.0.0.0"))
let keys = newKeyPair()
node = newEthereumNode(keys, address, 1, nil, addAllCapabilities = false)
node.addCapability Whisper
var bootnodes: seq[ENode] = @[]
for nodeId in MainBootnodes:
var bootnode: ENode
discard initENode(nodeId, bootnode)
bootnodes.add(bootnode)
asyncCheck node.connectToNetwork(bootnodes, true, true)
# main network has mostly non SHH nodes, so we connect directly to SHH nodes
for nodeId in WhisperNodes:
var whisperENode: ENode
discard initENode(nodeId, whisperENode)
var whisperNode = newNode(whisperENode)
asyncCheck node.peerPool.connectToNode(whisperNode)
node.protocolState(Whisper).config.powRequirement = 0
let app = newQApplication()
defer: app.delete
proc joinChannel(channel: string) =
subscribeChannel(channel, handler)
rootItem = newRoot(app, poll, joinChannel)
defer: rootItem.delete
let engine = newQQmlApplicationEngine()
defer: engine.delete
let rootVariant = newQVariant(rootItem)
defer: rootVariant.delete
engine.setRootContextProperty("root", rootVariant)
engine.load("main.qml")
app.exec()
dispatch(run)

View File

@ -0,0 +1,4 @@
--dynliboverrideall
-d:"chronicles_log_level=info"
gcc.linkerexe="g++"

View File

@ -0,0 +1,13 @@
mode = ScriptMode.Verbose
packageName = "stratus"
version = "0.0.0.1"
author = "Status Research & Development GmbH"
description = "Fun with nimbus"
license = "MIT"
bin = @["stratus"]
requires "eth_p2p",
"nimqml",
"cligen 0.9.18"

View File

@ -0,0 +1 @@
<svg width="124" height="124" xmlns="http://www.w3.org/2000/svg"><path d="M72.458 61.429c-7.431.427-12.088-1.299-19.52-.871a31.245 31.245 0 0 0-5.47.796C48.565 47.65 58.292 35.662 71.519 34.9c8.117-.467 16.23 4.53 16.67 12.642.433 7.973-5.664 13.307-15.73 13.886M52.503 89.46c-7.776.438-15.547-4.24-15.969-11.831-.415-7.462 5.427-12.454 15.07-12.996 7.118-.4 11.58 1.216 18.698.815a30.589 30.589 0 0 0 5.24-.745C74.493 77.528 65.175 88.748 52.503 89.46M62 .181C27.758.18 0 27.857 0 62s27.758 61.82 62 61.82c34.242 0 62-27.678 62-61.82C124 27.858 96.242.18 62 .18" fill="#ff9c00" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 607 B